Vue3组件库打包指南,一次生成esm、esm-bundle、commonjs、umd四种格式

读完本篇,你可以了解到如何将组件库打包成各种格式

上一篇里提到了启动服务前会先进行一下组件库的打包,运行的命令为:

varlet-cli compile

显然是varlet-cli提供的一个命令:

处理函数为compile,接下来我们详细看一下这个函数都做了什么。

// varlet-cli/src/commands/compile.ts
export async function compile(cmd: {
    noUmd: boolean }) {
   
    process.env.NODE_ENV = 'compile'
    await removeDir()
    // ...
}

// varlet-cli/src/commands/compile.ts
export function removeDir() {
   
    // ES_DIR:varlet-ui/es
    // LIB_DIR:varlet-ui/lib
    // HL_DIR:varlet-ui/highlight
    // UMD_DIR:varlet-ui/umd
    return Promise.all([remove(ES_DIR), remove(LIB_DIR), remove(HL_DIR), remove(UMD_DIR)])
}

首先设置了一下当前的环境变量,然后清空相关的输出目录。

// varlet-cli/src/commands/compile.ts
export async function compile(cmd: {
    noUmd: boolean }) {
   
    // ...
    process.env.TARGET_MODULE = 'module'
    await runTask('module', compileModule)

    process.env.TARGET_MODULE = 'esm-bundle'
    await runTask('esm bundle', () => compileModule('esm-bundle'))

    process.env.TARGET_MODULE = 'commonjs'
    await runTask('commonjs', () => compileModule('commonjs'))

    process.env.TARGET_MODULE = 'umd'
    !cmd.noUmd && (await runTask('umd', () => compileModule('umd')))
}

接下来依次打包了四种类型的产物,方法都是同一个compileModule,这个方法后面会详细分析。

组件的基本组成

Button组件为例看一下未打包前的组件结构:

一个典型组件的构成主要是四个文件:

.less:样式

.vue:组件

index.ts:导出组件,提供组件注册方法

props.ts:组件的props定义

样式部分Varlet使用的是less语言,样式比较少的话会直接内联写到Vue单文件的style块中,否则会单独创建一个样式文件,比如图中的button.less,每个组件除了引入自己本身的样式外,还会引入一些基本样式、其他组件的样式:

index.ts文件用来导出组件,提供组件的注册方法:

props.ts文件用来声明组件的props类型:

有的组件没有使用.vue,而是.tsx,也有些组件会存在其他文件,比如有些组件就还存在一个provide.ts文件,用于向子孙组件注入数据。

打包的整体流程

首先大致过一遍整体的打包流程,主要函数为compileModule

// varlet-cli/src/compiler/compileModule.ts
export async function compileModule(modules: 'umd' | 'commonjs' | 'esm-bundle' | boolean = false) {
   
  if (modules === 'umd') {
   
    // 打包umd格式
    await compileUMD()
    return
  }

  if (modules === 'esm-bundle') {
   
    // 打包esm-bundle格式
    await compileESMBundle()
    return
  }
    
  // 打包commonjs和module格式
  // 打包前设置一下环境变量
  process.env.BABEL_MODULE = modules === 'commonjs' ? 'commonjs' : 'module'
  // 输出目录
  // ES_DIR:varlet-ui/es
  // LIB_DIR:varlet-ui/lib
  const dest = modules === 'commonjs' ? LIB_DIR : ES_DIR
  // SRC_DIR:varlet-ui/src,直接将组件的源码目录复制到输出目录
  await copy(SRC_DIR, dest)
  // 读取输出目录
  const moduleDir: string[] = await readdir(dest)
  // 遍历打包每个组件
  await Promise.all(
    // 遍历每个组件目录
    moduleDir.map((filename: string) => {
   
      const file: string = resolve(dest, filename)
      if (isDir(file)) {
   
        // 在每个组件目录下新建两个样式入口文件
        ensureFileSync(resolve(file, './style/index.js'))
        ensureFileSync(resolve(file, './style/less.js'))
      }
      // 打包组件
      return isDir(file) ? compileDir(file) : null
    })
  )
  // 遍历varlet-ui/src/目录,找出所有存在['index.vue', 'index.tsx', 'index.ts', 'index.jsx', 'index.js']这些文件之一的目录
  const publicDirs = await getPublicDirs()
  // 生成整体的入口文件
  await (modules === 'commonjs' ? compileCommonJSEntry(dest, publicDirs) : compileESEntry(dest, publicDirs))
}

umdesm-bundle两种格式都会把所有内容都打包到一个文件,用的是Vite提供的方法进行打包。

commonjsmodule是单独打包每个组件,不会把所有组件的内容都打包到一起,Vite没有提供这个能力,所以需要自行处理,具体操作为:

  • 先把组件源码目录varlet/src/下的所有组件文件都复制到对应的输出目录下;
  • 然后在输出目录遍历每个组件目录:
    • 创建两个样式的导出文件;
    • 删除不需要的目录、文件(测试、示例、文档);
    • 分别编译Vue单文件、ts文件、less文件;
  • 全部打包完成后,遍历所有组件,动态生成整体的导出文件;

compileESEntry方法为例看一下整体导出文件的生成:

// varlet-cli/src/compiler/compileScript.ts
export async function compileESEntry(dir: string, publicDirs: string[]) {
   
  const imports: string[] = []
  const plugins: string[] = []
  const constInternalComponents: string[] = []
  const cssImports: string[] = []
  const lessImports: string[] = []
  const publicComponents: string[] = []
  // 遍历组件目录名称
  publicDirs.forEach((dirname: string) => {
   
    // 连字符转驼峰式
    const publicComponent = bigCamelize(dirname)
	// 收集组件名称
    publicComponents.push(publicComponent)
    // 收集组件导入语句
    imports.push(`import ${
     publicComponent}, * as ${
     publicComponent}Module from './${
     dirname}'`)
    // 收集内部组件导入语句
    constInternalComponents.push(
      `export const _${
     publicComponent}Component = ${
     publicComponent}Module._${
     publicComponent}Component || {}`
    )
    // 收集插件注册语句
    plugins.push(`${
     publicComponent}.install && app.use(${
     publicComponent})`)
    // 收集样式导入语句
    cssImports.push(`import './${
     dirname}/style'`)
    lessImports.push(`import './${
     dirname}/style/less'`)
  })

  // 拼接组件注册方法
  const install = `
function install(app) {
  ${
     plugins.join('\n  ')}
}
`

  // 拼接导出入口index.js文件的内容,注意它是不包含样式的
  const indexTemplate = 
### 解决 UniApp 中 Vue-i18n ESM-Bundler 构建的 Tree-Shaking 问题 在 UniApp 环境中使用 `vue-i18n` 的 `esm-bundler` 构建时,为了实现正确的 Tree-Shaking 并消除控制台警告信息,需要对打包工具进行特定配置。以下是详细的解决方案。 --- #### 修改 Webpack 配置以替换特性标志全局变量 UniApp 提供了自定义 Webpack 配置的能力,可以通过 `chainWebpack` 方法来修改默认配置。以下是一个完整的配置示例[^4]: ```javascript // uni-app根目录下的vue.config.js文件 module.exports = { chainWebpack: (config) => { config.plugin('define').tap((args) => { args[0]['__VUE_I18N_FULL_INSTALL__'] = JSON.stringify(true); // 启用完整安装模式 args[0]['__VUE_I18N_LEGACY_API__'] = JSON.stringify(false); // 不启用旧版 API 支持 args[0]['__INTLIFY_PROD_DEVTOOLS__'] = JSON.stringify(false); // 关闭生产环境下的调试工具 return args; }); }, }; ``` 上述代码通过 `DefinePlugin` 插件将 `vue-i18n` 的特性标志全局变量替换为布尔字面量,从而确保 Tree-Shaking 能够正常工作。 --- #### 替代方案:强制引入 CJS 版本 如果希望绕过 `esm-bundler` 构建带来的复杂性,可以直接让 UniApp 使用 `vue-i18n` 的 CommonJS 版本。这种方法虽然简单,但可能会牺牲一些现代化构建的优势。以下是具体操作步骤[^5]: ```javascript // uni-app根目录下的vue.config.js文件 module.exports = { chainWebpack: (config) => { config.resolve.alias.set('vue-i18n', 'vue-i18n/dist/vue-i18n.cjs.js'); }, }; ``` 此方法通过别名机制强制指向 `vue-i18n` 的 CommonJS 版本,避免了与 ESM 构建相关的潜在问题。 --- #### 注意事项 无论选择哪种方式,都需要确保以下几点: 1. **开发环境与生产环境的一致性**:在开发环境中测试过的配置应同样适用于生产环境。 2. **Tree-Shaking 的验证**:可以通过查看最终打包结果(如使用 `webpack-bundle-analyzer` 工具)确认未使用的代码已被成功移除[^1][^2]。 3. **版本匹配**:确保 `vue-i18n` 和其依赖项的版本一致,以免因不兼容引发额外问题。 --- ### 总结 针对 UniApp 中 `vue-i18n` 的 `esm-bundler` 构建问题,可以通过两种主要途径解决:一是通过 Webpack 配置显式替换特性标志全局变量;二是直接切换至 CommonJS 版本。前者更符合现代前端工程的最佳实践,而后者则适合快速解决问题的场景。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值