Webpack组件库打包超详细指南

7,285 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情

本文介绍了从零开始,用Webpack打包一个组件库的过程。

1. 初始化项目

vue init webpack-simple tip-components

优化目录结构

修改一下项目结构,下面的目录结构是比较清晰合理的。

我们新建了build用于打包配置,doc存放文档,lib存放打包输出文件。

目录结构

2. 打包配置

不同需求的打包配置,放在不同文件中,是很好的做法。

我们的打包配置有一个基类文件,并根据不同的打包需求,有不同子类文件——完整组件库打包、单个组件打包、打包示例工程。

通过在npm的script中配置脚本,简化打包命令。

打包命令

(命令具体做了什么,选择哪个配置文件,看package.json)

运行示例工程 npm run test

打包完整组件库 npm run build

打包单个组件 npm run build:components

生成组件文档 npm run build:doc

*任意打包命令加:analyze,启用webpack-bundle-analyzer插件,打包完成后会打开bundle分析页面。

我们主要来看下三个打包配置文件。

webpack.base.js

通用的webpack配置,包括rules配置模块的读取和解析规则,以及webpack-bundle-analyzer插件。

//webpack.base.js
var path = require('path')
var webpack = require('webpack')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
  module: {
    rules: [
    //……
    ]
  }
}

if (process.env.npm_config_report) {
  module.exports.plugins = (module.exports.plugins || []).concat([new BundleAnalyzerPlugin()])
}

webpack.prod.js

继承自webpack.base.js,完整组件库的打包配置,输出文件为tip-components.min.js,包含所有组件。模块化格式为umd,适用各种引入方式。

//webpack.prod.js
var path = require('path')
var webpack = require('webpack')
const merge = require('webpack-merge');
const webpackBaseConfig = require('./webpack.base.js');

const basePath = path.resolve(__dirname, '../')

module.exports = merge(webpackBaseConfig, {
  entry: path.join(basePath, 'src', 'enter.js'),
  output: {
    path: path.resolve(__dirname, '../lib'),
    publicPath: '/lib/',
    filename: 'tip-components.min.js',  // 输出文件名
    library: 'tip-components', // 组件库名称
    libraryTarget: 'umd',  //模块化格式
    umdNamedDefine: true
  },
  externals: {
    vue: {  //将vue依赖 "外部化",不打包进组件库
      root: 'Vue',
      commonjs: 'vue',
      commonjs2: 'vue',
      amd: 'vue'
    }
  },
});

这里打包的入口文件是src/enter.js,我们来看下这个入口文件做了什么。

enter.js
import dialog from './component/dialog/dialog.js'
import marquee from './component/marquee/main.js'
import toast from './component/toast/toast.js'
//……

const components = {
    dialog,
    marquee,
    toast,
    //……
}

export default components

这里只是简单的引入所有的组件,放到components里面,再export出来。

我们在package.json文件里面添加一个打包命令。

"build": "webpack --config build/webpack.prod.js --progress --hide-modules",

运行npm run build,打包整个组件库,完成!

最后要提的配置文件是webpack.component.js,用于打包单个组件,我们放在下一节。

3. 分块打包配置

一般情况下,我们只需要用组件库中的一两个组件,引入整个组件库显然是不合理的。我们实现按需加载,按需加载的前提就是,我们的组件是支持一个一个单独打包的。

我们先新建一个components.json文件,用于配置哪些组件需要单独打包,以及组件的路径。

//components.json
{
    "marquee": "component/marquee/main.js",
    "toast": "component/toast/toast.js",
    "dialog": "component/dialog/dialog.js",
    //……
}

webpack.component.js

继承自webpack.base.js,打包单个组件。

相比webpack.prod.js,我们把entry配置为多个入口,遍历components.json来填充组件的名称和路径信息。output只需要指定一个,用占位符确保输出文件位组件的名称。

var path = require('path')
var webpack = require('webpack')
const merge = require('webpack-merge');
const components = require('./components.json')
const webpackBaseConfig = require('./webpack.base.js');

const basePath = path.resolve(__dirname, '../')
let entries = {}
Object.keys(components).forEach(key => {
  entries[key] = path.join(basePath, 'src', components[key])
})

module.exports = merge(webpackBaseConfig, {
  entry: entries,
  output: {
    path: path.resolve(__dirname, '../lib'),
    publicPath: '/lib/',
    filename: '[name].js',
    chunkFilename: '[id].js',
    library: '[name]', // 指定的就是你使用require时的模块名
    libraryTarget: 'umd',
    umdNamedDefine: true // 会对 UMD 的构建过程中的 AMD 模块进行命名。否则就使用匿名的 define
  },
  externals: {
    vue: {
      root: 'Vue',
      commonjs: 'vue',
      commonjs2: 'vue',
      amd: 'vue'
    }
  }
});

我们在package.json文件里面添加一个打包命令。

"build:components": "webpack --config build/webpack.component.js --progress --hide-modules",

运行npm run build:components,单独打包组件们,完成!

4. 按需引入组件

既然实现了单个组件的打包,我们当然不会每次都引入整个的组件库,在我们把组件库发布到tnpm后,为了引入单个组件,我们可以这样。

import dom2image from "@tencent/tip-components/lib/dom2image";

但路径太长,太麻烦了。

babel-plugin-import

我们可以用babel-plugin-import插件,对路径做一个转换。

安装插件

npm install babel-plugin-import --save-dev

在babel.config.js添加配置

//babel.config.js
plugins: [["import", {
	"libraryName": "@tencent/tip-components",
  "libraryDirectory": "lib",  // default: lib
}]]

于是,我们可以直接这样用,默认去找到lib文件下的单个组件,美滋滋。

import { dom2image } from "@tencent/tip-components";

5. 示例工程

为了方便在开发过程中调试组件,我们可以添加一个入口,用于拉起一个html页面进行调试。这与我们创建一个普通vue页面的操作是一样的,具体配置可以看webpack.demo.js。

我们添加一个打包命令,用webpack-dev-server跑一个示例页面的服务。

"test": "webpack-dev-server --config build/webpack.demo.js --open --hot",

需要注意的是,由于我们一开始是创建的webpack-simple,不是完整版的webpack模版,需要主动在html文件里面引入打包输出的js文件dist/main.js,如果你的示例页面一片空白,请检查一下输出文件是否正确引入。

6. 文档生成

jsdoc

这里选择用jsdoc来自动生成文档(后续发现,这里直接用jsdoc生成的文档比较不好看,示例不能清楚说明用法,缺少图片和GIF演示,jsdoc做为及时更新的API文档是够用的,但为了让库的用户更方便入手,建议自己组织组件的文档)。

为了vue组件能更好得用注释生成文档,我们还用了jsdoc-vuejs插件。

配置文件,看doc.conf.json。

在package.json新增一条生成文档的命令:

"build:doc": "jsdoc -c ./build/doc.conf.json ./src/*"

npm run build:doc 就能生成文档。

文档生成的具体过程可以看另一篇文章VueJS文档生成

0. 参考文章

如何打造一套vue组件库

VueJS文档生成

往期好文

“告别烂代码”

2022代码规范最佳实践(附web和小程序最优配置示例)

【前端探索】告别烂代码!用责任链模式封装网络请求

【前端探索】告别烂代码第二期!用策略模式封装分享组件

代码人生

【三年前端开发的思考】如何有效地阅读需求?

前端踩坑必看指南

【前端探索】图片加载优化的最佳实践

【前端探索】移动端H5生成截图海报的探索

【前端探索】H5获取用户定位?看这一篇就够了

【前端探索】微信小程序跳转的探索——开放标签为什么存在?

【前端探索】vConsole花式用法