如何利用 `Vite` 的 `lib` 模式,将 Vue 组件库打包为多种格式,并支持按需引入?

各位朋友,老铁们,大家好!今天咱来聊聊如何用 Vite 的 lib 模式,把你的 Vue 组件库打造成十八般武艺样样精通的绝世高手,既能整体梭哈,也能按需索取,让你的用户用起来倍儿爽!

开场白:为什么选 Vite?

在打包组件库的江湖里,Webpack 绝对是老前辈,经验丰富,但有时候也显得有点笨重。而 Vite,作为后起之秀,就像一位身手敏捷的剑客,轻量级,启动快,热更新速度惊人,打包速度也是杠杠的。 特别是对于组件库这种需要频繁迭代的项目来说,Vite 的速度优势简直是救命稻草。

正题:Vite 的 lib 模式

Vite 的 lib 模式,顾名思义,就是专门用来构建库(library)的。它能帮你把你的 Vue 组件库打包成各种格式,比如:

  • ES Module (ESM): 现代浏览器和 Node.js 都支持,是按需引入的最佳选择。
  • CommonJS (CJS): 传统的 Node.js 模块格式,为了兼容老项目还是有必要的。
  • UMD (Universal Module Definition): 通用模块定义,可以在浏览器和 Node.js 环境中使用,但通常体积较大,不推荐。
  • IIFE (Immediately Invoked Function Expression): 立即执行函数表达式,直接生成一个全局变量,简单粗暴,但容易污染全局命名空间,不推荐。

目标:

  • 生成 ESM 格式的包,用于按需引入。
  • 生成 UMD 或 CJS 格式的包,用于传统项目或 CDN 引入。
  • 提供 TypeScript 类型定义文件 (.d.ts),方便用户使用。
  • 支持 CSS 样式的打包和按需引入。

实战:撸起袖子开始干

1. 初始化项目

首先,我们需要创建一个新的 Vue 项目(如果你已经有组件库项目,可以跳过这一步)。

npm create vite my-vue-component-lib --template vue-ts  # 如果你用 TypeScript
# 或者
npm create vite my-vue-component-lib --template vue       # 如果你用 JavaScript

进入项目目录:

cd my-vue-component-lib

2. 安装必要的依赖

除了 Vite 之外,我们还需要一些额外的依赖来支持组件库的构建:

npm install vue sass -D  #sass是为了css支持,vue是因为需要把vue设置成外部依赖

3. 目录结构

一个典型的组件库项目目录结构可能如下所示:

my-vue-component-lib/
├── src/
│   ├── components/      # 组件目录
│   │   ├── MyButton.vue
│   │   └── MyInput.vue
│   ├── index.ts         # 组件库入口文件
│   └── style/            # 样式目录
│       ├── index.scss     # 全局样式
│       ├── MyButton.scss  # 组件样式
│       └── MyInput.scss   # 组件样式
├── vite.config.ts       # Vite 配置文件
├── package.json
└── tsconfig.json        # TypeScript 配置文件 (如果使用 TypeScript)

4. 编写组件

src/components 目录下创建你的 Vue 组件。例如,MyButton.vue

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

<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';

const props = defineProps({
  label: {
    type: String,
    required: true,
  },
});

const emit = defineEmits(['click']);

const handleClick = () => {
  emit('click');
};
</script>

<style lang="scss" scoped>
.my-button {
  padding: 10px 20px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;

  &:hover {
    background-color: #3e8e41;
  }
}
</style>

再创建一个 MyInput.vue

<template>
  <input type="text" class="my-input" :value="modelValue" @input="handleInput" />
</template>

<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';

const props = defineProps({
  modelValue: {
    type: String,
    default: '',
  },
});

const emit = defineEmits(['update:modelValue']);

const handleInput = (event: Event) => {
  const target = event.target as HTMLInputElement;
  emit('update:modelValue', target.value);
};
</script>

<style lang="scss" scoped>
.my-input {
  padding: 8px;
  border: 1px solid #ccc;
  border-radius: 4px;
}
</style>

5. 组件库入口文件 (src/index.ts)

这是组件库的入口文件,用于导出所有的组件。

import MyButton from './components/MyButton.vue';
import MyInput from './components/MyInput.vue';

export {
  MyButton,
  MyInput,
};

// 定义一个 install 方法,方便全局注册组件 (可选)
import { App } from 'vue';

const components = [MyButton, MyInput]

const install = (app:App) => {
  components.forEach(component => {
    app.component(component.name, component)
  })
}

export default {
  install
}

6. 配置 Vite (vite.config.ts)

这是最关键的一步,我们需要配置 Vite 的 lib 模式。

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

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  build: {
    lib: {
      // 入口文件
      entry: resolve(__dirname, 'src/index.ts'),
      // 组件库名称 (导出的全局变量名称)
      name: 'MyVueComponentLib',
      // 文件名格式 (umd/es/iife)
      fileName: (format) => `my-vue-component-lib.${format}.js`,
    },
    rollupOptions: {
      // 确保外部化处理那些你不想打包进库的依赖
      external: ['vue'],
      output: {
        // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
        globals: {
          vue: 'Vue',
        },
      },
    },
    // 输出 source map 方便调试
    sourcemap: true,
  },
});

配置项解释:

  • entry: 组件库的入口文件,这里指向 src/index.ts
  • name: 组件库的名称,用于生成 UMD 格式的包时,作为全局变量的名称。
  • fileName: 输出文件的名称格式。
  • external: 指定哪些依赖不需要打包进组件库。这里我们将 vue 设置为外部依赖,因为用户在使用你的组件库时,肯定已经安装了 vue
  • globals: 为外部依赖提供全局变量名称。在 UMD 模式下,需要指定外部依赖的全局变量名称。
  • sourcemap: 生成 sourcemap 文件,方便调试。

7. 配置 package.json

我们需要在 package.json 中添加一些配置,以便更好地发布和使用组件库。

{
  "name": "my-vue-component-lib",
  "version": "0.0.1",
  "description": "A simple Vue component library",
  "main": "./dist/my-vue-component-lib.umd.js",  // CommonJS 入口
  "module": "./dist/my-vue-component-lib.es.js",   // ES Module 入口
  "types": "./dist/index.d.ts",                     // TypeScript 类型定义文件
  "files": [
    "dist"
  ],
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc --noEmit && vite build",
    "preview": "vite preview"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^4.2.3",
    "sass": "^1.64.1",
    "typescript": "^5.0.2",
    "vite": "^4.4.5",
    "vue-tsc": "^1.8.5"
  },
  "peerDependencies": {
    "vue": "^3.0.0"
  }
}

配置项解释:

  • main: 指定 CommonJS 模块的入口文件。
  • module: 指定 ES Module 模块的入口文件。
  • types: 指定 TypeScript 类型定义文件的入口文件。
  • files: 指定需要发布到 npm 上的文件或目录。
  • peerDependencies: 指定组件库的 peer dependencies。 peer dependencies 是指你的组件库依赖的,但是不由你的组件库安装的依赖。 用户必须手动安装这些依赖。 这里我们将 vue 设置为 peer dependency,表明你的组件库依赖 vue,但是用户需要自己安装 vue

8. 构建组件库

现在,我们可以运行 npm run build 命令来构建组件库了。

npm run build

构建完成后,你会在 dist 目录下看到生成的各种格式的包:

dist/
├── my-vue-component-lib.es.js    # ES Module
├── my-vue-component-lib.umd.js   # UMD
├── index.d.ts                    # TypeScript 类型定义文件
└── style.css                     # 样式文件 (如果你的组件有样式)

9. 发布组件库 (可选)

如果你想将你的组件库发布到 npm 上,可以执行以下步骤:

  1. 登录 npm: npm login
  2. 发布组件库: npm publish

注意: 在发布之前,请确保你的 package.json 中的 name 字段是唯一的,并且没有被其他人占用。

按需引入的实现

Vite 默认就支持 ES Module 的按需引入,所以我们只需要确保我们的组件库输出了 ES Module 格式的包即可。

使用方法:

import { MyButton, MyInput } from 'my-vue-component-lib';

// 或者,如果你想更精细地按需引入:
import MyButton from 'my-vue-component-lib/dist/components/MyButton.vue';
import MyInput from 'my-vue-component-lib/dist/components/MyInput.vue';

// 在 Vue 组件中使用
export default {
  components: {
    MyButton,
    MyInput,
  },
  template: `
    <MyButton label="Click me" @click="handleClick" />
    <MyInput v-model="inputValue" />
  `,
  data() {
    return {
      inputValue: '',
    };
  },
  methods: {
    handleClick() {
      alert('Button clicked!');
    },
  },
};

CSS 样式的按需引入

如果你的组件有独立的 CSS 样式文件,你需要手动引入它们。

import 'my-vue-component-lib/dist/style.css'; // 引入全部样式
// 或者
import 'my-vue-component-lib/dist/components/MyButton.css'; // 引入单个组件的样式
import 'my-vue-component-lib/dist/components/MyInput.css';  // 引入单个组件的样式

为了方便用户使用,你可以在你的组件库中提供一个专门用于引入样式的入口文件,比如 src/style/index.ts

// src/style/index.ts
import '../components/MyButton.scss';
import '../components/MyInput.scss';

然后在 vite.config.ts 中,将这个文件也打包成一个单独的 CSS 文件:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { resolve } from 'path';
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(),
    cssInjectedByJsPlugin()
  ],
  build: {
    lib: {
      // 入口文件
      entry: {
        index: resolve(__dirname, 'src/index.ts'),
        style: resolve(__dirname, 'src/style/index.ts'), // 新增样式入口
      },
      // 组件库名称 (导出的全局变量名称)
      name: 'MyVueComponentLib',
      // 文件名格式 (umd/es/iife)
      fileName: (format, entryName) => {
        return entryName === 'style' ? `style.${format}.js` : `my-vue-component-lib.${format}.js`
      },
    },
    rollupOptions: {
      // 确保外部化处理那些你不想打包进库的依赖
      external: ['vue'],
      output: {
        // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
        globals: {
          vue: 'Vue',
        },
      },
    },
    // 输出 source map 方便调试
    sourcemap: true,
  },
});

现在,你可以通过以下方式引入样式:

import 'my-vue-component-lib/dist/style.es.js'; // 引入全部样式

cssInjectedByJsPlugin 这个插件让css以js的形式插入页面,避免打包成一个css文件。

TypeScript 类型定义

Vite 会自动生成 TypeScript 类型定义文件 (.d.ts),所以你不需要手动编写。只需要确保你的代码是符合 TypeScript 规范的即可。

高级技巧:代码分割

如果你的组件库非常庞大,你可以考虑使用代码分割来进一步优化按需引入的性能。代码分割可以将你的组件库拆分成更小的 chunk,只有在需要的时候才会加载。

Vite 默认就支持代码分割,你只需要按照一定的规则来组织你的代码即可。例如,你可以将每个组件都放在一个单独的目录下,并使用 ES Module 的 import() 语法来动态加载组件。

总结:打造你的 Vue 组件库

通过 Vite 的 lib 模式,我们可以轻松地将 Vue 组件库打包成各种格式,并支持按需引入。 总结一下步骤:

步骤 描述 关键配置
1 初始化项目 使用 npm create vite 创建 Vue 项目。
2 安装依赖 安装 vuesass 等必要的依赖。
3 创建组件 src/components 目录下创建你的 Vue 组件。
4 创建入口文件 创建 src/index.ts 文件,导出所有的组件。
5 配置 Vite (vite.config.ts) 配置 build.lib 选项,包括 entrynamefileNamerollupOptions.external
6 配置 package.json 配置 mainmoduletypespeerDependencies 字段。
7 构建组件库 运行 npm run build 命令构建组件库。
8 发布组件库 (可选) 登录 npm 并发布组件库。
9 按需引入 用户可以通过 ES Module 的 import 语法按需引入组件。

希望今天的分享对你有所帮助。 祝你早日打造出自己的 Vue 组件库,成为前端界的武林盟主! 咱们下期再见!

发表回复

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