组件库开发小记(三)组件库打包

2,140 阅读4分钟

组件库开发小记(三)组件库打包

组件库打包

组件库打包属于一个相对不好处理但关键的环节,全局导入和按需加载均与这个环节有关。

在这里我进行了尝试,第一阶段是直接在vue/cli上进行组件搭建,但是由于vue内置对webpack进行了多种配置,因此在第二阶段自我搭建vue的开发环境,自定义webpack配置,发现按需加载的打包后包的体积大幅度缩小,同时这也带来一些问题,比如在自我搭建的环境下,vue test utils + jest的失效与不能使用vue-cli-service lib全局库打包命令。

全局引入

如果仅仅需要全局引入,那么可以使用vue自带的生成库的命令,如针对本组件库来说可运行如下命令:

vue-cli-service build --target lib --name CreateUI --dest libs packages/index.js

这样就能在目标文件夹libs生成所需要的全局加载模块,前端引用的时候直接使用CreateUI.umd.min.js文件即可。

dist/CreateUI.umd.min.js    // 压缩后的 UMD 构建版本
dist/CreateUI.umd.js        // 一个直接给浏览器或 AMD loader 使用的 UMD 包
dist/CreateUI.common.js     // 一个给打包器用的 CommonJS 包
dist/CreateUI.css           // 提取出来的 CSS 文件

更多内容见官方文档中的描述构建目标 | Vue CLI

按需引入

按需引入就需要配置webpack.config.js来进行定制。

打包配置

module.exports = {
  outputDir: resolve("lib"),
  configureWebpack: {
    entry: getComponentEntries("packages"),
    output: {
      filename: "[name].js",
      library: "create-ui",
      libraryTarget: "umd",
      libraryExport: "default",
    },
    // exclude 'vue'
    externals: {
      vue: {
        root: "Vue",
        commonjs: "vue",
        commonjs2: "vue",
        amd: "vue",
      },
    },
    resolve: {
      extensions: ["", ".js", ".vue"],
    },
  },
  // extract css from file
  css: {
    extract: {
      filename: "theme/[name].css",
    },
  },
  // delete some build resource
  chainWebpack: (config) => {
    config.optimization.delete("splitChunks");
    config.entryPoints.delete("app");
    config.plugins.delete("preload");
    config.plugins.delete("prefetch");
    config.plugins.delete("html");
    config.plugins.delete("copy");
    config.plugins.delete("hmr");
  },
};

按字段进行如下分析:

  • outputDir:指明输出的目录

  • entry:指明入口文件。按需加载需要分别对组件进行打包,获取每个组件的入口目录,通过以下代码来获取了packages目录下全局加载和各个组件的index.js文件。

    function getComponentEntries(dir) {
      let entries = {};
      let files = fs.readdirSync(resolve(dir));
    
      files.forEach((file) => {
        const filepath = path.join(dir, file);
        const isDir = fs.statSync(resolve(filepath)).isDirectory();
        const [name, suffix] = file.split(".");
    
        if (isDir) {
          entries[name] = resolve(path.join(filepath, "index.js"));
        } else if (suffix === "js") {
          entries[name] = resolve(filepath);
        }
      });
    
      return entries;
    }
    
    
  • output 具体配置见HERE

    • filename:对应entry打包后的目标文件,[name]则代表入口名称,另外还有[id] [hash]等多种形式的字符变量。

    • library:代表库名,为你的入口做导出。如下,MyLibrary即代表该名称。

      <script src="https://example.org/path/to/my-library.js"></script>
      <script>
        MyLibrary.hello('webpack');
      </script>
      
    • libraryTarget:配置如何暴露 library。它包含着多种选项。其中umd的意思是将 library 暴露为所有的模块定义下都可运行的方式。它将在 CommonJS, AMD 环境下运行,或将模块导出到 global 下的变量。

    • libraryExport:指定哪一个导出应该被暴露为一个库。

      // 如果入口有一个默认导出
      var MyLibrary = _entry_return_.default;
      
  • externals 从输出的 bundle 中排除依赖,例如Vue模块本身不需要被打包,而它的目的就是为了使得在本身不引入vue的情况下,在各种环境中都可以运行。

    • root:可以通过一个全局变量访问 library

    • commonjs:可以将 library 作为一个 CommonJS 模块访问,例如:如果代码中包含import vue from 'vue',则会编译为如下结果,const vue = require('vue')

    • ...

  • resolve:设置模块如何被解析。例如引入import 'App',不用加后缀.vue

  • css/extract:分离出样式文件并设置目标文件,这里将样式全部写入theme文件夹下,模仿了ElementUI的lib格式。

  • chainWebpack:此处可以对Vue内置的打包配置进行修改,比如一般打包后会出html页面文件等乱七八糟的东西,这里删除一些打包资源。(该配置来源于另外文章,不过现在找不到在哪里了)

在这样的打包配置下,就可以生成如下的目录结构:

├── lib
│   ├── chatbox.js
│   ├── index.js
│   ├── loading.js
│   ├── modal.js
│   ├── theme
│   │   ├── chatbox.css
│   │   ├── index.css
│   │   ├── loading.css
│   │   ├── modal.css
│   │   └── toast.css
│   └── toast.js

环境配置

非常建议此处进行各类环境的配置,包括:开发环境、src打包环境、lib打包环境、测试环境等等。

Vue环境变量

这里可以使用vue特有的环境变量,模式和环境变量 | Vue CLI。例如:

  1. 在根目录下创建.env.lib代表lib环境,内容如下:

    NODE_ENV=lib
    VUE_APP_TITLE=create-ui
    
  2. 使用环境变量脚本:"build:lib": "vue-cli-service build --mode lib"

  3. 打包配置根据环境变量进入不同配置文件中:

    const { defineConfig } = require("@vue/cli-service");
    
    const envs = {
      development: () => require("./config/dev.env"),
      lib: () => require("./config/lib.env"),
      production: () => require("./config/prod.env"),
      test: () => require("./config/test.env"),
    };
    
    module.exports = defineConfig(envs[process.env.NODE_ENV]);
    

Cross-env

可以使用Cross-env这一个模块来跨平台设置环境变量:

"build:dev": "cross-env NODE_ENV=lib vue-cli-service build"

加载方式

如果是全局引入那么直接引入index.js文件和全局样式index.css,而如果是对应组件的文件则同理:

import Chatbox from "CreateUI/chatbox.js"
import "CreateUI/theme/chatbox.css"

这里你就会思考了,我们一般使用的时候是下面的这种方式:

import { Chatbox } from "CreateUI"

因此接下来该讲到与它相关的两个Babel plugin,这里经常会遇到问题,因此下篇文章中我会详细介绍我们最常用的两种插件如何配置按需加载,以及如何发布测试它的使用:

babel-plugin-componentbabel-plugin-import

相关资料