手摸手带你封装Vue组件库(7)组件库打包

1,464 阅读6分钟

最近有很多小伙伴让我赶紧出打包教程,那就我们先打包,久等了👋

Rollup 打包

Rollup 是一个 JavaScript 模块打包器,可以将多个模块打包成一个或多个文件。它支持多种模块化标准,如 CommonJS、ESM 等,并且可以自动移除未使用的代码,从而减小打包文件的大小。

选择 Rollup 打包的优势:

  • Tree-shaking Rollup 可以自动移除未使用的代码,这在一定程度上减少了打包文件的大小,提高了应用的加载速度。
  • 模块化支持 Rollup 支持各种模块化代码,能够很好地处理 ESM。
  • 插件丰富 Rollup 的插件系统非常强大,开发者可以通过安装各种插件来实现不同的打包需求。
  • 配置灵活 Rollup 的配置文件可以实现非常灵活的打包规则,开发者可以根据应用需求调整配置,满足个性化需求。
  • 优化性能 Rollup 可以高效地处理模块依赖关系,减少重复代码的加载,提高应用的执行效率。

安装

npm install -D rollup @rollup/plugin-node-resolve rollup-plugin-vue -w

Vue 打包配置

我们在项目根目录下创建一个 rollup.config.js 文件,我们先打包为我们最常用的 es 模块,配置如下:

import resolve from "@rollup/plugin-node-resolve";
import vuePlugin from "rollup-plugin-vue";

export default {
  input: "./packages/components/index.js",
  output: {
    file: "dist/es.js",
    name: "TestUI",
    format: "es",
  },
  plugins: [resolve(), vuePlugin()],
  external: ["vue"], // 依赖模块
};

然后我们需要配置打包命令 "build": "rollup -c"

package.json

{
  "name": "test-ui",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "dev": "vite examples",
    "test": "echo \"Error: no test specified\" && exit 1",
    "docs:dev": "vitepress dev docs",
    "docs:build": "vitepress build docs",
    "docs:preview": "vitepress preview docs",
    "build": "rollup -c"
  }
}

我们执行一下 npm run build,结果报错了

project-20241226-1.png

这是因为我们的配置文件使用的 ES module 写法,我们需要将 package.json 中的 type 改为 module

这时候再来试一下,成功了

project-20241226-2.png

由于我们打包的是 es 模块,所有可以直接在我们的 examples 项目中引入一下我们刚才打包的文件,看看是否能够正常使用

examples/index.js

import { createApp } from "vue";
import App from "./app.vue";
// import TestUI from '@test-ui/components'
// import "@test-ui/theme-chalk/index.less"; // 引入样式文件
import TestUI from "../dist/es.js";

const app = createApp(App);
app.use(TestUI);
app.mount("#app");

project-20241226-3.png

我们可以看到是正常的,只是没有样式,接下来我们配置一下样式文件打包

配置样式文件打包

我们会用到一些插件,来安装一下

pnpm i rollup-plugin-postcss autoprefixer -D -w

使用插件配置一下

import resolve from "@rollup/plugin-node-resolve";
import vuePlugin from "rollup-plugin-vue";
import postcss from "rollup-plugin-postcss";
import autoprefixer from "autoprefixer";

export default {
  input: "./packages/components/index.js",
  output: {
    file: "dist/es.js",
    name: "TestUI",
    format: "es",
  },
  plugins: [
    resolve(),
    vuePlugin(),
    postcss({
      extract: "theme-chalk/style.css", // 将css提取到单独的文件中
      plugins: [autoprefixer()],
    }),
  ],
  external: ["vue"], // 依赖模块
};

因为我们需要使用 postcss 来抽离我们 js 中所引入的样式文件,所以我们需要在我们的组件库中引入样式文件,然后使用 extract 抽离打包到 dist/theme-chalk/style.css

packages/components/index.js

import * as components from "./components";
import "@test-ui/theme-chalk/index.less";

const FUNCTION_COMP = ["TMessage"];

export default {
  install(app) {
    Object.entries(components).forEach(([key, value]) => {
      if (!FUNCTION_COMP.includes(key)) app.component(key, value);
    });
  },
};

export const TMessage = components.TMessage;

我们打包一下试试

project-20241226-4.png

发现样式确实已经打包成功,但是我们的 font 文件并没有一并打包出来,这时候我们需要将 font 文件拷贝出来,我们需要用到一个插件 rollup-plugin-copy

pnpm i rollup-plugin-copy -D -w

再来添加一下配置,我们将 packages/theme-chalk/fonts/ 路径下的所有文件打包拷贝到 dist/theme-chalk/fonts/

import resolve from "@rollup/plugin-node-resolve";
import vuePlugin from "rollup-plugin-vue";
import postcss from "rollup-plugin-postcss";
import autoprefixer from "autoprefixer";
import copy from "rollup-plugin-copy";

export default {
  input: "./packages/components/index.js",
  output: {
    file: "dist/es.js",
    name: "TestUI",
    format: "es",
  },
  plugins: [
    resolve(),
    vuePlugin(),
    postcss({
      extract: "theme-chalk/style.css", // 将css提取到单独的文件中
      plugins: [autoprefixer()],
    }),
    copy({
      targets: [{ src: "packages/theme-chalk/fonts/*", dest: "dist/theme-chalk/fonts/" }],
    }),
  ],
  external: ["vue"], // 依赖模块
};

再打包一下看看

project-20241226-5.png

font 文件顺利拷贝出来了,但是路径不对,我们总不能每次打包完成后手动修改路径,这时候我们又需要使用一个插件 postcss-url

pnpm i postcss-url -D -w
import resolve from "@rollup/plugin-node-resolve";
import vuePlugin from "rollup-plugin-vue";
import postcss from "rollup-plugin-postcss";
import autoprefixer from "autoprefixer";
import copy from "rollup-plugin-copy";
import url from "postcss-url";

export default {
  input: "./packages/components/index.js",
  output: {
    file: "dist/es.js",
    name: "TestUI",
    format: "es",
  },
  plugins: [
    resolve(),
    vuePlugin(),
    postcss({
      extract: "theme-chalk/style.css", // 将css提取到单独的文件中
      plugins: [
        autoprefixer(),
        url({
          url: "copy",
          basePath: "fonts",
          assetsPath: "fonts",
        }),
      ],
    }),
    copy({
      targets: [{ src: "packages/theme-chalk/fonts/*", dest: "dist/theme-chalk/fonts/" }],
    }),
  ],
  external: ["vue"], // 依赖模块
};

再来试试,发现路径已经正确了

project-20241226-6.png

我们再启动一下 examples 项目看看样式是否正常

project-20241226-7.png

丸美!

✨ 拓展一下,postcss-url 也可以配置 url 属性为 inline,这样就可以将字体文件直接内联到 css 文件中,这样就可以不用拷贝 font 文件了,我们可以改一下然后打包试试

import resolve from "@rollup/plugin-node-resolve";
import vuePlugin from "rollup-plugin-vue";
import postcss from "rollup-plugin-postcss";
import autoprefixer from "autoprefixer";
import copy from "rollup-plugin-copy";
import url from "postcss-url";

export default {
  input: "./packages/components/index.js",
  output: {
    file: "dist/es.js",
    name: "TestUI",
    format: "es",
  },
  plugins: [
    resolve(),
    vuePlugin(),
    postcss({
      extract: "theme-chalk/style.css", // 将css提取到单独的文件中
      plugins: [
        autoprefixer(),
        url({
          url: "inline",
          basePath: "fonts",
          assetsPath: "fonts",
        }),
      ],
    }),
    // copy({
    //   targets: [
    //     { src: 'packages/theme-chalk/fonts/*', dest: 'dist/theme-chalk/fonts/' }
    //   ]
    // })
  ],
  external: ["vue"], // 依赖模块
};

打包之后我们点开样式文件看看,他会转为 base64 编码,这时候我们即使不用拷贝 font 文件也就可以使用,我们也可以运行一下看看也是正常显示的

project-20241226-8.png

不过还是建议将 font 分出来,因为这会造成 css 文件过大,浏览器加载时间就会变长,影响用户体验

打包为其他格式

我们也可以打包为其他格式,比如 cjs,umd 等,我们可以在 rollup.config.js 中配置一下

import resolve from "@rollup/plugin-node-resolve";
import vuePlugin from "rollup-plugin-vue";
import postcss from "rollup-plugin-postcss";
import autoprefixer from "autoprefixer";
import copy from "rollup-plugin-copy";
import url from "postcss-url";

export default {
  input: "./packages/components/index.js",
  output: [
    {
      file: "dist/es.js",
      name: "TestUI",
      format: "es",
    },
    {
      file: "dist/cjs.js",
      name: "TestUI",
      format: "cjs",
      exports: "named",
    },
    {
      file: "dist/umd.js",
      name: "TestUI",
      format: "umd",
      exports: "named",
      globals: {
        vue: "Vue",
      },
    },
  ],
  plugins: [
    resolve(),
    vuePlugin(),
    postcss({
      extract: "theme-chalk/style.css", // 将css提取到单独的文件中
      plugins: [
        autoprefixer(),
        url({
          url: "copy",
          basePath: "fonts",
          assetsPath: "fonts",
        }),
      ],
    }),
    copy({
      targets: [{ src: "packages/theme-chalk/fonts/*", dest: "dist/theme-chalk/fonts/" }],
    }),
  ],
  external: ["vue"], // 依赖模块
};

project-20241226-9.png

Vite 打包

由于我们在 examples 中使用了 vite,所以我们也可以直接使用 vite 来打包,我们在根目录下创建一个 build 文件夹,分别创建一个 vite.es.config.js vite.umd.config.js,分来代表 es 的配置和 umd 的配置

/build/vite.es.config.js

// 组件库打包配置
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";

export default defineConfig({
  plugins: [vue()],
  build: {
    outDir: "dist/es",
    lib: {
      entry: path.resolve(__dirname, "../packages/components/index.js"),
      name: "TestUI",
      fileName: "index",
      formats: ["es"],
    },
    rollupOptions: {
      external: ["vue"],
      output: {
        exports: "named",
        globals: {
          vue: "Vue",
        },
      },
    },
  },
});

/build/vite.umd.config.js

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

export default defineConfig({
  plugins: [vue()],
  build: {
    outDir: "dist/umd",
    lib: {
      entry: path.resolve(__dirname, "../packages/components/index.js"),
      name: "TestUI",
      fileName: "index",
      formats: ["umd"],
    },
    rollupOptions: {
      external: ["vue"],
      output: {
        exports: "named",
        globals: {
          vue: "Vue",
        },
        assetFileNames: (assetInfo) => {
          if (assetInfo.name === "index.css") {
            return "style.css";
          }
          return assetInfo.name;
        },
      },
    },
  },
});

我们添加一下打包命令

{
  "name": "test-ui",
  "version": "1.0.0",
  "description": "",
  "type": "module",
  "scripts": {
    "dev": "vite examples",
    "test": "echo \"Error: no test specified\" && exit 1",
    "docs:dev": "vitepress dev docs",
    "docs:build": "vitepress build docs",
    "docs:preview": "vitepress preview docs",
    "build": "rollup -c",
    "build:es": "vite build --config ./build/vite.es.config.js",
    "build:umd": "vite build --config ./build/vite.umd.config.js"
  }
}

这样我们就可以使用 pnpm run build:espnpm run build:umd 来打包 es 和 umd 格式的组件库了

本专栏源码地址