编写库(Authoring Libraries)
编写库(Authoring Libraries)
除了应用程序,webpack 还可用于打包 JavaScript 库。以下指南面向希望简化其打包策略的库作者。
编写一个库
假设我们正在编写一个小型库 webpack-numbers,它允许用户将数字 1 到 5 从数字表示转换为文本表示,反之亦然,例如,将 2 转换为 'two'。
基本项目结构如下:
项目
diff
+ ├── webpack.config.js
+ ├── package.json
+ └── /src
+ ├── index.js
+ └── ref.json
使用 npm 初始化项目,然后将 webpack、webpack-cli 和 lodash 安装为开发依赖:
bash
npm init -y
npm install --save-dev webpack webpack-cli lodash
我们将 lodash 安装为 devDependency,因为最初我们会将它打包进我们的库中。由于它包含在最终输出中,我们库的使用者将无需自行安装它。
src/ref.json
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
js
import _ from "lodash";
import numRef from "./ref.json";
export function numToWord(num) {
return _.reduce(
numRef,
(accum, ref) => (ref.num === num ? ref.word : accum),
"",
);
}
export function wordToNum(word) {
return _.reduce(
numRef,
(accum, ref) => (ref.word === word && word.toLowerCase() ? ref.num : accum),
-1,
);
}
Webpack 配置
让我们从以下基本的 webpack 配置开始:
webpack.config.js
js
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "webpack-numbers.js",
},
};
在上面的示例中,我们让 webpack 将 src/index.js 打包到 dist/webpack-numbers.js。
添加 Source Map
在打包库时,建议生成 source map。Source map 允许您库的使用者调试您的原始源代码,而不是压缩后的 bundle。这可以通过 devtool 选项实现:
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
+ devtool: 'source-map',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
},
};
T> 将 devtool 的值设置为 'source-map' 会生成一个与 bundle 同目录的独立 .map 文件。请确保也发布这个 .map 文件,以便使用者可以用它进行调试。
暴露库
到目前为止,一切都应该与打包应用程序相同,接下来的部分则有所不同——我们需要通过 output.library 选项暴露入口点的导出。
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
+ library: 'webpackNumbers',
},
};
我们将入口点暴露为 webpackNumbers,以便用户可以通过 script 标签使用它:
html
<script src="https://example.org/webpack-numbers.js"></script>
<script>
window.webpackNumbers.wordToNum("Five");
</script>
然而,这仅在通过 script 标签引用时有效,它不能用于其他环境,如 CommonJS、AMD、Node.js 等。
作为库作者,我们希望它能兼容不同的环境,即用户应该能够通过以下几种方式使用打包后的库:
-
CommonJS 模块 require:
jsconst webpackNumbers = require("webpack-numbers"); // ... webpackNumbers.wordToNum("Two"); -
AMD 模块 require:
jsrequire(["webpackNumbers"], (webpackNumbers) => { // ... webpackNumbers.wordToNum("Two"); }); -
script 标签:
html<!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':
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'webpack-numbers.js',
- library: 'webpackNumbers',
+ globalObject: 'this',
+ library: {
+ name: 'webpackNumbers',
+ type: 'umd',
+ },
},
};
现在 webpack 将打包一个可以同时适用于 CommonJS、AMD 和 script 标签的库。
T> 请注意,library 配置与 entry 配置相关。对于大多数库,指定一个单一的入口点就足够了。虽然可以实现多部分库,但更简单的做法是通过一个 index script 作为单一入口点来暴露部分导出。对于库来说,不建议使用 array 作为 entry 入口点。
外部化 Lodash
现在,如果你运行 npx webpack,你会发现创建了一个相当大的 bundle。如果你检查该文件,会看到 lodash 已经与你的代码一起被打包。为了避免打包 lodash 并使我们的库变得臃肿,我们可以配置 webpack 将其视为外部模块。由于我们不再打包它,使用者将需要自行提供它。因此,你应该将 lodash 从 devDependencies 移动到 dependencies(或 peerDependencies),以便包管理器为你库的使用者自动安装它。
这可以通过 externals 配置来实现:
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
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 的依赖。
外部化的限制
对于使用某个依赖中多个文件的库:
js
import A from "library/one";
import B from "library/two";
// ...
你无法通过在 externals 中指定 library 来将它们排除在 bundle 之外。你需要逐个排除它们,或者使用正则表达式。
js
export default {
// ...
externals: [
"library/one",
"library/two",
// 所有以 "library/" 开头的内容
/^library\/.+$/,
],
};
最终步骤
按照生产环境指南中提到的步骤优化你的生产环境输出。同时,让我们在 package.json 中添加生成 bundle 的路径作为包的 main 字段。
package.json
json
{
...
"main": "dist/webpack-numbers.js",
...
}
或者,根据此指南将其作为标准模块添加:
json
{
...
"module": "src/index.js",
...
}
main 键指的是 package.json 的标准,而 module 键指的是一个提案,旨在让 JavaScript 生态系统在不破坏向后兼容性的情况下升级使用 ES2015 模块。
W> module 属性应指向使用 ES2015 模块语法但不使用浏览器或 node 尚不支持的其他语法特性的脚本。这使得 webpack 可以自行解析模块语法,如果用户只使用库的某些部分,则可以通过摇树优化实现更轻量的 bundle。
现在,你可以将其发布为 npm 包,并在 unpkg.com 上找到它,以分发给你的用户。
T> 要暴露与你的库关联的样式表,应使用 MiniCssExtractPlugin。用户可以像加载任何其他样式表一样使用和加载它们。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
