组内负责的几个项目都有一些一样的公共组件,所以就着手搭建了个公共组件开发脚手架,第一次开发 library,所以是参考着 iview 的配置来搭建的。记录如何使用webpack4
搭建一个library
的脚手架
前言
使用 webpack4,需要安装 webpack 和 webpack-cli
1 | yarn add webpack webpack-cli -D |
然后就是书写配置文件。
webpack4 会另写一篇总结
library 结构
我写的 library 的目录结构如下,仅供参考,主要是模仿 iview
的结构,其中部分配置参考了 vue-cli
的 webpack 配置文件。
1 | ├─build |
经过 webpack 编译后的代码
为了好理解 library,先来了解下 webpack 编译后的代码。
1 | // webpack编译后的代码 |
webpack 编译后的代码的整体结构就是一个IIFE函数
,接受一个 modules: Array
参数。
对于模块处理,无论是 ES Module
的 import
还是 commonjs
的 require
都转化为__webpack_require__
这个函数来引入模块。
__webpack_require__
函数,会从 modules
数组的第一个元素开始(moduleId 为 0,也就是入口文件),执行该模块(实为一个函数)的逻辑,利用传入的module.exports
的数据类型为引用类型Object
,间接地给module.exports
添加属性。
return __webpack_require__(0)
从入口文件开始,逐个引入依赖模块,最后返回入口模块的 module.exports
此时这个编译后的 js 文件,是无法被其他模块所引用的,只在当前作用域内有效, webpack
就提供了创建 library 的方式,就是在output
里定义library
和 libraryTarget
。使得构建完的 js 可以供其他模块引入使用。
对于作为一个 library 使用的项目来说,output 选项需要设置 library
1 | // webpack.dist.pord.conf.js |
library
可以是字符串,也可以是对象,(对象仅限于 libraryTarget
的值为 umd
的情况下使用)
1 | output: { |
commonjs
和 commonjs2
的区别。
commonjs
规范就是定义了一个 exports
对象,而 nodejs
在实现的时候,在 commonjs
规范的前提下做了一些扩展,定义了 module.exports
,从而也叫这种为 commonjs2
规范。
我们在引用别人的库的时候,通常都是可以通过多种方法引入的,比如 <script>
标签引入,通过 commonJS
模块 引入,通过 ES6 Module
引入。
libraryTarget
设置为umd
(通用模块规范)的话,则打包后可以通过多种模块加载的方法加载 library,具有高兼容性。
library 的依赖问题
如果我们的 library 是基于某某库的基础上开发的,比如说写一个基于vue
的 UI 组件库,在开发的这个组件库的时候,我们需要引入vue
,如果使用这个组件库的用户本身就已经引入了vue
,那么vue
就会被引入并打包两次,所以我们在开发一个library
的时候,对于一些所依赖的模块,可以由引入library
的使用者提供。所以我们需要将依赖的模块在 library 的打包构建中去除。
externals
的作用,防止将某些 import
的包打包到 bundle 中,而是在运行时再去外部获取这些扩展依赖。
通过设置 externals
,从输出的 bundle
中排除 vue
和 iview
。
这些外部依赖可能是以下的任何一种形式。
root
全局变量访问commonjs
作为一个commonjs
模块引入commonjs2
与commonjs
类似,不过导出的是module.exports
amd
使用amd
模块规范引入
1 | // webpack.dist.pord.conf.js |
另外在 package.json 加多一个peerDependencies
字段,作用是约定library
所依赖的库的版本号,在使用者下载使用library
的时候,如果所依赖的 iview
和 vue
的版本号不对,就会发出警告。
1 | "peerDependencies": { |
对于这两个依赖,写到开发环境依赖中 ,不然安装时,会在库的目录下安装vue
和 iview
,这也不符合让 library
的引用者提供 library
的依赖这个想法。
vue 插件库/ 组件库
对于 vue 的插件库 / 组件库来说,如果想要全局引入的话,需要有一个install
方法。install 内部逻辑就是通过参数传进来的vue
对象,注册所有组件。然后最后将所有公共组件连同install
方法组成一个新对象暴露出去。
1 | // 引入公共组件 |
在暴露含有 install 方法的对象时,一开始使用的是 module.exports
,引用 library 的时候报错Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'
。
因为我测试用的项目关闭了 babel
对 ES Module
的编译,通常情况下,没有手动关闭的话,babel
会将 ES6 Module
编译转换成 commonjs
规范。所以在关闭了module
的转换的情况下,由于库的输出使用的是 commonjs
规范的 module.exports
,而引入库使用的是 ES6 模块规范的import
关键字,所以产生了报错,我将库的导出写成export
关键字,就没报错了。
以后书写某一模块的导入导出,还是使用同一种规范的,不要混用。
我也了解到了现在大多数库,都是用的 commonjs
规范,由于 webpack
的 tree-shaking
只对 ES Module
起作用。而webpack
的 tree-shaking
实际上是由Uglylify
来实现的。
所以库的模块规范可以使用两种,利用 package.json
的 main
和 module
字段分别定义库的两种模块规范的入口文件。 main
使用的是 commonjs
规范语法书写的文件,而 module
是使用 ES6 module
语法书写的文件,module
字段目前还是一个提案。所以采用了 ES2015
模块语法的库的,当我们只使用到 library
的部分代码,则可以利用webpack
进行 tree-shaking
,去除未引用的代码,减少打包文件体积。
发布 npm 包
首先就是需要注册一个 npm 账号。
如果之前使用的是淘宝镜像的话,需要先切回 npm 官方源。不然是发不了包的。
打开命令行
切换官方源 npm config set registry https://registry.npmjs.org/
执行npm login
,然后输入你的账号信息。
可以配置.npmignore
忽略一些不需要上传的文件,写法跟.gitignore
相同。
需要保证项目有正确的package.json
文件和README.md
文件
然后执行npm publish
进行发包。
发完包就可以切回淘宝镜像源
npm config set registry https://registry.npm.taobao.org
引用组件库
Script 标签引入
一开始是将 JS 文件放在本地测试,发现在HTML
文件的第一行就报错,Unexpected token <
,StackOverflow说的原因是引入路径不正确,所以我就把 JS 文件放到 CDN 上了,
1 | <script src="http://osuuzm0m8.bkt.clouddn.com/hbf.min.js"></script> |
输出
全量引用
可以像使用其他 vue 插件库/组件库一样使用。
1 | import hbf from 'hbf-public-components' |
如果是没有导出default
变量,则使用另外一种方式全量引入
1 | import * as hbf from 'hbf-public-components' |
按需引用
1 | import { publicMenu } from 'hbf-public-components' |
按需引用,如果 library 使用的是ES2015 Module
规范,则不需要安装任何插件,webpack 会对其进行tree-shaking
,去除未引用的代码。
前面提过,webpack
的tree-shaking
是由Uglylify
插件实现的,我在开发环境下,没有启用Uglylify
来压缩代码,所以查看模块打包图,会发现整个库都被引入了,虽然我只引入了一个组件。webpack4
在生产环境下,才会进行tree-shaking
,设置mode
的值为production
就会开启生产环境下的优化。
如果是使用 commonjs
规范的 library 则需要一个插件支持,babel-plugin-import。该插件是ant
官方开发的。许多 UI 组件库的按需引入也是依赖于这个插件。
安装
yarn add babel-plugin-import -D
修改.babelrc
文件,
1 | "plugins": [ |
总结
其实如果对于一个不是很稳定,需要一直迭代更新
的公用组件库
来说,使用 npm 包的话,会比较不方便,经常更新的公共组件代码可以使用Git subtree
(教程)来维护,可以等到一定地步之后,公共组件库稳定下来之后再考虑发布一个 npm 包。
而开发一个组件库,也可以使用 rollup.js 来搭脚手架,rollup.js
默认使用的就是 ES2015 Module
,可以进行静态分析,去除未引用的代码,tree-shaking
也是 rollup.js
先提出的。Rollup
相比较于Webpack
,更适合用于构建library
,Vue.js
就是使用 Rollup
构建的。Webpack
在代码分割这方面比较有优势,所以webpack
相对来说比较适合构建应用程序,不过使用 webpack 构建 library 也是可以的。
这个项目也可以用做 webpack 构建 library 的通用脚手架。下次再尝试Rollup
构建 library
项目地址: https://github.com/huya-base-fed/public-components