# 搭建组件库

利用 webpack,通过入口将所有的组件模块导入并且注册,然后通过生成一个 bundle.js 文件即可。

# 创建 vue 项目

创建 vue 项目,除了 vue 项目自带的文件目录外,在根目录下添加以下目录结构:

└───build 目录:主要存放 webpack 相关配置
| 	└───webpack.build.config.js // 打包生成组件库 webpack 配置
|───lib 目录:打包生成的 bundle.js 目录
| 	└───bundle.js // 打包生成的 bundle
|───packages
|   └───index.js // 注册所有组件脚本文件
| 	└───组件名称 1
| 		└───src
| 			└───index.vue // 组件源码
| 		└───index.js // 单个组件注册
| 	└───组件名称 2
| 		└───src
| 			└───index.vue // 组件源码
| 		└───index.js // 单个组件注册
| 	└───组件名称 n
| 	└───...

# 添加生成组件库 webpack 配置

配置目录:build/webpack.biuld.config.js

# entry

入口作用:入口将所有的组件模块导入并且注册。

entry: {
    xx: path.resolve(__dirname, '../packages/index.js'),
}

# output

output: {
    path: path.resolve(__dirname, '../lib'),
    publicPath: '/lib/',
    library: 'xx', // 包名
    libraryTarget: 'umd',
    umdNamedDefine: true,
}

其中,libraryTarget (opens new window) 采用 umd 格式。

打包后的 library 暴露为所有的模块定义下都可运行的方式。它将在 CommonJS, AMD 环境下运行,或将模块导出到 global 下的变量。

# loader

babel-loader(@babel/preset-env、@babel/transform-runtime、dynamic-import-webpack)、vue-loader、css-loader、postcss-loader、sass-loader、url-loader。

静态资源如图片等统一转化成 base64,后面统一使用 iconfont 的方式使用图标。

module: {
    rules: [
        {
            test: /\.(png|jpe?g|gif|svg|ttf|woff|woff2|eot)$/i,
            use: [
                {
                    loader: 'url-loader',
                    options: {
                        limit: 10240000,
                        name: 'static/[name].[hash:6].[ext]',
                        esModule: false, // https://webpack.docschina.org/loaders/url-loader/#esmodule
                    },
                },
            ],
        },
        {
            test: /\.css$/i,
            use: [
                // 将 JS 字符串生成为 style 节点
                'style-loader',
                // 将 CSS 转化成 CommonJS 模块
                'css-loader',
            ],
        },
        {
            test: /\.s[ac]ss$/i,
            use: [
                // 将 JS 字符串生成为 style 节点
                'style-loader',
                // 将 CSS 转化成 CommonJS 模块
                'css-loader',
                // 将 Sass 编译成 CSS
                'sass-loader',
            ],
        },
        {
            test: /\.js$/,
            loader: 'babel-loader',
            exclude: /node_modules/,
        },
        {
            test: /\.vue$/,
            loader: 'vue-loader',
        },
    ],
}

# plugins

vue-loader

plugins: [
    new VueLoaderPlugin(),
    new webpack.LoaderOptionsPlugin({
        options: {
            errorDetails: true,
        },
    }),
],

# externals

externals 文档链接 (opens new window)

防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)

由于是封装组件库,有使用到如 element-ui 等第三方依赖,所以不需要再对第三方依赖包进行打包。在安装私有库的时候,会自动安装第三方依赖。

externals: [
    {
        vue: {
            root: 'Vue',
            commonjs: 'vue',
            commonjs2: 'vue',
            amd: 'vue',
        },
        lodash: {
            root: 'lodash',
            commonjs: 'lodash',
            commonjs2: 'lodash',
            amd: 'lodash',
        },
        // 'element-ui': 'element-ui',
        vuedraggable: 'vuedraggable',
    },
    /element-ui[\s\S]+/ig,
]

# 修改 package.json 入口

package.json 入口指向打包好的 bundle.js

{
	// ...
	"main": "lib/xx.js"
}

# 发布

# 方式一: 发布到 npm

通过 npm publish 发布到 npm 上,使用的时候通过 npm i xx 即可。

# 方式二:放到个人 gitlab / github

使用的时候,添加依赖到 package.json 中:

"xxx": "git+https://xxxxx.git",

删除 node_modules,重新安装依赖即可。


# 优化

原先打包处理的之后一个 bundle.js,这样在实际使用过程中没法做到按需加载。【参考链接 (opens new window)

在打包过程中,因为是全量加载,这样冗余代码都会被打包,这样明显是不合理的。

主要做两个优化:

  • 优化打包策略

# 插件使用

babel-plugin-import (opens new window) 这个插件会对 import 进行转化。

[
  'import',
  {
    libraryName: 'xxx', // 包名
  },
  'xxx', // 包名
]

转化前:

import { button } from 'xxx'

转化为:

var button = require('xxx/lib/button/index.js')

# 调整打包策略

这样除了 bundle.js,还需要将每个组件打包成独立的组件目录。

lib 目录结构调整如下:

|───lib 目录:打包生成的 bundle.js 目录
| 	└───bundle.js // 打包生成的 bundle
| 	└───组件一
| 		└───index.js
| 	└───组件 n
| 		└───index.js

通过 webpack 多入口实现,其他配置不变,添加以下配置:

/**
 * 获取 packages 下所有组件目录的名称和对应的路径, 生成所有的 entry
 * @returns entry
 */
const getWebpackEntries = () => { 
    // ...
}
module.exports = {
    // ...
    entry: getWebpackEntries(), // 多入口
    output: {
        filename: '[name]/index.js',
        path: path.resolve(__dirname, '../lib/'),
        libraryTarget: 'umd',
        globalObject: 'this',
        umdNamedDefine: true,
    },
}

# 打包后的文件大小对比

image.png