除了打包应用程序,webpack 还可以打包 JavaScript 库。以下指南适用于希望简化打包策略的库作者。
假设正在编写一个名为 webpack-numbers
的库,用于将数字 1 到 5 转换为文本表示,反之亦然,例如将 2 转换为 'two'。
基本的项目结构应该与下面类似:
project
+ |- webpack.config.js
+ |- package.json
+ |- /src
+ |- index.js
+ |- ref.json
使用 npm 初始化项目,然后安装 webpack
、webpack-cli
与 lodash
:
npm init -y
npm install --save-dev webpack webpack-cli lodash
注意:由于不需要将 lodash
一同打包到库中,因此应该将其安装为 devDependencies
而非 dependencies
,否则库体积会变得很大。
src/ref.json
[
{
"num": 1,
"word": "One"
},
{
"num": 2,
"word": "Two"
},
{
"num": 3,
"word": "Three"
},
{
"num": 4,
"word": "Four"
},
{
"num": 5,
"word": "Five"
},
{
"num": 0,
"word": "Zero"
}
]
src/index.js
import _ from 'lodash';
import numRef from './ref.json';
export function numToWord(num) {
return _.reduce(
numRef,
(accum, ref) => {
return ref.num === num ? ref.word : accum;
},
''
);
}
export function wordToNum(word) {
return _.reduce(
numRef,
(accum, ref) => {
return ref.word === word && word.toLowerCase() ? ref.num : accum;
},
-1
);
}
从如下 webpack 基本配置开始:
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
},
};
上述配置告诉 webpack 将 src/index.js
打包到 dist/webpack-numbers.js
中。
到目前为止,一切都应该与打包应用程序一样,但是打包库有一个不同的地方——需要通过 output.library
配置项暴露从入口起点导出的内容。
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
+ library: "webpackNumbers",
},
};
我们将入口暴露为 webpackNumbers
,这样用户就可以通过脚本标签使用它:
<script src="https://example.org/webpack-numbers.js"></script>
<script>
window.webpackNumbers.wordToNum('Five');
</script>
然而它只能通过被脚本标签引用而发挥作用,而不能运行在 CommonJS、AMD、Node.js 等环境中。
作为一个库作者,我们希望它能够兼容不同的环境。换言之,用户应该能够通过以下方式使用打包后的库:
在 CommonJS 模块中导入:
const webpackNumbers = require('webpack-numbers');
// ……
webpackNumbers.wordToNum('Two');
在 AMD 模块中导入:
require(['webpackNumbers'], function (webpackNumbers) {
// ……
webpackNumbers.wordToNum('Two');
});
使用脚本标签:
<!DOCTYPE html>
<html>
...
<script src="https://example.org/webpack-numbers.js"></script>
<script>
// ……
// 全局变量
webpackNumbers.wordToNum('Five');
// 属性处于 window 对象中
window.webpackNumbers.wordToNum('Five');
// ……
</script>
</html>
接下来更新 output.library
配置项,将其 type
设置为 'umd'
:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
- library: 'webpackNumbers',
+ library: {
+ name: 'webpackNumbers',
+ type: 'umd',
+ },
},
};
现在 webpack 将打包它,使其可以通过 CommonJS、AMD 模块以及脚本标签使用。
现在,如果执行 webpack
,你会发现创建了一个体积相当大的文件。查看文件可以发现 lodash 也被打包到代码中。在这种场景中,我们更倾向于把 lodash
当作 peerDependency
,即使用者应该已经自行安装过 lodash
,这样便可以放弃控制此外部库,将控制权让给使用此库的开发者。
使用 externals
配置即可实现上述目标:
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
library: {
name: "webpackNumbers",
type: "umd"
},
},
+ externals: {
+ lodash: {
+ commonjs: 'lodash',
+ commonjs2: 'lodash',
+ amd: 'lodash',
+ root: '_',
+ },
+ },
};
上面的配置意味着这个库需要一个名为 lodash
的依赖,这个依赖在开发者环境中必须存在且可用。
对于想要实现从一个依赖中调用多个文件的那些库:
import A from 'library/one';
import B from 'library/two';
// ...
无法通过在 externals
中指定整个 library
的方式将它们从 bundle 中排除,而是需要逐个或者使用正则表达式排除它们。
module.exports = {
//...
externals: [
'library/one',
'library/two',
// 匹配以 "library/" 开始的所有依赖
/^library\/.+$/,
],
};
遵循 生产环境 指南中提到的步骤优化生产环境下的输出结果。那么此时还需要生成 bundle 的文件路径,并将其添加到 package.json
中的 main
字段中。
package.json
{
...
"main": "dist/webpack-numbers.js",
...
}
或者也可以按照这个 指南 将其添加为标准模块:
{
...
"module": "src/index.js",
...
}
此处的键 main
是参照 package.json
标准,而 module
是参照 这个 提案,此提案允许 JavaScript 生态系统升级使用 ES2015 模块,而不会破坏向后兼容性。
现在便可以 将其发布为一个 npm 包,并且在 unpkg.com 找到它,并分发给用户。