React + Vite 实现 antd 组件按需自动引入
最近在写一个 React
+ TypeScript
+ Vite
的后台管理系统时遇到了一些问题。
import {
Button,
Input,
InputNumber,
Card,
Form,
Row,
Col,
Switch,
Badge,
Dropdown,
Divider
} from 'antd'
相信大家对上述代码一定不陌生,当组件中引入大量 antd
组件时,我们不得书写一个如此长的 import 声明。
这些声明在代码里极为啰嗦的,尤其是搭配 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 (Deprecated)
为了实现自动引入 antd
,我写了一个叫 unplugin-auto-import-antd
的解析器,已在 GitHub 开源。
补充:
2024/06/23,为了该更方便去维护这些库,我将这个库迁移至 @bit-ocean/auto-import
@bit-ocean/auto-import
- npm:www.npmjs.com/package/@bi…
- GitHub:github.com/bit-ocean-s…
- 示例项目:github.com/bit-ocean-s…
特性
- 支持
Vite
,Webpack
等 - 支持自动引入
antd
组件 - 支持使用自定义前缀重命名组件
- 支持通过包的别名引入
仅支持
antd
v5+.
安装
npm 安装
npm i -D unplugin-auto-import-antd unplugin-auto-import
yarn 安装
yarn add -D unplugin-auto-import-antd unplugin-auto-import
pnpm 安装
pnpm add -D unplugin-auto-import-antd unplugin-auto-import
bun 安装
bun add -D unplugin-auto-import-antd unplugin-auto-import
使用
Vite
// 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
// webpack.config.js
const AntdResolver = require('unplugin-auto-import-antd')
module.exports = {
/* ... */
plugins: [
require('unplugin-auto-import/webpack')({
resolvers: [AntdResolver()]
})
]
}
自定义前缀
// 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'
。
包别名引入
// 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
导出组件的名称,解析器是通过将未声明的变量与这个数组进行匹配,来判断是否要生成一个自动引入的声明。
// 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
这样的导出,我们要进行特殊处理,将其转换为大驼峰格式。
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'
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
开头),无需拘泥于本文的实现,本文仅提供一个实现解析器的思路。