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-import
和 unplugin-vue-components
的文档,本文不再赘述。
在 React 中使用
unplugin-auto-import
官方为我们提供了许多预设配置,如 react、react-router 等,这些预设这边称之为 "静态自动引入"。
所谓 "静态自动引入",是因为项目启动后,会根据这些预设生成自动引入的声明,即便你没有在项目中使用如 useLayoutEffect
这样的 Hook,插件也会自动生成该声明。
通常这样的预设比较适合的场景为:
- 包比较小,无须考虑打包后文件大小的问题
- 使用率较高,如
useState
、useEffect
这样的 Hook,几乎每个项目都会使用到
但是像 antd 这样的组件库,通常都导出了大量的组件,而很多组件通常是项目用不到的,此时 "静态自动引入" 就不合适了,我们需要使用一种 "动态自动引入" 的方法来解决问题。
值得庆幸的是,unplugin-auto-import
提供了 resolver
(解析器) 来帮助我们实现 "动态自动引入"。
unplugin-auto-import-antd
为了实现自动引入 antd,我写了一个叫 unplugin-auto-import-antd
的解析器,已在 GitHub 开源。
- npm 地址:www.npmjs.com/package/unp...
- GitHub 地址:github.com/recallwei/u...
特性
- 支持
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 导出的都是大陀峰命名的组件,像 theme
,message
,notification
这样的导出,我们要进行特殊处理,将其转换为大陀峰格式。
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()
)
解析器函数
解析器函数实现很简单,会返回一个包含 type
,resolve
的对象。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 开源。
- React 版本基于 React + TypeScript + antd
- GitHub 地址(React):github.com/bit-ocean-s...
- Vue 版本基于 Vue + TypeScript + Naive UI
- GitHub 地址(Vue):github.com/bit-ocean-s...
- 线上预览地址:dolphin-admin.bit-ocean.studio/
目前预览版只有 Vue 版本,React 版本还在缓慢进行中。
有兴趣的可以 🌟 一下,到时候会出详细的文档,包括后端(基于 Nest)。