React + Vite 实现 antd 组件按需自动引入

React + Vite 实现 antd 组件按需自动导入

最近在写一个 React + TypeScript + Vite 的后台管理系统时遇到了一些问题。

ts 复制代码
import { 
  Button, 
  Input, 
  InputNumber, 
  Card, 
  Form, 
  Row, 
  Col, 
  Switch, 
  Badge, 
  Dropdown, 
  Divider
} from 'antd'

相信大家对上述代码一定不陌生,当组件中引入大量 antd 组件时,我们不得书写一个如此长的 import 声明。

这些声明在代码里极为啰嗦的,尤其是搭配 Prettier 的时候,当每行的文本数超过 Prettier 的设定时,Prettier 会帮你自动换行,导致一个 import 声明就长达十几或二十几行。

如何避免这些繁琐的声明,且又能享受到 ES Module 静态引入的 Tree Shaking 呢?

unplugin-auto-import

在 Vite 中,我们可以使用 unplugin-auto-import 这个插件来解决上述问题。这个插件可以帮我们自动导入任何常量、函数、类、组件等。

如果你使用的是 Vue,可以搭配 unplugin-vue-components 来实现 Vue 组件的自动引入。

关于插件的使用方法详细请看 unplugin-auto-importunplugin-vue-components 的文档,本文不再赘述。

在 React 中使用

unplugin-auto-import 官方为我们提供了许多预设配置,如 react、react-router 等,这些预设这边称之为 "静态自动引入"。

所谓 "静态自动引入",是因为项目启动后,会根据这些预设生成自动引入的声明,即便你没有在项目中使用如 useLayoutEffect 这样的 Hook,插件也会自动生成该声明。

通常这样的预设比较适合的场景为:

  • 包比较小,无须考虑打包后文件大小的问题
  • 使用率较高,如 useStateuseEffect 这样的 Hook,几乎每个项目都会使用到

但是像 antd 这样的组件库,通常都导出了大量的组件,而很多组件通常是项目用不到的,此时 "静态自动引入" 就不合适了,我们需要使用一种 "动态自动引入" 的方法来解决问题。

值得庆幸的是,unplugin-auto-import 提供了 resolver(解析器) 来帮助我们实现 "动态自动引入"。

unplugin-auto-import-antd

为了实现自动引入 antd,我写了一个叫 unplugin-auto-import-antd 的解析器,已在 GitHub 开源。

特性

  • 支持 Vite, Webpack
  • 支持自动引入 antd 组件
  • 支持使用自定义前缀重命名组件
  • 支持通过包的别名引入

仅支持 antd v5+.

安装

npm 安装

bash 复制代码
npm i -D unplugin-auto-import-antd unplugin-auto-import

yarn 安装

bash 复制代码
yarn add -D unplugin-auto-import-antd unplugin-auto-import

pnpm 安装

bash 复制代码
pnpm add -D unplugin-auto-import-antd unplugin-auto-import

bun 安装

bash 复制代码
bun add -D unplugin-auto-import-antd unplugin-auto-import

使用

Vite

ts 复制代码
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import AntdResolver from 'unplugin-auto-import-antd'

export default defineConfig({
  plugins: [
    AutoImport({
      resolvers: [AntdResolver()]
    })
  ]
})

Webpack

js 复制代码
// webpack.config.js
const AntdResolver = require('unplugin-auto-import-antd')

module.exports = {
  /* ... */
  plugins: [
    require('unplugin-auto-import/webpack')({
      resolvers: [AntdResolver()]
    })
  ]
}

自定义前缀

ts 复制代码
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import AntdResolver from 'unplugin-auto-import-antd'

export default defineConfig({
  plugins: [
    AutoImport({
      resolvers: [
        AntdResolver({
          prefix: 'A'
        })
      ]
    })
  ]
})

使用自定义前缀,如 A, 书写组件的方式有原本的 Button 变为 AButton

等价于 import { Button as AButton } from 'antd'

包别名引入

ts 复制代码
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import AntdResolver from 'unplugin-auto-import-antd'

export default defineConfig({
  plugins: [
    AutoImport({
      resolvers: [
        AntdResolver({
          packageName: 'antd-v5'
        })
      ]
    })
  ]
})

通过别名安装 antd,如 antd-v5。

等价于 import { Button } from 'antd-v5'

如何写一个解析器

定义预设

我们需要定义一个数组,用于存放所有 antd 导出组件的名称,解析器是通过将未声明的变量与这个数组进行匹配,来判断是否要生成一个自动引入的声明。

ts 复制代码
// antd 内置组件
export const antdBuiltInComponents = [
  'Affix',
  'Alert',
  'Anchor',
  'App',
  'AutoComplete',
  'Avatar',
  'BackTop',
  'Badge',
  'Breadcrumb',
  'Button',
  'Calendar',
  'Card',
  'Carousel',
  'Cascader',
  'Checkbox',
  'Col',
  'Collapse',
  'ColorPicker',
  'ConfigProvider',
  'DatePicker',
  'Descriptions',
  'Divider',
  'Drawer',
  'Dropdown',
  'Empty',
  'Flex',
  'FloatButton',
  'Form',
  'Grid',
  'Image',
  'Input',
  'InputNumber',
  'Layout',
  'List',
  'Mentions',
  'Menu',
  'message',
  'Modal',
  'notification',
  'Pagination',
  'Popconfirm',
  'Popover',
  'Progress',
  'QRCode',
  'Radio',
  'Rate',
  'Result',
  'Row',
  'Segmented',
  'Select',
  'Skeleton',
  'Slider',
  'Space',
  'Spin',
  'Statistic',
  'Steps',
  'Switch',
  'Table',
  'Tabs',
  'Tag',
  'theme',
  'TimePicker',
  'Timeline',
  'Tooltip',
  'Tour',
  'Transfer',
  'Tree',
  'TreeSelect',
  'Typography',
  'Upload',
  'Watermark'
]

工具函数

这边工具函数主要是支持引入时添加前缀的功能,getAntdComponentsMap 将上述数组转化为形为 [带前缀的组件名,antd 导出的组件名] 的Map。这便于解析器通过我们的带前缀的组件名,找到 antd 原始导出的组件名,然后根据该映射创建别名导入。

同时我们注意到并非所有 antd 导出的都是大陀峰命名的组件,像 thememessagenotification 这样的导出,我们要进行特殊处理,将其转换为大陀峰格式。

ts 复制代码
import { antdBuiltInComponents } from './preset'

// 处理特殊的组件名(非大驼峰)
export const getComponentName = (name: string) => {
  if (name === 'theme') {
    return 'Theme'
  }
  if (name === 'message') {
    return 'Message'
  }
  if (name === 'notification') {
    return 'Notification'
  }
  return name
}

// 获取添加前缀后的组件名映射
export const getAntdComponentsMap = (prefix?: string): Map<string, string> =>
  antdBuiltInComponents.reduce(
    (map, name) => map.set(`${prefix ?? ''}${getComponentName(name)}`, name),
    new Map()
  )

解析器函数

解析器函数实现很简单,会返回一个包含 typeresolve 的对象。type 我们指定为 component 即组件。resolve 是解析函数,接受我们在代码中定义的未声明的变量名,返回一个用于自动导入的对象。

  • from: 从 xxx 包导入,如 from 'antd'
  • name: 导入的原生名称,如 import { Button }
  • as: 别名导入,如 import { Button as AButton} from 'antd'
ts 复制代码
import type { Resolver } from 'unplugin-auto-import/types'

import { antdBuiltInComponents } from './preset'
import type { AntdResolverOptions } from './types'
import { getAntdComponentsMap } from './utils'

export const antdResolver = (options: AntdResolverOptions = {}): Resolver => {
  const { prefix, packageName: from = 'antd' } = options
  const antdComponentsMap = getAntdComponentsMap(prefix)
  return {
    type: 'component',
    resolve: (originName: string) => {
      if (!prefix) {
        if (antdBuiltInComponents.includes(originName)) {
          return {
            from,
            name: originName
          }
        }
      } else {
        // 如果设定前缀,则重命名引入
        const name = antdComponentsMap.get(originName)
        if (name) {
          return {
            from,
            name,
            as: originName
          }
        }
      }
      return undefined
    }
  }
}

总结

可以自由设定预设集合,或使用一种特殊的方法去匹配需要解析器动态导入的组件(如 Naive UI 组件默认都以 N 开头),无需拘泥于本文的实现,本文仅提供一个实现解析器的思路。

其他

最近在开发一个叫 Dolphin Admin 的后台管理系统,目前在 GitHub 开源。

目前预览版只有 Vue 版本,React 版本还在缓慢进行中。

有兴趣的可以 🌟 一下,到时候会出详细的文档,包括后端(基于 Nest)。

相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui