如何利用 `Vite` 的 `lib` 模式,将 Vue 组件库打包为多种格式(ESM, UMD, CommonJS)?

各位观众老爷,晚上好! 今天咱们来聊聊如何用 Vite 的 lib 模式,把你的 Vue 组件库打造成“变形金刚”,想变啥样就变啥样,ESM、UMD、CommonJS,统统不在话下!

开场白:组件库的“格式化”需求

话说咱们写 Vue 组件,写得那叫一个行云流水,但写完之后,总得想着怎么把它分享给别人用吧? 这就涉及到组件库的打包和发布问题。 不同的项目,使用的模块化规范可能不一样,有的用 ESM,有的用 CommonJS,还有的用 UMD。 为了让你的组件库能适应各种环境,最好能打包成多种格式。

Vite 的 lib 模式,就是专门用来干这个的! 它能帮你把你的组件库打包成各种你想要的格式,简直是组件库开发者的福音。

第一幕:Vite lib 模式初体验

首先,咱们得有个 Vue 组件库的雏形。 假设我们有个非常简单的组件,就叫 MyButton.vue,内容如下:

<template>
  <button @click="handleClick">{{ label }}</button>
</template>

<script>
export default {
  name: 'MyButton',
  props: {
    label: {
      type: String,
      default: 'Click Me!'
    }
  },
  methods: {
    handleClick() {
      alert('Button clicked!');
    }
  }
};
</script>

接下来,咱们创建一个 vite.config.js 文件,这是 Vite 的配置文件,也是咱们施展魔法的地方。

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  build: {
    lib: {
      entry: 'src/MyButton.vue', // 组件库的入口文件
      name: 'MyButton', // 打包后的全局变量名 (UMD 模式下)
      fileName: (format) => `my-button.${format}.js` // 输出文件名
    },
    rollupOptions: {
      // 确保外部化处理那些你不想打包进库的依赖
      external: ['vue'],
      output: {
        // 在 UMD 构建模式下,需要配置全局变量来使用外部依赖
        globals: {
          vue: 'Vue'
        }
      }
    }
  }
});

解释一下几个关键配置:

  • lib.entry: 指定组件库的入口文件,这里是 src/MyButton.vue
  • lib.name: 指定打包后的全局变量名,在 UMD 模式下,会把组件库挂载到这个全局变量上,这里是 MyButton
  • lib.fileName: 指定输出文件名,可以是一个函数,根据 format 参数(ESM, UMD, CommonJS 等)动态生成文件名。
  • rollupOptions.external: 指定需要外部化的依赖,也就是说,这些依赖不会被打包进组件库,而是由用户在项目里提供。 像 Vue 这种基础依赖,一般都应该外部化。
  • rollupOptions.output.globals: 在 UMD 模式下,需要配置全局变量来使用外部依赖。 比如,我们把 vue 映射到全局变量 Vue 上,这样 UMD 版本的组件库就可以直接使用 Vue 了。

配置好了之后,就可以运行 vite build 命令来打包组件库了。 打包完成后,会在 dist 目录下生成一个 my-button.es.js 文件 (ESM 格式) 和一个 my-button.umd.js 文件(UMD格式)。

第二幕:多格式齐发,配置升级

上面的例子只打包了 ESM 和 UMD 两种格式。 如果我们还想要 CommonJS 格式,怎么办呢? 别慌,Vite 早就为我们准备好了!

只需要修改 vite.config.js 文件,把 lib.formats 选项加上:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  build: {
    lib: {
      entry: 'src/MyButton.vue',
      name: 'MyButton',
      formats: ['es', 'umd', 'cjs'], // 指定要打包的格式
      fileName: (format) => `my-button.${format}.js`
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: {
          vue: 'Vue'
        }
      }
    }
  }
});

lib 选项中添加 formats: ['es', 'umd', 'cjs'] 就可以了。 这样,Vite 就会打包出 ESM、UMD 和 CommonJS 三种格式的文件。

运行 vite build 命令,你会在 dist 目录下看到 my-button.es.jsmy-button.umd.jsmy-button.cjs.js 三个文件。

第三幕:高级定制,灵活应变

Vite 的 lib 模式还提供了很多高级定制选项,可以让你更灵活地控制打包过程。

  • lib.entry 可以是多个入口文件

    如果你的组件库由多个组件组成,可以把 lib.entry 设置为一个对象,指定多个入口文件。

    build: {
      lib: {
        entry: {
          MyButton: 'src/MyButton.vue',
          MyInput: 'src/MyInput.vue'
        },
        name: 'MyComponentLib', // 打包后的全局变量名
        formats: ['es', 'umd'],
        fileName: (format, name) => `${name}.${format}.js` // 根据入口文件名生成输出文件名
      },
      rollupOptions: {
        external: ['vue'],
        output: {
          globals: {
            vue: 'Vue'
          }
        }
      }
    }

    这样,Vite 就会分别打包 MyButton.vueMyInput.vue,生成 MyButton.es.jsMyButton.umd.jsMyInput.es.jsMyInput.umd.js 四个文件。 注意 fileName 的写法,name 参数会被传入文件名.

  • build.outDir 指定输出目录

    默认情况下,Vite 会把打包后的文件输出到 dist 目录下。 如果你想修改输出目录,可以使用 build.outDir 选项。

    build: {
      outDir: 'lib', // 指定输出目录为 lib
      lib: {
        entry: 'src/MyButton.vue',
        name: 'MyButton',
        formats: ['es', 'umd', 'cjs'],
        fileName: (format) => `my-button.${format}.js`
      },
      rollupOptions: {
        external: ['vue'],
        output: {
          globals: {
            vue: 'Vue'
          }
        }
      }
    }

    这样,打包后的文件就会输出到 lib 目录下。

  • build.minify 控制是否压缩代码

    默认情况下,Vite 会对打包后的代码进行压缩。 如果你想关闭代码压缩,可以设置 build.minifyfalse

    build: {
      minify: false, // 关闭代码压缩
      lib: {
        entry: 'src/MyButton.vue',
        name: 'MyButton',
        formats: ['es', 'umd', 'cjs'],
        fileName: (format) => `my-button.${format}.js`
      },
      rollupOptions: {
        external: ['vue'],
        output: {
          globals: {
            vue: 'Vue'
          }
        }
      }
    }

    关闭代码压缩可以提高打包速度,方便调试。

  • build.rollupOptions.output.exports 控制 CommonJS 导出方式

    CommonJS 格式的导出方式有两种:nameddefault。 默认情况下,Vite 使用 named 导出,也就是把组件库的每个组件都作为具名导出。 如果你想使用 default 导出,可以设置 build.rollupOptions.output.exportsdefault

    build: {
      lib: {
        entry: 'src/MyButton.vue',
        name: 'MyButton',
        formats: ['es', 'umd', 'cjs'],
        fileName: (format) => `my-button.${format}.js`
      },
      rollupOptions: {
        external: ['vue'],
        output: {
          exports: 'default', // 使用 default 导出
          globals: {
            vue: 'Vue'
          }
        }
      }
    }

    使用 default 导出后,在 CommonJS 环境下,需要使用 require('my-button.cjs.js').default 来引入组件。

第四幕:进阶技巧,玩转组件库

除了上面的基本配置,还有一些进阶技巧可以帮助你更好地构建组件库。

  • 使用 vite-plugin-dts 生成 TypeScript 类型声明文件

    如果你的组件库是用 TypeScript 写的,可以使用 vite-plugin-dts 插件来生成类型声明文件 (.d.ts)。 这样,其他开发者在使用你的组件库时,就可以获得更好的类型提示。

    首先,安装 vite-plugin-dts 插件:

    npm install vite-plugin-dts --save-dev

    然后,在 vite.config.js 文件中配置插件:

    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import dts from 'vite-plugin-dts';
    
    export default defineConfig({
      plugins: [vue(), dts()],
      build: {
        lib: {
          entry: 'src/MyButton.vue',
          name: 'MyButton',
          formats: ['es', 'umd', 'cjs'],
          fileName: (format) => `my-button.${format}.js`
        },
        rollupOptions: {
          external: ['vue'],
          output: {
            globals: {
              vue: 'Vue'
            }
          }
        }
      }
    });

    运行 vite build 命令后,会在 dist 目录下生成 my-button.d.ts 文件。

  • 使用 vue-demi 兼容 Vue 2 和 Vue 3

    如果你的组件库需要同时兼容 Vue 2 和 Vue 3,可以使用 vue-demi 库。 vue-demi 可以让你在同一份代码中编写 Vue 2 和 Vue 3 的兼容代码。

    首先,安装 vue-demi 库:

    npm install vue-demi --save-dev

    然后,在你的组件代码中使用 vue-demi 提供的 API。 例如,可以使用 defineComponent 替代 Vue.extend,使用 computed 替代 Vue.computed 等。

    <template>
      <button @click="handleClick">{{ label }}</button>
    </template>
    
    <script>
    import { defineComponent } from 'vue-demi';
    
    export default defineComponent({
      name: 'MyButton',
      props: {
        label: {
          type: String,
          default: 'Click Me!'
        }
      },
      methods: {
        handleClick() {
          alert('Button clicked!');
        }
      }
    });
    </script>

    最后,在 vite.config.js 文件中配置 vue-demi

    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    import { resolve } from 'path';
    
    export default defineConfig({
      plugins: [vue()],
      resolve: {
        alias: {
          'vue': 'vue-demi'
        }
      },
      build: {
        lib: {
          entry: 'src/MyButton.vue',
          name: 'MyButton',
          formats: ['es', 'umd', 'cjs'],
          fileName: (format) => `my-button.${format}.js`
        },
        rollupOptions: {
          external: ['vue'],
          output: {
            globals: {
              vue: 'Vue'
            }
          }
        }
      }
    });

    这样,你的组件库就可以同时在 Vue 2 和 Vue 3 项目中使用了。

第五幕:实战演练,组件库打包流程

为了让大家更好地掌握 Vite lib 模式的使用,咱们来模拟一个完整的组件库打包流程。

  1. 创建组件库项目

    mkdir my-component-lib
    cd my-component-lib
    npm init -y
    npm install vue @vitejs/plugin-vue vite -D
  2. 创建组件

    创建 src/MyButton.vue 文件,内容如下:

    <template>
      <button @click="handleClick">{{ label }}</button>
    </template>
    
    <script>
    export default {
      name: 'MyButton',
      props: {
        label: {
          type: String,
          default: 'Click Me!'
        }
      },
      methods: {
        handleClick() {
          alert('Button clicked!');
        }
      }
    };
    </script>
  3. 创建 vite.config.js 文件

    创建 vite.config.js 文件,内容如下:

    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    
    export default defineConfig({
      plugins: [vue()],
      build: {
        lib: {
          entry: 'src/MyButton.vue',
          name: 'MyButton',
          formats: ['es', 'umd', 'cjs'],
          fileName: (format) => `my-button.${format}.js`
        },
        rollupOptions: {
          external: ['vue'],
          output: {
            globals: {
              vue: 'Vue'
            }
          }
        }
      }
    });
  4. 修改 package.json 文件

    package.json 文件中添加 build 命令:

    {
      "name": "my-component-lib",
      "version": "1.0.0",
      "description": "",
      "main": "dist/my-button.umd.js", // 指定 CommonJS 格式的入口文件
      "module": "dist/my-button.es.js",  // 指定 ESM 格式的入口文件
      "exports": { // 定义不同环境下的入口文件
        ".": {
          "import": "./dist/my-button.es.js",
          "require": "./dist/my-button.cjs.js"
        }
      },
      "scripts": {
        "build": "vite build"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "@vitejs/plugin-vue": "^4.5.2",
        "vite": "^5.0.8",
        "vue": "^3.3.11"
      }
    }

    重要说明:

    • main 字段: 指定 CommonJS 格式的入口文件。 老项目可能会使用这个字段。
    • module 字段: 指定 ESM 格式的入口文件。 一些打包工具会优先使用这个字段。
    • exports 字段: 强烈推荐使用。 它允许你根据不同的环境(import/require)指定不同的入口文件,提供更精确的模块化支持。 . 表示默认入口,可以根据 importrequire 分别指定 ESM 和 CommonJS 的入口。
  5. 运行 build 命令

    npm run build

    打包完成后,会在 dist 目录下生成 my-button.es.jsmy-button.umd.jsmy-button.cjs.js 三个文件。

  6. 发布组件库

    npm publish

    发布成功后,其他开发者就可以在自己的项目中使用你的组件库了。

总结:Vite lib 模式的优势

Vite 的 lib 模式为组件库开发者带来了诸多便利:

  • 配置简单: 只需要简单的配置,就可以打包出多种格式的组件库。
  • 速度快: 基于 Rollup 的打包引擎,速度非常快。
  • 灵活定制: 提供了很多高级定制选项,可以满足各种需求。
  • 生态完善: Vite 生态中有丰富的插件,可以帮助你更好地构建组件库。

表格总结

配置项 描述 示例
build.lib.entry 组件库的入口文件,可以是单个文件或多个文件的对象。 'src/MyButton.vue'{ MyButton: 'src/MyButton.vue', MyInput: 'src/MyInput.vue' }
build.lib.name 打包后的全局变量名 (UMD 模式下)。 'MyComponentLib'
build.lib.formats 指定要打包的格式,例如 'es', 'umd', 'cjs' ['es', 'umd', 'cjs']
build.lib.fileName 输出文件名,可以是一个函数,根据 formatname 参数动态生成文件名。 (format) => my-button.${format}.js`(format, name) => `${name}.${format}.js“
build.outDir 指定输出目录,默认为 dist 'lib'
build.minify 控制是否压缩代码,默认为 true false
build.rollupOptions.external 指定需要外部化的依赖,这些依赖不会被打包进组件库。 ['vue']
build.rollupOptions.output.globals 在 UMD 模式下,需要配置全局变量来使用外部依赖。 { vue: 'Vue' }
build.rollupOptions.output.exports 控制 CommonJS 导出方式,可以是 'named''default' 'default'

好了,今天的讲座就到这里。 希望大家能够掌握 Vite lib 模式的使用,打造出高质量、多格式的 Vue 组件库! 咱们下期再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注