用vue3+vite实现一个按需引入的组件库


用vue3+vite实现一个按需引入的组件库

demo源码

最近在重构项目中需要将一些常用的组件封装成组件库,在使用的时候需要全量导入组件及样式,这样十分的不优雅,于是就有了这个demo项目。

明确要实现什么

目标:组件库支持按需导入,最好可以自动导入

之前打包出来的产物只有一个js文件和css文件,如果需要按需导入的话,每个组件的打包产物应该是独立的,包括css样式文件

实现

拆分组件

组件的源码

![image-20230518232831884](/Users/muyeyong/Library/Application Support/typora-user-images/image-20230518232831884.png)

组件的打包使用vite实现的,去看看vite有哪些配置可以支持实现我们的需求

有两个方法可以实现: preservemodulesinput 配置

首先看下这两个有什么区别

// 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))
      ]))
   ...
 }

image.png

//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'
        }
      ]
    }

image.png

这两个方法都按照组件目录打包成js文件,但是preservemodules打包出的内容太杂且层次太深,input打包出来的结果就清爽很多,其实rollup文档中有说过这种情况推荐使用input配置

image.png

于是利用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'))) 
};

image.png

运行结果会在打包目录下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()
    ...
   ]})

文章作者: 木叶勇
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 木叶勇 !
  目录