Vue3组件库搭建4-打包

200 阅读7分钟

组件库打包

Rollup 是一个 JavaScript 模块打包器,可以将小的代码片段编译成大的、复杂的代码库或应用程序。它使用 ES2015 的模块语法,让你可以自由地和无缝地使用你的 JavaScript 模块。

16920844918602

以下是一些 Rollup 的关键特性:

  1. 代码拆分与动态导入:Rollup 支持代码拆分和动态导入,这意味着你的代码可以被拆分为多个包,并且在运行时动态加载,以提高应用的性能。

  2. 树摇(Tree-shaking):这是 Rollup 的一个核心特性,它实现了静态的模块结构,可以识别出实际未被引用或未被使用的代码,移除它们以减少最终包的大小。

  3. 输出格式:Rollup 支持多种输出格式,包括 AMD、CommonJS、SystemJS、ESM,以及 IIFEUMD 格式的自执行函数,可以用于创建 JavaScript 库。

  4. 插件系统: Rollup 提供了一个插件系统,可以通过插件来定制 Rollup 的行为。例如,你可以使用插件来转换 TypeScriptJSX,或者来处理 CSS、图片等资源。

  5. 支持 code splitting,可以将代码分成多个部分,使得只有需要的部分被加载,进一步减少文件体积。

准备工作

首先第一步就是安装 rollup,我们这里还是选择将 rollup 安装到工作空间里面:

pnpm add rollup rollup-plugin-typescript2 rollup-plugin-vue rollup-plugin-postcss rollup-plugin-delete rollup-plugin-copy @rollup/plugin-terser -D -w
  • rollup-plugin-typescript2:这个插件提供了对 ts 的支持,用于将 ts 转换为 js
  • rollup-plugin-vue:这个插件用于提供对 vue 的支持,让 rollup 能够顺利的处理 vue 文件
  • rollup-plugin-postcss:该插件用于在构建过程对 CSS 相关的处理,例如我们的项目中用到了 sass,由该插件来进行处理
  • rollup-plugin-delete:该插件用于删除文件或者目录,它通常用于在打包之前清除上一次打包的内容,从而保证打的包里面始终只包含这一次所打包的最新内容。
  • rollup-plugin-copy:这个插件用于复制文件和目录,我们在做打包工作的时候,经常会涉及到一些文件资源需要原封不动的复制到打的包里面。
  • @rollup/plugin-terser:该插件用于做 JS 的压缩工作。

打包组件

安装好依赖之后,接下来就需要书写 rollup 配置文件,下面是打包组件的配置文件代码:

import typescript from "rollup-plugin-typescript2";
import vue from "rollup-plugin-vue";
import postcss from "rollup-plugin-postcss";
import del from "rollup-plugin-delete";
import copy from "rollup-plugin-copy";
import terser from "@rollup/plugin-terser";

// 导出一个数组,该数组里面是一个一个的对象
// 每一个对象就是一个打包任务
export default [
  {
    // 打包组件的任务
    input: "packages/components/index.ts", // 打包入口
    // 打包的输出
    output: {
      dir: "dist/components",
      format: "esm",
    },
    // 外部依赖,这一部分依赖不需要进行打包
    external: ["vue"],
    // 指定要使用的插件,注意插件是有顺序
    plugins: [
      del({ targets: "dist/components" }), // 先把上一次的打包内容删除掉
      vue({
        include: ["**/*.vue"],
      }),
      typescript({
        tsconfig: "tsconfig.app.json",
      }),
      terser(),
      copy({
        targets: [
          { src: "packages/components/package.json", dest: "dist/components" },
        ],
      }),
    ],
  },
];

接下来我们来补全 packages/components/index.ts 的内容:

import Button from "./button/src/button.vue";
import Card from "./card/src/card.vue";

import type { App, Plugin } from "vue";

// 之前是每个组件有一个入口文件,每个入口文件实际上所做的事情就是给这个组件添加一个 install 方法
// 之后在其他项目中就可以单独的引入这个组件:
// import DuyiButton from '@duyiui-plus/components/button'
// import DuyiCard from '@duyiui-plus/components/card'

// 现在整体项目的入口文件,要做的事情很简单:统一给所有组件添加上 install
// 之后在其他项目中使用的时候,就可以一次性导入所有的组件

const components = [Button, Card];

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

const duyiui: Plugin = {
  install,
};

export default duyiui;

除了 index.ts 的补充,我们还需要对 packages/components 下面的 package.json 文件进行一个补充,内容如下:

{
  "name": "@duyiui-plus/components",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "types": "types.d.ts",
  "scripts": {
    "test": "vitest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "peerDependencies": {
    "vue": "^3.0.0"
  }
}
  • type:设置为 module,代表我们的代码使用的是 ESM 模块化规范
  • types: 设置的值为 types.d.ts,这个是一个类型声明的入口文件
  • peerDependencies:这个库的依赖,表示其他项目在使用我这个库的时候,需要有的依赖,这里设置的值为 vue,表示其他项目在使用这个库的时候,需要安装 vue,并且版本在 3.0.0 以上。

vue-shim.d.ts

该文件一看就是一个类型声明文件:

declare module "*.vue" {
  import { DefineComponent } from "vue";
  const component: DefineComponent<{}, {}, any>;
  export default component;
}

该文件是一个 ts 的声明文件,用于告诉 ts 如何处理 .vue 类型的文件。

最后在项目根木下面的 package.json 中做如下的修改:

"type": "module",
"scripts": {
    ...
    "build": "rollup -c"
},

打包样式

打包样式需要新书写一个打包任务,对应的代码如下:

import typescript from "rollup-plugin-typescript2";
import vue from "rollup-plugin-vue";
import postcss from "rollup-plugin-postcss";
import del from "rollup-plugin-delete";
import copy from "rollup-plugin-copy";
import terser from "@rollup/plugin-terser";

// 导出一个数组,该数组里面是一个一个的对象
// 每一个对象就是一个打包任务
export default [
  {
    // 打包组件的任务
    input: "packages/components/index.ts", // 打包入口
    // 打包的输出
    output: {
      dir: "dist/components",
      format: "esm",
    },
    // 外部依赖,这一部分依赖不需要进行打包
    external: ["vue"],
    // 指定要使用的插件,注意插件是有顺序
    plugins: [
      del({ targets: "dist/components" }), // 先把上一次的打包内容删除掉
      vue({
        include: ["**/*.vue"],
      }),
      typescript({
        tsconfig: "tsconfig.app.json",
      }),
      terser(),
      copy({
        targets: [
          { src: "packages/components/package.json", dest: "dist/components" },
        ],
      }),
    ],
  },
  {
    // 打包样式的任务
    input: "packages/theme-chalk/src/index.scss",
    output: {
      file: "dist/theme-chalk/index.css",
      format: "esm",
    },
    plugins: [
      del({
        targets: "dist/theme-chalk",
      }),
      postcss({
        extract: true, // 单独生成一个 css 文件
        minimize: true, // 压缩
      }),
      copy({
        targets: [
          {
            src: "packages/theme-chalk/package.json",
            dest: "dist/theme-chalk",
          },
          {
            src: "packages/theme-chalk/src/fonts/*",
            dest: "dist/theme-chalk/fonts",
          },
        ],
      }),
    ],
  },
];

最后执行 pnpm build,就会将组件以及样式分别打包到 dist/components 以及 dist/theme-chalk 目录下面。

生成类型声明文件

首先第一步,在项目根目录下面的 tsconfig.app.json 文件中,添加 declaration 为 true

"compilerOptions": {
    "composite": true,
    "baseUrl": ".",
    "declaration": true, // add this line
    "paths": {
      "@/*": ["./src/*"]
    }
  }

之后重新打包的时候,我们就会发现在 components 下面就生成了类型说明文件。

接下来一步一步进行优化,首先 test 目录下面的文件是不需要生成类型声明文件的,针对这个情况,我们只需要修改 exclude 配置项即可:

"exclude": ["packages/components/**/test/**"],

目前为止,我们针对各个文件已经生成了类型说明文件,但是我们需要生成一个类型声明文件的入口文件,这个入口文件和 index.js 是同级别的,这个就需要我们手动的来书写代码生成。

在项目根目录下面创建 generate-types.cjs,对应的代码内容如下:

// 该文件的目的只有一个:生成类型声明文件的入口文件

const fs = require("fs");
const path = require("path");

// 定义组件和类型文件的目录路径
const componentsDir = path.resolve(
  __dirname,
  "dist",
  "components",
  "packages",
  "components"
);

// 类型声明入口文件的位置
const typesFile = path.resolve(__dirname, "dist", "components", "types.d.ts");

// 获取组件目录下面所有的子目录
const components = fs.readdirSync(componentsDir);

// 定义最终写入到 types.d.ts 里面的内容
let typesContent = 'import { Plugin } from "vue";\n\n';
typesContent += "declare const duyiui: Plugin; \n";
typesContent += "export default duyiui; \n\n";

// 为每个组件的类型声明文件生成一个 export 语句
// 并且添加到 typesContent 里面

typesContent += components
  .map((component) => `export * from './packages/components/${component}'`)
  .join("\n");

// 至此,我们要写入到 types.d.ts 的内容已经准备好了
fs.writeFileSync(typesFile, typesContent);

console.log("类型声明文件的入口文件已经生成完毕...");

之后在项目根目录下的 package.json 中,修改脚本命令:

"scripts": {
    ...
    "build": "rollup -c && node generate-types.cjs"
},

并且你最终要确认 packages/components 这个包的 package.json 中的 types 是正确设置了的。