用vue3+vite实现一个按需引入的组件库
最近在重构项目中需要将一些常用的组件封装成组件库,在使用的时候需要全量导入组件及样式,这样十分的不优雅,于是就有了这个demo项目。
明确要实现什么
目标:组件库支持按需导入,最好可以自动导入
之前打包出来的产物只有一个js文件和css文件,如果需要按需导入的话,每个组件的打包产物应该是独立的,包括css样式文件
实现
拆分组件
组件的源码
![image-20230518232831884](/Users/muyeyong/Library/Application Support/typora-user-images/image-20230518232831884.png)
组件的打包使用vite实现的,去看看vite有哪些配置可以支持实现我们的需求
有两个方法可以实现: preservemodules 和 input 配置
首先看下这两个有什么区别
// vite. config.ts,利用inpu属性
rollupOptions: {
...
input: Object.fromEntries(globSync(['component/**/*.ts', 'component/*.ts']).map(file => [
path.relative(
'component',
file.slice(0, file.length - path.extname(file).length)
),
fileURLToPath(new URL(file, import.meta.url))
]))
...
}
//vite.config.ts 利用 preservemodules
rollupOptions: {
external: ['cheerio', 'vue', 'vue-router'],
input: ['component/index.ts'],
output: [
{
// 打包格式
format: 'es',
// 打包后文件名
entryFileNames: '[name].mjs',
// 让打包目录和我们的组件库目录对应
preserveModules: true,
exports: 'named',
// 配置打包根目录
dir: './hope/es'
},
{
// 打包格式
format: 'cjs',
// 打包后文件名
entryFileNames: '[name].js',
// 让打包目录和我们的组件库目录对应
preserveModules: true,
exports: 'named',
// 配置打包根目录
dir: './hope/lib'
}
]
}
这两个方法都按照组件目录打包成js文件,但是preservemodules
打包出的内容太杂且层次太深,input
打包出来的结果就清爽很多,其实rollup文档中有说过这种情况推荐使用input
配置
于是利用rollupOptions.input
属性实现了组件库的分开打包
拆分CSS
在vite中样式文件都会被打包成一个文件,这里利用gulp对样式文件进行处理
// script/build/index.ts 打包样式
export const buildStyle = () => {
return src(`${componentPath}/component/**/*.less`)
.pipe(less())
.pipe(autoprefixer())
.pipe(dest(join(componentPath, 'hope', 'style')))
};
运行结果会在打包目录下style
文件下生成每个组件的样式文件,后续还可以将button.css
改成index.css
方便导出
实现自动导入
在项目中会用到一个插件unplugin-vue-components可以实现组件库的自动导入,例如自动导入antd,推荐在生产环境下使用,如果在开发环境下使用的话项目加载比较慢
// antd的自动导入
import Components from 'unplugin-vue-components/vite'
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
{
...Components({
dirs: ['components/**'],
extensions: ['vue'],
deep: true,
dts: 'types/components.d.ts',
directoryAsNamespace: false,
globalNamespaces: [],
directives: true,
importPathTransform: (v) => v,
allowOverrides: false,
include: [/\.vue$/, /\.vue\?vue/],
exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],
resolvers: [AntDesignVueResolver()]
}),
// 生成环境下使用,开发环境可以全量导入
apply: 'build'
},
]
})
其中比较关键的是实现一个自己的解析器,类似于AntDesignVueResolver
import type { ComponentResolver, SideEffectsInfo } from "unplugin-vue-components/types"
const getSideEffects = (compName: string): SideEffectsInfo => {
const packageName = '@XY/components'
const styleDir = compName.slice(0,1).toLocaleLowerCase() + compName.slice(1)
const styleName = compName.replace('My', '').toLocaleLowerCase() + '.css'
return `${packageName}/hope/style/${styleDir}/${styleName}`
}
export default function MyComponentResolve(): ComponentResolver{
return {
type: 'component',
resolve: (name: string) => {
if (name.startsWith('My')) {
const importName = name
const path = `@XY/components/hope/es`
return {
name: importName,
from: path,
sideEffects: getSideEffects(importName)
}
}
}
}
}
这是我针对自定义组件库写的一个解析器,也可以参考其他的
针对几个关键点说一下, resolve
方法接受的name
属性是组件的名字,例如我的组件名基本上是Myxxx
,之后就需要判断这个组件是不是我们需要处理的组件,判断完之后就返回一个对象
name: 组件名
from: 打包的路径
sideEffects: 对样式进行处理,返回对于的样式路径
这样就实现了自动导入
PS
- 给组件一个名字
我的按钮
// vite.config.ts
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
export default defineConfig({
plugins: [
...
VueSetupExtend()
...
]})