在前文中,我说过本系列文章的受众是在现代前端体系下可以熟练编写业务代码的同窗,所以本文在介绍 webpack 配置时,仅说起构建一个库所特有的配置,其他配置请参考 webpack 官方文档。javascript
构建一个库与构建一个通常应用最大的不一样点在于构建完成后输出的产物。css
通常应用构建完成后会输出:html
虽然输出的资源很是多,但实际上全部的依赖、加载关系都已经从 html 文件开始一层一层定下来了,换句话说,这个 html 文件实际上就是整个应用的入口。前端
一个库构建完成后会输出:vue
库的入口分别是上面罗列的 js 文件;你可能会奇怪,一个库怎么会有3个入口文件呢?莫急,且听我一一道来。java
CommonJS 是 Node.js 推行的一种模块化规范,主要语法包括module.exports
、require()
等;而咱们在使用 webpack 引入 npm 包时,其实是处于 Node.js 环境,由此可知,这个 CommonJS 格式的入口 js 文件(<库名称>.common.js
)是供其它应用在 Node.js 环境下引入 npm 包使用的。因为在引用 npm 包时通常不会过多考虑 npm 包的体积(在构建本身的应用时若有须要可自行压缩),且为了方便调试,所以该 js 入口文件是没有通过压缩的。node
UMD 是一个模块化规范大杂烩,除了兼容 CommonJS 外,它还兼容 AMD 模块化规范,以及最传统的全局变量模式。webpack
这边稍微介绍一下 AMD 规范, AMD 全称 Asyncchronous Module Definition ,通常应用在浏览器端(这是与 CommonJS规范最大的不一样点),最著名的 AMD 加载器是 RequireJS 。目前因为 webpack 的流行, AMD 这一模块化方案已逐渐退出市场。git
全局变量模式就很好理解了,就是把库的入口挂载在一个全局变量(如window.xxx
)上,页面上的任何位置都能随时取用,属于最传统的 js 插件加载方案。github
由上可知, UMD 格式的入口 js 文件,既能够用于引用 npm 包的场景(未压缩的版本,即<库名称>.umd.js
),也能够直接用于浏览器端(已压缩的版本,即<库名称>.umd.min.js
)。
目前, webpack 不支持同时生成多份入口 js 文件,所以须要分屡次来进行构建。
关键的 webpack 配置是:
output.libraryTarget: "commonjs2"
output.libraryTarget: "umd"
对于 UMD ,咱们还须要设置全局变量名称,即output.library: "LibraryName"
。
为了压缩构建出来的文件,最简单的方法是在 CLI 中调用 webpack 命令时带上 mode 参数,如webpack --mode=production
;这是由于当 mode 的值为production
时, webpack 会自动启用 UglifyJsPlugin 对源码进行压缩。
我在某公司工做时,该公司对第三方依赖抓得很紧,全部在项目里使用的第三方依赖都必须申请且审核经过后才可以使用;且申请时是精确到具体版本的,未申请的软件版本也一律不容许使用。某些第三方依赖不管在文件内容上,仍是在文件名称上,都没有体现出版本号,这就对咱们识别这类第三方依赖产生障碍,这是咱们开发本身的库时须要引觉得戒的。
在构建库时,咱们彻底能够利用 webpack 把库的信息直接输出到文件内容里,有了这“身份信息”,用户使用起来也会格外安心。
输出库版本信息的方法是使用 webpack.BannerPlugin ,最简单的使用方法以下:
const pgk = require('./package.json');
const banner = ` ${pkg.name} ${pkg.description}\n @version v${pkg.version} @homepage ${pkg.homepage} @repository ${pkg.repository.url}\n (c) 2019 Array-Huang Released under the MIT License. hash: [hash] `;
/* webpack 配置 */
{
// ...其它配置
plugins: [
// ...其它 plugin 配置
new webpack.BannerPlugin(banner);
]
}
复制代码
最终生成出来的效果是:
/*! * * vue-directive-window * Vue.js directive that enhance your Modal Window, support drag, resize and maximize. * * @version v0.7.5 * @homepage https://github.com/Array-Huang/vue-directive-window * @repository git+https://github.com/Array-Huang/vue-directive-window.git * * (c) 2019 Array-Huang * Released under the MIT License. * hash: dc6c11a1e50821f4444a * */
复制代码
若是库的用户是直接经过在浏览器里加载你的库来使用的话,那么提供一份 source map 文件是很是有必要的;这是由于你的库在通过 webpack 构建,甚至压缩后,与源代码已经截然不同了,用户将难以在浏览器中进行调试;但若是你能为本身的库附上一份 source map ,浏览器开发者工具会调用 source map 来帮助解析,用户的调试体验会更接近于调试库的源码。
相应的 webpack 配置为:
// webpack 配置
{
// ...其它配置
devtool: 'cheap-module-source-map'
}
复制代码
webpack 支持多种类型的 source map ,不一样类型的 source map 在生成速度、支持功能(如 babel )、调试位置偏移等问题上均有不一样表现,我这边只作推荐:
关于其它类型的 source map ,请查看 webpack 官方文档的 devtool 章节。
与通常应用不同,在开发库的时候,咱们应尽可能避免引入第三方库(构建过程当中使用的工具链除外),由于这些第三方库会让咱们写的库的大小猛增;极可能会出现这样的状况:咱们本身写的小功能只有几百行代码的逻辑,构建出来的库却有几百k,那这样的库意义就不大了。
但咱们的确也很难避免使用第三方库,那该咋办呢?
// webpack 配置
{
// ...其它配置
externals: {
lodash: {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_'
}
}
}
复制代码
使用上述配置后,咱们构建出来的库中就不会包含配置中指定的第三方库(例子中为lodash
)了,下面来一一详解:
commonjs
和commonjs2
项都是指明用户在 node.js 环境下使用当前库时,以 CommonJS 的方式来加载名为lodash
的 npm 包。amd
项表示在浏览器中加载运行本库时,本库会试图以 AMD 的方式来加载名为lodash
的 AMD 模块。root
项表示在浏览器中加载运行本库时,本库会试图取全局变量window._
(经过<script>
标签加载lodash.js
时, lodash 会把本身注入到全局变量window._
中)。在通常应用中,你或许会看到这样的 externals 配置:
// webpack 配置
{
// ...其它配置
externals: {
lodash: '_'
}
}
复制代码
这样的 externals 配置方式意味着:不管在什么环境,都要取_
这个全局变量;若是当前是在通常应用且肯定已经使用<script>
来加载指定的第三方库(好比 jQuery、 Vue 等核心库,的确很常以这种方式来加载),固然大可直接这样用;但咱们做为库的做者,应提供更宽松更灵活的使用方式。
因为构建不一样模块化规范的库须要不一样的 webpack 配置(其实也只是稍有不一样)来进行屡次构建,所以本文只针对构建 UMD 格式且已压缩这一场景来展现最简单的 webpack 配置示例;若是想知道如何更有效率地拼接 webpack 配置,请看 micro-schema-validator 项目的 webpack 配置文件。
// webpack.config.js
const webpack = require('webpack');
const pkg = require('./package.json'); // 把 package.json 做为信息源
const banner = ` ${pkg.name} ${pkg.description}\n @version v${pkg.version} @homepage ${pkg.homepage} @repository ${pkg.repository.url}\n (c) 2019 Array-Huang Released under the MIT License. hash: [hash] `;
module.exports = {
entry: `${__dirname}/index.js`,
devtool: 'cheap-module-source-map',
output: {
path: `${__dirname}/dist`, // 定义输出的目录
filename: 'micro-schema-validator.min.js', // 定义输出文件名
library: 'MicroSchemaValidator', // 定义暴露到浏览器环境的全局变量名称
libraryTarget: 'umd', // 指定遵循的模块化规范
},
/* 排除第三方依赖 */
externals: {
lodash: {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_'
}
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
loader: 'babel-loader',
exclude: /(node_modules|bower_components)/
},
{
test: /(\.jsx|\.js)$/,
loader: 'eslint-loader',
exclude: /(node_modules|bower_components)/
}
]
},
plugins: [
new webpack.BannerPlugin(banner) // 输出项目信息
]
};
复制代码
对于 Vue 生态的库,如 Vue 组件、Vue 自定义指令等,可使用 vue-cli (本文特指 vue-cli 3.0 后的版本)根据你的需求来定制 webpack 配置,可定制内容包括:
定制完成后, vue-cli 将生成一个种子项目,该项目可执行(包括本地开发和构建生产环境的包)但没有实际内容(实际内容不还得由你来写嘛哈哈)。与通常的脚手架工具相比, vue-cli 除了能够生成 webpack 配置外,还将持续对其进行管理和维护,如:
摘自 vue-directive-window 项目的 vue.config.js 文件:
const webpack = require('webpack');
const pkg = require('./package.json');
const banner = ` ${pkg.name} ${pkg.description}\n @version v${pkg.version} @homepage ${pkg.homepage} @repository ${pkg.repository.url}\n (c) 2019 Array-Huang Released under the MIT License. hash: [hash] `;
module.exports = {
chainWebpack: config => {
config.output.libraryExport('default');
config.plugin('banner').use(webpack.BannerPlugin, [
{
banner,
entryOnly: true,
},
]);
},
};
复制代码
看起来是否是比上文中最基础的 webpack 配置还要简洁呢?当项目的架构逐渐丰富起来后,这个差距将不断拉大。
我会以我最近写的两个开源库:javascript-library-boilerplate 和 vue-directive-window 来做为实例项目代码来辅助介绍。
javascript-library-boilerplate 是一个现代前端生态下快速构建 javascript 库的脚手架(或称种子项目,或称示例代码,看你理解了),本库支持 GitHub 的 repository templates 功能,你能够直接在项目首页点击 Use this template 来直接套用本脚手架的代码来建立你本身的 javascript 库。
vue-directive-window 是一个能够快速让模态框(modal)支持类窗口操做的加强库;类窗口操做主要包括三大类:拖拽移动、拖拽调整窗口尺寸、窗口最大化; vue-directive-window 支持以 Vue 自定义指令或是通常 js 类的方式来调用。
vue-directive-window 相对于 javascript-library-boilerplate 来讲,更贴近 Vue 生态圈,若是你最近想为 Vue 生态圈添砖加瓦,不妨参考一下本项目。
想要阅读更多个人技术文章?请到个人 GitHub 博客 Array-Huang/blog 来,若是对您有帮助的话请 Star&Watch 走一波哈(〃^ω^)