Webpack 提供了多种接口来自定义编译过程。 接口间存在一些重叠功能,例如配置选项可能通过 CLI 标志启用, 而其他仅存在于单个接口。 以下概念可以帮助你快速入门。
命令行界面(CLI),
用于配置构建并与之交互。它在早期原型制作和性能分析的情况下特别有用。
大多数情况下,
CLI 只是用于通过配置文件和一些标志(例如--env
)来启动进程。
使用 webpack 处理模块时, 最重要的是理解不同的模块语法 - 特别是受支持的方法和变量。
尽管大多数用户可以借助 CLI 和配置文件控制编译, 通过 Node 接口可以实现更细粒度的控制。 这包括传递多个配置、 以程序的方式运行、查阅并收集统计信息。
Loaders 用于对模块的源代码进行转换。 它们被编写成一类将源代码作为参数传入, 并将编译转换后的新版本代码传出的函数体。
插件接口允许用户直接介入编译过程。 插件可以在不同时期运行的生命周期挂钩上注册回调函数。 在单个编译流程中,当每个钩子都被执行后,插件将拥有 当前编译流程的完整访问权限。
为了更合适且方便地使用配置,可以在 webpack.config.js
中对 webpack 进行配置。CLI 中传入的任何参数会在配置文件中映射为对应的参数。
如果你还没有安装过 webpack 和 CLI,请先阅读 安装指南。
webpack-cli 提供了许多命令来使 webpack 的工作变得更简单。默认情况下,webpack 提供了以下命令:
Command | Usage | Description |
---|---|---|
build | build|bundle|b [entries...] [options] | Run webpack (default command, can be omitted). |
configtest | configtest|t [config-path] | Validate a webpack configuration. |
help | help|h [command] [option] | Display help for commands and options. |
info | info|i [options] | Outputs information about your system. |
init | init|create|c|new|n [generation-path] [options] | Initialize a new webpack project. |
loader | loader|l [output-path] [options] | Scaffold a loader. |
plugin | plugin|p [output-path] [options] | Scaffold a plugin. |
serve | serve|server|s [options] | Run the webpack-dev-server . |
version | version|v [commands...] | Output the version number of webpack , webpack-cli and webpack-dev-server . |
watch | watch|w [entries...] [options] | Run webpack and watch for files changes. |
运行 webpack(默认命令,可用输出文件)。
npx webpack build [options]
示例
npx webpack build --config ./webpack.config.js --stats verbose
用于初始化一个新的 webpack 项目。
npx webpack init [generation-path] [options]
example
npx webpack init ./my-app --force --template=default
生成配置的位置。默认为 process.cwd()
。
-t
, --template
string = 'default'
要生成的模板名称。
-f
, --force
boolean
不输入配置项就生成一个项目。该配置启用时所有命令行配置项将使用默认值。
初始化一个 loader。
npx webpack loader [output-path] [options]
示例
npx webpack loader ./my-loader --template=default
输出文件夹,例如 ./loader-name
。
-t
, --template
string = 'default'
模板类型。
初始化一个插件。
npx webpack plugin [output-path] [options]
示例
npx webpack plugin ./my-plugin --template=default
输出文件夹,例如 ./plugin-name
.
-t
, --template
string = 'default'
模板类型。
输出你的系统信息。
npx webpack info [options]
示例
npx webpack info --output json --addition-package postcss
-a
, --additional-package
string
在输出信息中添加额外的包。
example
npx webpack info --additional-package postcss
-o
, --output
string : 'json' | 'markdown'
获取指定格式的输出。
示例
npx webpack info --output markdown
校验 webpack 配置。
npx webpack configtest [config-path]
示例
npx webpack configtest ./webpack.config.js
你的 webpack 配置文件路径。默认为 ./webpack.config.js
。
运行 webpack 开发服务器。
npx webpack serve [options]
示例
npx webpack serve --static --open
运行 webpack 并且监听文件变化。
npx webpack watch [options]
示例
npx webpack watch --mode development
默认情况下,webpack 提供了以下 flag:
Flag / 别名 | 类型 | 描述 |
---|---|---|
--entry | string[] | 应用程序的入口文件,例如 ./src/main.js |
--config, -c | string[] | 提供 webpack 配置文件的路径,例如 ./webpack.config.js |
--config-name | string[] | 要使用的配置名 |
--name | string | 配置名称,在加载多个配置时使用 |
--color | boolean | 启用控制台颜色 |
--merge, -m | boolean | 使用 webpack-merge 合并两个配置文件,例如 -c ./webpack.config.js -c ./webpack.test.config.js |
--env | string[] | 当它是一个函数时,传递给配置的环境变量 |
--progress | boolean, string | 在构建过程中打印编译进度 |
--help | boolean | 输出所有支持的 flag 和命令 |
--output-path, -o | string | webpack 生成文件的输出位置,例如 ./dist |
--target, -t | string[] | 设置要构建的 target |
--watch, -w | boolean | 监听文件变化 |
--watch-options-stdin | boolean | stdin stream 结束时,停止监听 |
--hot, -h | boolean | 启用 HMR |
--devtool, -d | string | 控制是否生成 source map,以及如何生成 |
--prefetch | string | 预先发生请求 |
--json, -j | boolean, string | 将结果打印成 JSON 格式或存储在文件中 |
--mode | string | 定义 webpack 所需的 mode |
--version, -v | boolean | 获取当前 cli 版本 |
--stats | boolean, string | 它告诉 webpack 如何处理 stats |
--analyze | boolean | 它调用 webpack-bundle-analyzer 插件来获取 bundle 信息 |
Flag | 描述 |
---|---|
--no-color | 禁用控制台颜色 |
--no-hot | 如果你通过配置启用了 HMR,则禁用它 |
--no-stats | 禁用任何由 webpack emit 出来的 stats |
--no-watch | 禁用文件变更的监听 |
--no-devtool | 禁止生成 source maps |
从 CLI v4 和 webpack v5 开始,CLI 将采用从 webpack 的 core 中导入整个配置的模式,允许 CLI 调整几乎所有配置项。
链接中是 webpack v5 和 CLI v4 支持的所有核心 flag 列表 - 详戳
例如,如果你想在项目中启用性能提示,你需在配置中使用此选项,而如果使用核心 flag,你可以这样做:
npx webpack --performance-hints warning
npx webpack [--config webpack.config.js]
配置文件中的相关选项,请参阅配置。
npx webpack --entry <entry> --output-path <output-path>
example
npx webpack --entry ./first.js --entry ./second.js --output-path /build
构建项目时入口可以配置一个文件名或一组被命名过的文件名。你可以传递多个入口(每个入口在启动时加载)。 如下是通过 CLI 指定 entry 的多种方式:
npx webpack ./first-entry.js
npx webpack --entry ./first-entry.js
npx webpack ./first-entry.js ./other-entry.js
npx webpack --entry ./first-entry.js ./other-entry.js
用于存储构建后的文件路径。它将映射到配置选项 output.path
。
示例
假设你的项目结构像下面这样:
.
├── dist
├── index.html
└── src
├── index.js
├── index2.js
└── others.js
npx webpack ./src/index.js --output-path dist
这将对源码进行打包,其入口为 index.js
,且 bundle 文件的输出路径为 dist
。
asset main.js 142 bytes [compared for emit] [minimized] (name: main)
./src/index.js 30 bytes [built] [code generated]
./src/others.js 1 bytes [built] [code generated]
webpack 5.1.0 compiled successfully in 187 ms
npx webpack ./src/index.js ./src/others2.js --output-path dist/
以多个入口的方式打包文件
asset main.js 142 bytes [compared for emit] [minimized] (name: main)
./src/index.js 30 bytes [built] [code generated]
./src/others2.js 1 bytes [built] [code generated]
./src/others.js 1 bytes [built] [code generated]
webpack 5.1.0 compiled successfully in 198 ms
CLI 会在你的项目路径中寻找默认配置,以下是 CLI 采集到的配置文件。
此处按顺序递增进行优先级查询:
示例 —— 配置文件的查找顺序 .webpack/webpackfile > .webpack/webpack.config.js > webpack.config.js
'webpack.config',
'.webpack/webpack.config',
'.webpack/webpackfile',
列出命令行可用的基础命令和 flag
通过 webpack help [command] [option]
以及 webpack [command] --help
均可获得帮助信息:
npx webpack --help
# or
npx webpack help
列出所有 cli 支持的命令和 flag
npx webpack --help=verbose
查看特定命令或选项的帮助:
npx webpack help --mode
显示已安装的 package 以及子 package 的版本。
如需检查你正在使用的 webpack
和 webpack-cli
的版本,只需运行如下命令:
npx webpack --version
# or
npx webpack version
运行结果如下:
webpack 5.31.2
webpack-cli 4.6.0
若已安装 webpack-dev-server
,其版本信息会一并输出:
webpack 5.31.2
webpack-cli 4.6.0
webpack-dev-server 3.11.2
如需检查 webpack-cli
子包的版本(如 @webpack-cli/info
),只需运行如下命令:
npx webpack info --version
运行结果如下:
@webpack-cli/info 1.2.3
webpack 5.31.2
webpack-cli 4.6.0
webpack-dev-server 3.11.2
使用配置文件进行构建
配置文件默认为 webpack.config.js
,还可以指定其它的配置文件。
npx webpack --config example.config.js
如果你的配置文件导出了多个配置,你可以使用 --config-name
来指定要运行的配置。
如果你的 webpack.config.js
如下:
module.exports = [
{
output: {
filename: './dist-first.js',
},
name: 'first',
entry: './src/first.js',
mode: 'development',
},
{
output: {
filename: './dist-second.js',
},
name: 'second',
entry: './src/second.js',
mode: 'development',
},
{
output: {
filename: './dist-third.js',
},
name: 'third',
entry: './src/third.js',
mode: 'none',
stats: 'verbose',
},
];
并且只想运行第二个配置项:
npx webpack --config-name second
你也可以传递多个值来实现:
npx webpack --config-name first --config-name second
你可以通过 --merge
选项来合并两个或多个不同的 webpack 配置:
npx webpack --config ./first.js --config ./second.js --merge
以 JSON 格式输出 webpack 的运行结果
npx webpack --json
如果你想把 stats 数据存储为 JSON 而非输出,你可以使用:
npx webpack --json stats.json
在其他情况下,webpack 会打印出 bundle、chunk 以及 timing 细节的 stats 信息。使用此选项,会输出 JSON 对象。这个输出结果可以被 webpack 的 analyse 工具,或者 chrisbateman 的 webpack-visualizer,再或者 th0r 的 webpack-bundle-analyzer 所识别。analyse 工具会接收 JSON,并以图形的形式展示所有构建的细节。
当 webpack 配置导出为函数时,会接收到一个 "environment" 的参数。
npx webpack --env production # env.production = true
--env
参数可以接收多个值:
Invocation | Resulting environment |
---|---|
npx webpack --env prod | { prod: true } |
npx webpack --env prod --env min | { prod: true, min: true } |
npx webpack --env platform=app --env production | { platform: "app", production: true } |
npx webpack --env foo=bar=app | { foo: "bar=app"} |
npx webpack --env app.platform="staging" --env app.name="test" | { app: { platform: "staging", name: "test" } |
除了上面所说的自定义 env
变量,在你的 webpack 配置中也使用了一些 env
内置变量:
变量名 | 描述 |
---|---|
WEBPACK_SERVE | 如果使用了 serve|server|s ,则为 true 。 |
WEBPACK_BUILD | 如果使用了 build|bundle|b ,则为 true 。 |
WEBPACK_WATCH | 如果使用了 --watch|watch|w ,则为 true 。 |
请注意你不能在 bundle 代码中使用这些内置环境变量。
module.exports = (env, argv) => {
return {
mode: env.WEBPACK_SERVE ? 'development' : 'production',
};
};
你可以使用 --node-env
选项来设置 process.env.NODE_ENV
:
npx webpack --node-env production # process.env.NODE_ENV = 'production'
参数 | 说明 | 输入类型 | 默认值 |
---|---|---|---|
--config | 配置文件的路径 | string[] | 默认配置 |
--config-name | 要使用的配置名 | string[] | |
--env | 当配置文件为函数时,environment 将作为参数传递给配置 | string[] |
你可以使用 webpack-bundle-analyzer
插件来分析你 webpack 输出的 bundle。你还可以通过 CLI 的 --analyze
flag 调用它
npx webpack --analyze
如需查看 webpack 的编译进度,你可以使用 --progress
flag。
npx webpack --progress
如需收集编译过程中每一步的 profile 数据,你可以将 profile
作为值传递给 --progress
flag。
npx webpack --progress=profile
将参数直接传递给 Node.js 进程,你可以使用 NODE_OPTIONS
选项。
例如,将 Node.js 进程的内存限制增加到 4 GB。
NODE_OPTIONS="--max-old-space-size=4096" webpack
此外,你也可以将多个选项传递给 Node.js 进程。
NODE_OPTIONS="--max-old-space-size=4096 -r /path/to/preload/file.js" webpack
退出代码 | 描述 |
---|---|
0 | 成功 |
1 | webpack Error |
2 | 配置/选项问题,或者内部错误 |
环境变量 | 描述 |
---|---|
WEBPACK_CLI_SKIP_IMPORT_LOCAL | 设置为 true 时将不会使用 webpack-cli 本地实例。 |
WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG | 设置为 true 则强制加载 ESM 配置。 |
WEBPACK_PACKAGE | 在 CLI 中使用自定义 webpack 版本。 |
WEBPACK_DEV_SERVER_PACKAGE | 在 CLI 中使用自定义 webpack-dev-server 版本。 |
WEBPACK_CLI_HELP_WIDTH | 用于帮助输出的自定义宽度。 |
WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG=true npx webpack --config ./webpack.config.esm
在 CLI 中使用自定义 webpack 版本。在你的 package.json
中配置以下内容:
{
"webpack": "^4.0.0",
"webpack-5": "npm:webpack@^5.32.0",
"webpack-cli": "^4.5.0"
}
使用 webpack v4.0.0
:
npx webpack
使用 webpack v5.32.0
:
WEBPACK_PACKAGE=webpack-5 npx webpack
You might encounter this error in the case of using native ESM in TypeScript (i.e. type: "module"
in package.json
).
webpack-cli
supports configuration in both CommonJS
and ESM
format, at first it tries to load a configuration using require()
, once it fails with an error code of 'ERR_REQUIRE_ESM'
(a special code for this case) it would try to load the configuration using import()
.
However, the import()
method won't work with ts-node
without loader hooks enabled (described at TypeStrong/ts-node#1007
).
To fix the error above use the following command:
NODE_OPTIONS="--loader ts-node/esm" npx webpack --entry ./src/index.js --mode production
For more information, see our documentation on writing a webpack configuration in TypeScript
.
Webpack 提供了 Node.js API,可以在 Node.js 运行时下直接使用。
当你需要自定义构建或开发流程时,Node.js API 非常有用,因为此时所有的报告和错误处理都必须自行实现,webpack 仅仅负责编译的部分。所以 stats
配置选项不会在 webpack()
调用中生效。
开始使用 webpack 的 Node.js API 之前,首先你需要安装 webpack:
npm install --save-dev webpack
在 Node.js 文件中,引入 webpack 模块:
const webpack = require('webpack');
或者如果你更喜欢 ES2015:
import webpack from 'webpack';
导入的 webpack
函数会将 配置对象 传给 webpack,如果同时传入回调函数会在 webpack compiler 运行时被执行:
const webpack = require('webpack');
webpack({}, (err, stats) => {
if (err || stats.hasErrors()) {
// ...
}
// 处理完成
});
如果你不向 webpack
传入可执行的回调函数,
它会返回一个 webpack Compiler
实例。
你可以通过手动执行它或者为它的构建时添加一个监听器,
就像 CLI 类似。Compiler
实例提供了以下方法:
.run(callback)
.watch(watchOptions, handler)
通常情况下,仅会创建一个主要 Compiler
实例,
虽然可以创建一些子 compiler 来代理到特定任务。
Compiler
基本上只是执行最低限度的功能,以维持生命周期运行的功能。
它将所有的加载、打包和写入工作,
都委托到注册过的插件上。
Compiler
实例上的 hooks
属性,用于将一个插件注册
到 Compiler
的生命周期中的所有钩子事件上。
webpack
使用
WebpackOptionsDefaulter
和 WebpackOptionsApply
来配置 Compiler
实例以及所有内置插件。
使用 run
方法启动所有编译工作。
完成之后,执行传入的的 callback
函数。
最终记录下来的概括信息(stats)和错误(errors),都应在这个 callback 函数中获取。
调用 Compiler
实例的 run
方法跟上文提到的
快速执行方法很类似:
const webpack = require('webpack');
const compiler = webpack({
// ...
});
compiler.run((err, stats) => {
// ...
compiler.close((closeErr) => {
// ...
});
});
调用 watch
方法会触发 webpack 执行,但之后会监听变更(很像 CLI 命令: webpack --watch
),
一旦 webpack 检测到文件变更,就会重新执行编译。
该方法返回一个 Watching
实例。
watch(watchOptions, callback);
const webpack = require('webpack');
const compiler = webpack({
// ...
});
const watching = compiler.watch(
{
// 示例
aggregateTimeout: 300,
poll: undefined,
},
(err, stats) => {
// 这里打印 watch/build 结果...
console.log(stats);
}
);
Watching
配置选项的细节可以在
这里查询。
Watching
(Close Watching
)watch
方法返回一个 Watching
实例,该实例会暴露一个 .close(callback)
方法。
调用该方法将会结束监听:
watching.close((closeErr) => {
console.log('Watching Ended.');
});
Watching
使用 watching.invalidate
,你可以手动使当前编译循环(compiling round)无效,
而不会停止监听进程(process):
watching.invalidate();
stats
对象会被作为 webpack()
回调函数的第二个参数传递,
可以通过它获取到代码编译过程中的有用信息,
包括:
webpack CLI 正是基于这些信息在控制台 展示友好的格式输出。
stats
对象暴露了以下方法:
可以用来检查编译期是否有错误,
返回值为 true
或 false
。
可以用来检查编译期是否有警告,
返回值为 true
或 false
。
以 JSON 对象形式返回编译信息。
options
可以是一个字符串(预设值)或是颗粒化控制的对象:
stats.toJson('minimal');
stats.toJson({
assets: false,
hash: true,
});
所有可用的配置选项和预设值都可查询 stats 文档。
这里有
该函数输出的
示例。
以格式化的字符串形式返回描述编译信息 (类似 CLI 的输出)。
配置对象与 stats.toJson(options)
一致,除了额外增加的一个选项:
stats.toString({
// 增加控制台颜色开关
colors: true,
});
下面是 stats.toString()
用法的示例:
const webpack = require('webpack');
webpack(
{
// ...
},
(err, stats) => {
if (err) {
console.error(err);
return;
}
console.log(
stats.toString({
chunks: false, // 使构建过程更静默无输出
colors: true, // 在控制台展示颜色
})
);
}
);
MultiCompiler
模块可以让 webpack 同时执行多个配置。
如果传给 webpack 的 Node.js API 的 options
参数,
该参数由是由多个配置对象构成的数组,webpack 会相应地创建多个 compiler 实例,
并且在所有 compiler 执行完毕后调用 callback
方法。
var webpack = require('webpack');
webpack(
[
{ entry: './index1.js', output: { filename: 'bundle1.js' } },
{ entry: './index2.js', output: { filename: 'bundle2.js' } },
],
(err, stats) => {
process.stdout.write(stats.toString() + '\n');
}
);
完备的错误处理中需要考虑以下三种类型的错误:
下面是一个覆盖这些场景的示例:
const webpack = require('webpack');
webpack(
{
// ...
},
(err, stats) => {
if (err) {
console.error(err.stack || err);
if (err.details) {
console.error(err.details);
}
return;
}
const info = stats.toJson();
if (stats.hasErrors()) {
console.error(info.errors);
}
if (stats.hasWarnings()) {
console.warn(info.warnings);
}
// Log result...
}
);
默认情况下,webpack 使用普通文件系统来读取文件并将文件写入磁盘。
但是,还可以使用不同类型的文件系统(内存(memory), webDAV 等)来更改输入或输出行为。
为了实现这一点,
可以改变 inputFileSystem
或 outputFileSystem
。
例如,可以使用 memfs
替换默认的 outputFileSystem
,
以将文件写入到内存中,
而不是写入到磁盘:
const { createFsFromVolume, Volume } = require('memfs');
const webpack = require('webpack');
const fs = createFsFromVolume(new Volume());
const compiler = webpack({
/* options */
});
compiler.outputFileSystem = fs;
compiler.run((err, stats) => {
// 之后读取输出:
const content = fs.readFileSync('...');
compiler.close((closeErr) => {
// ...
});
});
值得一提的是,被 webpack-dev-server 及众多其他包依赖的 webpack-dev-middleware 就是通过这种方式, 将你的文件神秘地隐藏起来,但却仍然可以用它们为浏览器提供服务!
使用 webpack
编译源码时,用户可以生成一个包含模块统计信息的 JSON
文件。这些统计信息可以用来分析应用中的依赖关系图,从而优化 webpack
的编译速度。该文件通常由以下 CLI
命令生成:
npx webpack --profile --json=compilation-stats.json
--json=compilation-stats.json
标志告诉 webpack
生成一个包含依赖关系图和其他各种构建信息的 compilation-stats.json
文件。通常情况下,--profile
标志也会被添加,这样的话每个 module
objects 都会增加一个 profile
部分,它包含了特定模块的统计信息。
输出 JSON 文件的顶层结构相当简单,但是也包含部分嵌套的数据结构。为了让文档更易于使用,每个嵌套的数据结构都有对应的一小节来讲,注意,你可以点击如下的链接,跳转到相关章节查看文档:
{
"version": "5.9.0", // 用来编译的 webpack 版本
"hash": "11593e3b3ac85436984a", // 编译的特定哈希值
"time": 2469, // 编译时间(毫秒)
"publicPath": "auto",
"outputPath": "/", // webpack 的输出目录路径
"assetsByChunkName": {
// 输出资源对应的 Chunk 名称
"main": [
"web.js?h=11593e3b3ac85436984a"
],
"named-chunk": [
"named-chunk.web.js"
],
"other-chunk": [
"other-chunk.js",
"other-chunk.css"
]
},
"assets": [
// asset objects 列表
],
"chunks": [
// chunk objects 列表
],
"modules": [
// module objects 列表
],
"entryPoints": {
// entry objects 列表
},
"errors": [
// error objects 列表
],
"errorsCount": 0, // 错误个数
"warnings": [
// warning objects 列表
],
"warningsCount": 0, // 告警个数
}
每个 assets
对象表示编译过程中生成的 output
文件。它们都遵循类似的结构:
{
"chunkNames": [], // 该资源文件包含的 chunks
"chunks": [ 10, 6 ], // 该资源文件包含的 chunk ID
"comparedForEmit": false, // 指定是否对该资源文件和输出文件系统上相同文件进行比较
"emitted": true, // 指定资源文件是否要生成到 `output` 目录中
"name": "10.web.js", // `output` 文件名
"size": 1058, // 文件大小(字节为单位)
"info": {
"immutable": true, // 指定 asset 是否可以长期缓存的标志位(包括哈希值)
"size": 1058, // 单位大小为字节,只有在资源文件生成之后才可以使用
"development": true, // 指定 asset 是否只用于 development 环境,而不面向用户的标志位
"hotModuleReplacement": true, // 指定 asset 是否加载用于更新现有应用程序 (HMR) 的数据标志位
"sourceFilename": "originalfile.js", // 从源文件创建资产时(可能转换)sourceFilename
"javascriptModule": true // 当 asset 是 javascript 和 ESM 时为 true
}
}
每个 chunks
对象代表一组名为 chunk 的模块。每个对象都遵循如下结构:
{
"entry": true, // 指定 webpack 运行时是否包含 chunk
"files": [
// 包含 chunk 的文件名字符数组
],
"filteredModules": 0, // 查看关于 [top-level structure](#structure) 描述
"id": 0, // chunk 对应的 ID
"initial": true, // 指定 chunk 是在页面初始化时加载还是[按需加载](/guides/lazy-loading)
"modules": [
// [module objects](#module-objects) 列表
"web.js?h=11593e3b3ac85436984a"
],
"names": [
// 包含当前 chunk 的 chunk 名称列表
],
"origins": [
// 查看后面的描述...
],
"parents": [], // 父级 chunk ID
"rendered": true, // 指定 chunk 是否经过代码生成
"size": 188057 // chunk 大小,单位字节
}
chunks
对象还包含一个 origins
列表,它描述来给定的 chunk 每个 origins
对象都遵循以下模式:
{
"loc": "", // 生成当前 chunk 的代码行
"module": "(webpack)\\test\\browsertest\\lib\\index.web.js", // module的路径
"moduleId": 0, // module 对应的 ID
"moduleIdentifier": "(webpack)\\test\\browsertest\\lib\\index.web.js", // module 对应的路径
"moduleName": "./lib/index.web.js", // module对应的相对路径
"name": "main", // chunk 名称
"reasons": [
// 在 [module objects](#module-objects) 中找到相同的 `reason` 列表
]
}
假如不描述编译后的应用程序的实际模块,这些统计的数据有什么作用?其依赖关系图中的每个模块用如下结构表示:
{
"assets": [
// [asset objects](#asset-objects) 列表
],
"built": true, // 指定该模块经过 [Loaders](/concepts/loaders)、解析和代码生成
"cacheable": true, // 是否可以缓存
"chunks": [
// 当前模块包含的 chunk ID
],
"errors": 0, // 解析或处理模块时的错误个数
"failed": false, // 当前模块编译是否失败
"id": 0, // 模块 ID (类似于 [`module.id`](/api/module-variables/#moduleid-commonjs))
"identifier": "(webpack)\\test\\browsertest\\lib\\index.web.js", // 内部使用的唯一 ID
"name": "./lib/index.web.js", // 实际文件的路径
"optional": false, // 对当前模块的所有请求都带上 `try... catch` blocks (与 ESM 无关)
"prefetched": false, // 指定模块是否被 [prefetched](/plugins/prefetch-plugin)
"profile": {
// 对应于 [`--profile` 标志位](/api/cli/#profiling) 的模块特定编译统计(以毫秒为单位)
"building": 73, // 加载和解析
"dependencies": 242, // 构建依赖
"factory": 11 // 解析依赖关系
},
"reasons": [
// 查看下面的描述...
],
"size": 3593, // 预估模块大小,单位为字节
"source": "// 不要改变它...\r\nif(typeof...", // 字符串的原始源头
"warnings": 0 // 解析或处理模块时的警告数
}
每个模块还包含了一个 reasons
列表,它描述了为什么该模块会被包含在依赖关系图中。每个 reason
都类似于上面的 chunk objects 章节的 origins
:
{
"loc": "33:24-93", // 当前模块包含的代码行数
"module": "./lib/index.web.js", // 基于 [context](/configuration/entry-context/#context) 的模块相对路径
"moduleId": 0, // 模块对应的 ID
"moduleIdentifier": "(webpack)\\test\\browsertest\\lib\\index.web.js", // 模块对应路径
"moduleName": "./lib/index.web.js", // 可读性更高的模块名称
"type": "require.context", // 使用的 [请求类型](/api/module-methods)
"userRequest": "../../cases" // 用于 `import` 或者 `require` 请求的原始字符串
}
"main": {
"name": "main",
"chunks": [
179
],
"assets": [
{
"name": "main.js",
"size": 22
}
],
"filteredAssets": 0,
"assetsSize": 22,
"auxiliaryAssets": [],
"filteredAuxiliaryAssets": 0,
"auxiliaryAssetsSize": 0,
"children": {},
"childAssets": {},
"isOverSizeLimit": false
}
包含 errors
and warnings
属性的一个对象列表。每个对象包含一条消息,一个堆栈跟踪信息和其他各种属性:
{
"moduleIdentifier": "C:\\Repos\\webpack\\test\\cases\\context\\issue-5750\\index.js",
"moduleName": "(webpack)/test/cases/context/issue-5750/index.js",
"loc": "3:8-47",
"message": "Critical dependency: Contexts can't use RegExps with the 'g' or 'y' flags.",
"moduleId": 29595,
"moduleTrace": [
{
"originIdentifier": "C:\\Repos\\webpack\\test\\cases|sync|/^\\.\\/[^/]+\\/[^/]+\\/index\\.js$/",
"originName": "(webpack)/test/cases sync ^\\.\\/[^/]+\\/[^/]+\\/index\\.js$",
"moduleIdentifier": "C:\\Repos\\webpack\\test\\cases\\context\\issue-5750\\index.js",
"moduleName": "(webpack)/test/cases/context/issue-5750/index.js",
"dependencies": [
{
"loc": "./context/issue-5750/index.js"
}
],
"originId": 32582,
"moduleId": 29595
},
{
"originIdentifier": "C:\\Repos\\webpack\\testCases.js",
"originName": "(webpack)/testCases.js",
"moduleIdentifier": "C:\\Repos\\webpack\\test\\cases|sync|/^\\.\\/[^/]+\\/[^/]+\\/index\\.js$/",
"moduleName": "(webpack)/test/cases sync ^\\.\\/[^/]+\\/[^/]+\\/index\\.js$",
"dependencies": [
{
"loc": "1:0-70"
}
],
"originId": 8198,
"moduleId": 32582
}
],
"details": "at RequireContextDependency.getWarnings (C:\\Repos\\webpack\\lib\\dependencies\\ContextDependency.js:79:5)\n at Compilation.reportDependencyErrorsAndWarnings (C:\\Repos\\webpack\\lib\\Compilation.js:1727:24)\n at C:\\Repos\\webpack\\lib\\Compilation.js:1467:10\n at _next2 (<anonymous>:16:1)\n at eval (<anonymous>:42:1)\n at C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2830:7\n at Object.each (C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2850:39)\n at C:\\Repos\\webpack\\lib\\FlagDependencyExportsPlugin.js:219:18\n at C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2830:7\n at Object.each (C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2850:39)\n at C:\\Repos\\webpack\\lib\\FlagDependencyExportsPlugin.js:40:16\n at Hook.eval [as callAsync] (<anonymous>:38:1)\n at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (C:\\Repos\\tapable\\lib\\Hook.js:18:14)\n at Compilation.finish (C:\\Repos\\webpack\\lib\\Compilation.js:1462:28)\n at C:\\Repos\\webpack\\lib\\Compiler.js:909:18\n at processTicksAndRejections (internal/process/task_queues.js:75:11)\n",
"stack": "ModuleDependencyWarning: Critical dependency: Contexts can't use RegExps with the 'g' or 'y' flags.\n at Compilation.reportDependencyErrorsAndWarnings (C:\\Repos\\webpack\\lib\\Compilation.js:1732:23)\n at C:\\Repos\\webpack\\lib\\Compilation.js:1467:10\n at _next2 (<anonymous>:16:1)\n at eval (<anonymous>:42:1)\n at C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2830:7\n at Object.each (C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2850:39)\n at C:\\Repos\\webpack\\lib\\FlagDependencyExportsPlugin.js:219:18\n at C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2830:7\n at Object.each (C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2850:39)\n at C:\\Repos\\webpack\\lib\\FlagDependencyExportsPlugin.js:40:16\n at Hook.eval [as callAsync] (<anonymous>:38:1)\n at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (C:\\Repos\\tapable\\lib\\Hook.js:18:14)\n at Compilation.finish (C:\\Repos\\webpack\\lib\\Compilation.js:1462:28)\n at C:\\Repos\\webpack\\lib\\Compiler.js:909:18\n at processTicksAndRejections (internal/process/task_queues.js:75:11)\n"
}
webpack-dev-server
provides a Node.js API which can be used directly in Node.js runtime.
To start using the webpack-dev-server
Node.js API, first install webpack
and webpack-dev-server
if you haven’t yet:
npm install --save-dev webpack webpack-dev-server
Then require the modules in your Node.js script:
const Webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
It instructs webpack-dev-server
instance to start the server.
server.js
const Webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const webpackConfig = require('./webpack.config.js');
const compiler = Webpack(webpackConfig);
const devServerOptions = { ...webpackConfig.devServer, open: true };
const server = new WebpackDevServer(devServerOptions, compiler);
const runServer = async () => {
console.log('Starting server...');
await server.start();
};
runServer();
And then run the server with the following command:
node server.js
It instructs webpack-dev-server
instance to start the server and then run the callback function.
server.js
const Webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const webpackConfig = require('./webpack.config.js');
const compiler = Webpack(webpackConfig);
const devServerOptions = { ...webpackConfig.devServer, open: true };
const server = new WebpackDevServer(devServerOptions, compiler);
server.startCallback(() => {
console.log('Successfully started server on http://localhost:8080');
});
And then run the server with the following command:
node server.js
It instructs webpack-dev-server
instance to stop the server.
server.js
const Webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const webpackConfig = require('./webpack.config.js');
const compiler = Webpack(webpackConfig);
const devServerOptions = { ...webpackConfig.devServer, open: true };
const server = new WebpackDevServer(devServerOptions, compiler);
const runServer = async () => {
console.log('Starting server...');
await server.start();
};
const stopServer = async () => {
console.log('Stopping server...');
await server.stop();
};
runServer();
setTimeout(stopServer, 5000);
And then run the server with the following command:
node server.js
It instructs webpack-dev-server
instance to stop the server and then run the callback function.
server.js
const Webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const webpackConfig = require('./webpack.config.js');
const compiler = Webpack(webpackConfig);
const devServerOptions = { ...webpackConfig.devServer, open: true };
const server = new WebpackDevServer(devServerOptions, compiler);
server.startCallback(() => {
console.log('Successfully started server on http://localhost:8080');
});
const stopServer = () =>
server.stopCallback(() => {
console.log('Server stopped.');
});
setTimeout(stopServer, 5000);
And then run the server with the following command:
node server.js
Returns the internal IPv4
/IPv6
address asynchronously.
server.js
const WebpackDevServer = require('webpack-dev-server');
const logInternalIPs = async () => {
const localIPv4 = await WebpackDevServer.internalIP('v4');
const localIPv6 = await WebpackDevServer.internalIP('v6');
console.log('Local IPv4 address:', localIPv4);
console.log('Local IPv6 address:', localIPv6);
};
logInternalIPs();
Returns the internal IPv4
/IPv6
address synchronously.
server.js
const WebpackDevServer = require('webpack-dev-server');
const localIPv4 = WebpackDevServer.internalIPSync('v4');
const localIPv6 = WebpackDevServer.internalIPSync('v6');
console.log('Local IPv4 address:', localIPv4);
console.log('Local IPv6 address:', localIPv6);
如果已经通过 HotModuleReplacementPlugin
启用了 Hot Module Replacement, 则它的接口将被暴露在 module.hot
以及 import.meta.webpackHot
属性下。请注意,只有 import.meta.webpackHot
可以在 strict ESM 中使用。
通常,用户先要检查这个接口是否可访问, 再使用它。你可以这样使用 accept
操作一个更新的模块:
if (module.hot) {
module.hot.accept('./library.js', function () {
// 对更新过的 library 模块做些事情...
});
}
// or
if (import.meta.webpackHot) {
import.meta.webpackHot.accept('./library.js', function () {
// Do something with the updated library module…
});
}
支持以下方法……
接受(accept)给定 依赖模块(dependencies)
的更新,并触发一个 回调函数
来响应更新,除此之外,你可以附加一个可选的 error 处理程序:
module.hot.accept(
dependencies, // 可以是一个字符串或字符串数组
callback // 用于在模块更新后触发的函数
errorHandler // (err, {moduleId, dependencyId}) => {}
);
// or
import.meta.webpackHot.accept(
dependencies, // 可以是一个字符串或字符串数组
callback, // 用于在模块更新后触发的函数
errorHandler // (err, {moduleId, dependencyId}) => {}
);
当使用 ESM import
时,所有从 dependencies
中导入的符号都会自动更新。注意:依赖项字符串必须与 import
中的 from
字符串完全匹配。在某些情况下, 甚至可以省略 callback
。在 callback
中使用的 require()
在这里没有任何意义。
在使用 CommonJS 时,你应该通过 callback
中的 require()
手动更新依赖模块。省略 callback
在这里没有任何意义。
(err, {moduleId, dependencyId}) => {}
err
: 当使用 ESM 依赖项时,回调函数在第二个参数中或在依赖项执行期间抛出的错误。moduleId
: 当前模块 id。dependencyId
: (第一个)被更改依赖项的模块 id。接受自身更新。
module.hot.accept(
errorHandler // 在计算新版本时处理错误的函数
);
// or
import.meta.webpackHot.accept(
errorHandler // Function to handle errors when evaluating the new version
);
在此模块或依赖模块更新时,可以在不通知父依赖的情况下,对此模块处理和重新取值。如果此模块没有导出(或以其他方式更新的导出),这是有意义的。
当执行此模块(或依赖模块)抛出异常时,会触发 errorHandler
。
(err, {moduleId, module}) => {}
err
: 计算新版本时的错误。moduleId
: 当前模块 id。module
: 当前模块实例。module.hot
: 允许访问出错模块实例的 HMR API。一个常见的场景是再次自我接收(accept)。添加一个 dispose 处理程序来传递数据也是有意义的。注意,错误的模块可能已经部分执行,所以请确保不要进入不一致的状态。你可以使用 module.hot.data
存储部分状态。module.exports
: 可以被重载,但是要小心,因为属性名在生产模式下可能会被破坏。拒绝给定依赖模块
的更新,使用 'decline'
方法强制更新失败。
module.hot.decline(
dependencies // 可以是一个字符串或字符串数组
);
// or
import.meta.webpackHot.decline(
dependencies // Either a string or an array of strings
);
将依赖模块标记为不可更新(not-update-able)。在处理「依赖的导出正在更新」或「尚未实现处理」时,这是有意义的。取决于你的 HMR 管理代码, 此依赖模块(或其未接受的依赖模块)更新,通常会导致页面被完全重新加载。
拒绝自身更新。
module.hot.decline();
// or
import.meta.webpackHot.decline();
将依赖模块标记为不可更新(not-update-able)。当此模块具有无法避免的外部作用(side-effect),或者尚未对此模块进行 HMR 处理时,这是有意义的。取决于你的 HMR 管理代码,此依赖模块(或其未接受的依赖模块)更新,通常会导致页面被完全重新加载。
添加一个处理函数,在当前模块代码被替换时执行。此函数应该用于移除你声明或创建的任何持久资源。如果要将状态传入到更新过的模块,请添加给定 data
参数。更新后,此对象在更新之后可通过 module.hot.data
调用。
module.hot.dispose((data) => {
// 清理并将 data 传递到更新后的模块...
});
// or
import.meta.webpackHot.dispose((data) => {
// Clean up and pass data to the updated module...
});
调用此方法将使当前模块无效,而当前模块将在应用 HMR 更新时进行部署并重新创建。这个模块的更新像冒泡一样,拒绝自身更新。
在 idle
状态下调用时,将创建一个包含此模块的新 HMR 更新。HMR 将进入 ready
状态。
在 ready
或 prepare
状态下调用时,此模块将添加到当前 HMR 的更新中。
在 check
状态期间被调用时,如果有可用更新,则此模块将添加到更新中。如果没有可用的更新,它将创建一个新更新。HMR 将进入 ready
状态。
在 dispose
或 apply
状态下调用时,HMR 将在退出这些状态后将其拾取。
Conditional Accepting
一个模块可以接受一个依赖,但是当依赖的改变无法处理时,可以调用 invalidate
:
import { x, y } from './dep';
import { processX, processY } from 'anotherDep';
const oldY = y;
processX(x);
export default processY(y);
module.hot.accept('./dep', () => {
if (y !== oldY) {
// 无法处理,冒泡给父级
module.hot.invalidate();
return;
}
// 可以处理
processX(x);
});
Conditional self accept
模块可以自我接受,但是当更改无法处理时可以使自身失效:
const VALUE = 'constant';
export default VALUE;
if (
module.hot.data &&
module.hot.data.value &&
module.hot.data.value !== VALUE
) {
module.hot.invalidate();
} else {
module.hot.dispose((data) => {
data.value = VALUE;
});
module.hot.accept();
}
Triggering custom HMR updates
const moduleId = chooseAModule();
const code = __webpack_modules__[moduleId].toString();
__webpack_modules__[moduleId] = eval(`(${makeChanges(code)})`);
if (require.cache[moduleId]) {
require.cache[moduleId].hot.invalidate();
module.hot.apply();
}
删除由 dispose
或 addDisposeHandler
添加的回调函数。
module.hot.removeDisposeHandler(callback);
// or
import.meta.webpackHot.removeDisposeHandler(callback);
获取当前模块热替换进程的状态。
module.hot.status(); // 返回以下字符串之一...
// 或者
import.meta.webpackHot.status();
Status | Description |
---|---|
idle | 该进程正在等待调用 check (见下文) |
check | 该进程正在检查以更新 |
prepare | 该进程正在准备更新(例如,下载已更新的模块) |
ready | 此更新已准备并可用 |
dispose | 该进程正在调用将被替换模块的 dispose 处理函数 |
apply | 该进程正在调用 accept 处理函数,并重新执行自我接受(self-accepted)的模块 |
abort | 更新已中止,但系统仍处于之前的状态 |
fail | 更新已抛出异常,系统状态已被破坏 |
测试所有加载的模块以进行更新,如果有更新,则 apply
它们。
module.hot
.check(autoApply)
.then((outdatedModules) => {
// 超时的模块...
})
.catch((error) => {
// 捕获错误
});
// or
import.meta.webpackHot
.check(autoApply)
.then((outdatedModules) => {
// outdated modules...
})
.catch((error) => {
// catch errors
});
当被调用时,传递给 apply
方法的 autoApply
参数可以是布尔值,也可以是 options
,
继续更新进程(当 module.hot.status() === 'ready'
时)。
module.hot
.apply(options)
.then((outdatedModules) => {
// 超时的模块...
})
.catch((error) => {
// 捕获错误
});
// or
import.meta.webpackHot
.apply(options)
.then((outdatedModules) => {
// outdated modules...
})
.catch((error) => {
// catch errors
});
可选的 options
对象可以包含以下属性:
ignoreUnaccepted
(boolean): 忽略对不可接受的模块所做的更改。ignoreDeclined
(boolean): 忽略对已拒绝的模块所做的更改。ignoreErrored
(boolean): 忽略在接受处理程序、错误处理程序以及重新评估模块时抛出的错误。onDeclined
(function(info)): 拒绝模块的通知者。onUnaccepted
(function(info)): 不可接受的模块的通知程序。onAccepted
(function(info)): 可接受模块的通知者。onDisposed
(function(info)): 废弃模块的通知者。onErrored
(function(info)): 错误通知者。info
参数将是一个包含以下某些值的对象:
{
type: 'self-declined' | 'declined' |
'unaccepted' | 'accepted' |
'disposed' | 'accept-errored' |
'self-accept-errored' | 'self-accept-error-handler-errored',
moduleId: 4, // 有问题的模块。
dependencyId: 3, // 对于错误:拥有接受处理程序的模块 ID。
chain: [1, 2, 3, 4], // 对于拒绝/接受/不接受:传播更新的 `chain`。
parentId: 5, // 对于拒绝:下降的父模块 ID。
outdatedModules: [1, 2, 3, 4], // 对于接受:已过时且将被处置的模块。
outdatedDependencies: { // 对于接受:将处理更新的接受处理程序的位置。
5: [4]
},
error: new Error(...), // 对于错误:抛出错误
originalError: new Error(...) // 对于自我接受错误处理程序错误:
// 在错误处理程序尝试处理该模块之前,该模块引发的错误。
}
注册一个函数来监听 status
的变化。
module.hot.addStatusHandler((status) => {
// 响应当前状态...
});
// or
import.meta.webpackHot.addStatusHandler((status) => {
// React to the current status...
});
请记住,当 status 处理函数返回一个 Promise
时,HMR 系统将会在继续之前等待 Promise
resolve。
移除一个注册的状态处理函数。
module.hot.removeStatusHandler(callback);
// or
import.meta.webpackHot.removeStatusHandler(callback);
loader 本质上是导出为函数的 JavaScript 模块。loader runner 会调用此函数,然后将上一个 loader 产生的结果或者资源文件传入进去。函数中的 this
作为上下文会被 webpack 填充,并且 loader runner 中包含一些实用的方法,比如可以使 loader 调用方式变为异步,或者获取 query 参数。
起始 loader 只有一个入参:资源文件的内容。compiler 预期得到最后一个 loader 产生的处理结果。这个处理结果应该为 String
或者 Buffer
(能够被转换为 string)类型,代表了模块的 JavaScript 源码。另外,还可以传递一个可选的 SourceMap 结果(格式为 JSON 对象)。
如果是单个处理结果,可以在 同步模式 中直接返回。如果有多个处理结果,则必须调用 this.callback()
。在 异步模式 中,必须调用 this.async()
来告知 loader runner 等待异步结果,它会返回 this.callback()
回调函数。随后 loader 必须返回 undefined
并且调用该回调函数。
/**
*
* @param {string|Buffer} content 源文件的内容
* @param {object} [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
* @param {any} [meta] meta 数据,可以是任何内容
*/
function webpackLoader(content, map, meta) {
// 你的 webpack loader 代码
}
以下部分提供了不同类型的 loader 的一些基本示例。注意,map
和 meta
参数是可选的,查看下面的 this.callback
。
无论是 return
还是 this.callback
都可以同步地返回转换后的 content
值:
sync-loader.js
module.exports = function (content, map, meta) {
return someSyncOperation(content);
};
this.callback
方法则更灵活,因为它允许传递多个参数,而不仅仅是 content
。
sync-loader-with-multiple-results.js
module.exports = function (content, map, meta) {
this.callback(null, someSyncOperation(content), map, meta);
return; // 当调用 callback() 函数时,总是返回 undefined
};
对于异步 loader,使用 this.async
来获取 callback
函数:
async-loader.js
module.exports = function (content, map, meta) {
var callback = this.async();
someAsyncOperation(content, function (err, result) {
if (err) return callback(err);
callback(null, result, map, meta);
});
};
async-loader-with-multiple-results.js
module.exports = function (content, map, meta) {
var callback = this.async();
someAsyncOperation(content, function (err, result, sourceMaps, meta) {
if (err) return callback(err);
callback(null, result, sourceMaps, meta);
});
};
默认情况下,资源文件会被转化为 UTF-8 字符串,然后传给 loader。通过设置 raw
为 true
,loader 可以接收原始的 Buffer
。每一个 loader 都可以用 String
或者 Buffer
的形式传递它的处理结果。complier 将会把它们在 loader 之间相互转换。
raw-loader.js
module.exports = function (content) {
assert(content instanceof Buffer);
return someSyncOperation(content);
// 返回值也可以是一个 `Buffer`
// 即使不是 "raw",loader 也没问题
};
module.exports.raw = true;
loader 总是 从右到左被调用。有些情况下,loader 只关心 request 后面的 元数据(metadata),并且忽略前一个 loader 的结果。在实际(从右到左)执行 loader 之前,会先 从左到右 调用 loader 上的 pitch
方法。
对于以下 use
配置:
module.exports = {
//...
module: {
rules: [
{
//...
use: ['a-loader', 'b-loader', 'c-loader'],
},
],
},
};
将会发生这些步骤:
|- a-loader `pitch`
|- b-loader `pitch`
|- c-loader `pitch`
|- requested module is picked up as a dependency
|- c-loader normal execution
|- b-loader normal execution
|- a-loader normal execution
那么,为什么 loader 可以利用 "pitching" 阶段呢?
首先,传递给 pitch
方法的 data
,在执行阶段也会暴露在 this.data
之下,并且可以用于在循环时,捕获并共享前面的信息。
module.exports = function (content) {
return someSyncOperation(content, this.data.value);
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
data.value = 42;
};
其次,如果某个 loader 在 pitch
方法中给出一个结果,那么这个过程会回过身来,并跳过剩下的 loader。在我们上面的例子中,如果 b-loader
的 pitch
方法返回了一些东西:
module.exports = function (content) {
return someSyncOperation(content);
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
if (someCondition()) {
return (
'module.exports = require(' +
JSON.stringify('-!' + remainingRequest) +
');'
);
}
};
上面的步骤将被缩短为:
|- a-loader `pitch`
|- b-loader `pitch` returns a module
|- a-loader normal execution
loader context 表示在 loader 内使用 this
可以访问的一些方法或属性。
下面提供一个例子,将使用 require 进行调用:
在 /abc/file.js
中:
require('./loader1?xyz!loader2!./resource?rrr');
addContextDependency(directory: string)
添加目录作为 loader 结果的依赖。
addDependency(file: string)
dependency(file: string) // shortcut
添加一个文件作为产生 loader 结果的依赖,使它们的任何变化可以被监听到。例如,sass-loader
, less-loader
就使用了这个技巧,当它发现无论何时导入的 css
文件发生变化时就会重新编译。
addMissingDependency(file: string)
添加一个不存在的文件作为 loader 结果的依赖项,以使它们可监听。类似于 addDependency
,但是会在正确附加观察者之前处理在编译期间文件的创建。
告诉 loader-runner 这个 loader 将会异步地回调。返回 this.callback
。
设置是否可缓存标志的函数:
cacheable(flag = true: boolean)
默认情况下,loader 的处理结果会被标记为可缓存。调用这个方法然后传入 false
,可以关闭 loader 处理结果的缓存能力。
一个可缓存的 loader 在输入和相关依赖没有变化时,必须返回相同的结果。这意味着 loader 除了 this.addDependency
里指定的以外,不应该有其它任何外部依赖。
可以同步或者异步调用的并返回多个结果的函数。预期的参数是:
this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any
);
Error
或者 null
string
或者 Buffer
。如果这个函数被调用的话,你应该返回 undefined 从而避免含糊的 loader 结果。
clearDependencies();
移除 loader 结果的所有依赖,甚至自己和其它 loader 的初始依赖。考虑使用 pitch
。
模块所在的目录 可以用作解析其他模块成员的上下文。
在我们的 例子 中:因为 resource.js
在这个目录中,这个属性的值为 /abc
在 pitch 阶段和 normal 阶段之间共享的 data 对象。
emitError(error: Error)
emit 一个错误,也可以在输出中显示。
ERROR in ./src/lib.js (./src/loader.js!./src/lib.js)
Module Error (from ./src/loader.js):
Here is an Error!
@ ./src/index.js 1:0-25
emitFile(name: string, content: Buffer|string, sourceMap: {...})
产生一个文件。这是 webpack 特有的。
emitWarning(warning: Error)
发出一个警告,在输出中显示如下:
WARNING in ./src/lib.js (./src/loader.js!./src/lib.js)
Module Warning (from ./src/loader.js):
Here is a Warning!
@ ./src/index.js 1:0-25
用于访问 compilation 的 inputFileSystem 属性。
提取给定的 loader 选项,接受一个可选的 JSON schema 作为参数
getResolve(options: ResolveOptions): resolve
resolve(context: string, request: string, callback: function(err, result: string))
resolve(context: string, request: string): Promise<string>
创建一个类似于 this.resolve
的解析函数。
在 webpack resolve
选项 下的任意配置项都是可能的。他们会被合并进 resolve
配置项中。请注意,"..."
可以在数组中使用,用于拓展 resolve
配置项的值。例如:{ extensions: [".sass", "..."] }
。
options.dependencyType
是一个额外的配置。它允许我们指定依赖类型,用于从 resolve
配置项中解析 byDependency
。
解析操作的所有依赖项都会自动作为依赖项添加到当前模块中。
loaders 的 HMR(模块热替换)相关信息。
module.exports = function (source) {
console.log(this.hot); // true if HMR is enabled via --hot flag or webpack configuration
return source;
};
this.importModule(request, options, [callback]): Promise
一种可以选择的轻量级解决方案,用于子编译器在构建时编译和执行请求。
request
: 加载模块的请求字符串options
:layer
:指定该模块放置/编译的层publicPath
:用于构建模块的公共路径callback
:一个可选的 Node.js 风格的回调,返回模块的 exports 或 ESM 的命名空间对象。如果没有提供回调,importModule
将返回一个 Promise。webpack.config.js
module.exports = {
module: {
rules: [
{
test: /stylesheet\.js$/i,
use: ['./a-pitching-loader.js'],
type: 'asset/source', // 我们将 type 设置为 'asset/source',其会返回一个字符串。
},
],
},
};
a-pitching-loader.js
exports.pitch = async function (remaining) {
const result = await this.importModule(
this.resourcePath + '.webpack[javascript/auto]' + '!=!' + remaining
);
return result.default || result;
};
src/stylesheet.js
import { green, red } from './colors.js';
export default `body { background: ${red}; color: ${green}; }`;
src/colors.js
export const red = '#f00';
export const green = '#0f0';
src/index.js
import stylesheet from './stylesheet.js';
// stylesheet 在构建时会成为一个字符串:`body { background: #f00; color: #0f0; }`。
在上面的例子中你可能会注意到一些东西:
!=!
语法来为请求设置 matchResource,例如,我们将使用 this.resourcePath + '.webpack[javascript/auto]'
而不是原始资源匹配 module.rules
,.webpack[javascript/auto]
是 .webpack[type]
模式的伪拓展,当没有指定其他模块类型时,我们使用它指定一个默认 模块类型,它通常和 !=!
语法一起使用。注意,上面的示例是一个简化的示例,你可以查看 webpack 仓库的完整示例。
当前 loader 在 loader 数组中的索引。
在示例中:loader1 中得到:0
,loader2 中得到:1
loadModule(request: string, callback: function(err, source, sourceMap, module))
解析给定的 request 到模块,应用所有配置的 loader,并且在回调函数中传入生成的 source、sourceMap 和模块实例(通常是 NormalModule
的一个实例)。如果你需要获取其他模块的源代码来生成结果的话,你可以使用这个函数。
this.loadModule
在 loader 上下文中默认使用 CommonJS 来解析规则。用一个合适的 dependencyType
使用 this.getResolve
。例如,在使用不同的语义之前使用 'esm'
、'commonjs'
或者一个自定义的。
所有 loader 组成的数组。它在 pitch 阶段的时候是可以写入的。
loaders = [{request: string, path: string, query: string, module: function}]
在此示例中:
[
{
request: '/abc/loader1.js?xyz',
path: '/abc/loader1.js',
query: '?xyz',
module: [Function],
},
{
request: '/abc/node_modules/loader2/index.js',
path: '/abc/node_modules/loader2/index.js',
query: '',
module: [Function],
},
];
当 webpack 运行时读取 mode
的值
可能的值为:'production'
, 'development'
, 'none'
options
对象的话,this 就指向这个对象。options
,而是以 query 字符串作为参数调用时,this.query 就是一个以 ?
开头的字符串。被解析出来的 request 字符串。
在我们的示例中:'/abc/loader1.js?xyz!/abc/node_modules/loader2/index.js!/abc/resource.js?rrr'
resolve(context: string, request: string, callback: function(err, result: string))
像 require 表达式一样解析一个 request。
context
必须是一个目录的绝对路径。此目录用作解析的起始位置。request
是要被解析的 request。通常情况下,像 ./relative
的相对请求或者像 module/path
的模块请求会被使用,但是像 /some/path
也有可能被当做 request。callback
是一个给出解析路径的 Node.js 风格的回调函数。解析操作的所有依赖项都会自动作为依赖项添加到当前模块中。
request 中的资源部分,包括 query 参数。
在示例中:'/abc/resource.js?rrr'
资源文件的路径。
在【示例](#example-for-the-loader-context)中:'/abc/resource.js'
资源的 query 参数。
在示例中:'?rrr'
从 webpack 4 开始,原先的 this.options.context
被改为 this.rootContext
。
是否应该生成一个 source map。因为生成 source map 可能会非常耗时,你应该确认 source map 确实需要。
compilation 的目标。从配置选项中传递。
示例:'web'
, 'node'
可以访问 contextify
与 absolutify
功能。
contextify
: 返回一个新的请求字符串,尽可能避免使用绝对路径。absolutify
: 尽可能使用相对路径返回一个新的请求字符串。my-sync-loader.js
module.exports = function (content) {
this.utils.contextify(
this.context,
this.utils.absolutify(this.context, './index.js')
);
this.utils.absolutify(this.context, this.resourcePath);
// …
return content;
};
loader API 的版本号 目前是 2
。这对于向后兼容性有一些用处。通过这个版本号,你可以自定义逻辑或者降级处理。
如果是由 webpack 编译的,这个布尔值会被设置为 true。
loader 接口提供所有模块的相关信息。然而,在极少数情况下,你可能需要访问 compiler api 本身。
因此,你应该把它们作为最后的手段。使用它们将降低 loader 的可移植性。
用于访问 webpack 的当前 Compilation 对象。
用于访问 webpack 的当前 Compiler 对象。
由于我们计划将这些属性从上下文中移除,因此不鼓励使用这些属性。它们仍然列在这里,以备参考。
一个布尔值,当处于 debug 模式时为 true。
从上一个 loader 那里传递过来的值。如果你会以模块的方式处理输入参数,建议预先读入这个变量(为了性能因素)。
决定处理结果是否应该被压缩。
向下一个 loader 传值。如果你知道了作为模块执行后的结果,请在这里赋值(以元素数组的形式)。
一种 hack 写法。用于访问当前加载的 Module 对象。
您可以通过以下方式从 loader 内部报告错误:
throw
(或其他未捕获的意外异常)。loader 运行时引发错误将导致当前模块编译失败。callback
(异步模式)。向回调传递错误也会导致模块编译失败。示例:
./src/index.js
require('./loader!./lib');
从 loader 当中抛出错误:
./src/loader.js
module.exports = function (source) {
throw new Error('This is a Fatal Error!');
};
或者在异步模式下,传入一个错误给 callback:
./src/loader.js
module.exports = function (source) {
const callback = this.async();
//...
callback(new Error('This is a Fatal Error!'), source);
};
这个模块将获取像下面的 bundle:
/***/ "./src/loader.js!./src/lib.js":
/*!************************************!*\
!*** ./src/loader.js!./src/lib.js ***!
\************************************/
/*! no static exports found */
/***/ (function(module, exports) {
throw new Error("Module build failed (from ./src/loader.js):\nError: This is a Fatal Error!\n at Object.module.exports (/workspace/src/loader.js:3:9)");
/***/ })
然后构建输出结果将显示错误,与 this.emitError
相似:
ERROR in ./src/lib.js (./src/loader.js!./src/lib.js)
Module build failed (from ./src/loader.js):
Error: This is a Fatal Error!
at Object.module.exports (/workspace/src/loader.js:2:9)
@ ./src/index.js 1:0-25
如下所示,不仅有错误消息,还提供了有关所涉及的 loader 和模块的详细信息:
ERROR in ./src/lib.js
(./src/loader.js!./src/lib.js)
(from ./src/loader.js)
@ ./src/index.js 1:0-25
在 webpack v4 中引入了一种新的内联请求语法。前缀为 <match-resource>!=!
将为此请求设置 matchResource
。
当 matchResource
被设置时,它将会被用作匹配 module.rules
而不是源文件。如果需要对资源应用进一步的 loader,或者需要更改模块类型,这可能会很有用。 它也显示在统计数据中,用于匹配 Rule.issuer
和 test
in splitChunks
。
示例:
file.js
/* STYLE: body { background: red; } */
console.log('yep');
loader 可以将文件转换为以下文件,并使用 matchResource
应用用户指定的 CSS 处理规则:
file.js (transformed by loader)
import './file.js.css!=!extract-style-loader/getStyles!./file.js';
console.log('yep');
这将会向 extract-style-loader/getStyles!./file.js
中添加一个依赖,并将结果视为 file.js.css
。因为 module.rules
有一条匹配 /\.css$/
的规则,并且将会应用到依赖中。
这个 loader 就像是这样:
extract-style-loader/index.js
const getStylesLoader = require.resolve('./getStyles');
module.exports = function (source) {
if (STYLES_REGEXP.test(source)) {
source = source.replace(STYLES_REGEXP, '');
return `import ${JSON.stringify(
this.utils.contextify(
this.context || this.rootContext,
`${this.resource}.css!=!${getStylesLoader}!${this.remainingRequest}`
)
)};${source}`;
}
return source;
};
extract-style-loader/getStyles.js
module.exports = function (source) {
const match = source.match(STYLES_REGEXP);
return match[0];
};
自 webpack 4.37 发布以来,Logging API 就可用了。当 stats configuration
或者 infrastructure logging
中启用 logging
时,loader 可以记录消息,这些消息将以相应的日志格式(stats,infrastructure)打印出来。
this.getLogger()
进行日志记录,这是指向 compilation.getLogger()
具有 loader 路径和已处理的文件。这种日志记录被存储到 Stats 中并相应地格式化。它可以被 webpack 用户过滤和导出。this.getLogger('name')
获取具有子名称的独立记录器。仍会添加 loader 路径和已处理的文件。this.getLogger() ? this.getLogger() : console
。在使用不支持 getLogger
方法的旧 webpack 版本时提供回退方法。使用 Logger 输出消息是一种向用户展示信息的有效方式。
Webpack Logger 可以用在 loader 和 plugin。生成的 Logger 将作为 统计信息 的一部分进行输出,并且用户可以在 webpack 配置文件 中对 Logger 进行配置。
在 Webpack 中使用自定义 Logger API 的优点:
stats.json
的一部分输出通过引入 Webpack Logger API,我们希望统一 Webpack plugins 和 loaders 生成日志的方式,并提供更好的方法来检查构建问题。 集成的 Logging 解决方案可以帮助 plugins 和 loader 的开发人员提升他们的开发经验。同时为非 CLI 形式的 Webpack 解决方案构建铺平了道路,例如 dashboard 或其他 UI。
my-webpack-plugin.js
const PLUGIN_NAME = 'my-webpack-plugin';
export class MyWebpackPlugin {
apply(compiler) {
// you can access Logger from compiler
const logger = compiler.getInfrastructureLogger(PLUGIN_NAME);
logger.log('log from compiler');
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
// you can also access Logger from compilation
const logger = compilation.getLogger(PLUGIN_NAME);
logger.info('log from compilation');
});
}
}
my-webpack-loader.js
module.exports = function (source) {
// you can get Logger with `this.getLogger` in your webpack loaders
const logger = this.getLogger('my-webpack-loader');
logger.info('hello Logger');
return source;
};
从上面的 my-webpack-plugin.js
例子中你可以看到,有两种打印日志的方法,
compilation.getLogger
compiler.getInfrastructureLogger
当 plugin/logging 与编译相关时,建议使用 compilation.getLogger
,它们将存储在 stats 中。对于在编译周期之外发生的日志记录,请改用 compiler.getInfrastructureLogger
。
logger.error(...)
:用于输出错误信息logger.warn(...)
:用于输出警告信息logger.info(...)
:用于输出重要信息。默认情况下会显示这些信息,所以仅用于输出用户真正需要查看的消息logger.log(...)
:用于输出不重要的信息。只有当用户选择查看时,才会显示logger.debug(...)
:用于输出调试信息。只有当用户选择查看特定模块的调试日志时,才会显示logger.trace()
:显示堆栈跟踪信息,展示形式类似于 logger.debug
logger.group(...)
:将消息进行分组,展示形式类似于 logger.log
logger.groupEnd()
:结束消息分组logger.groupCollapsed(...)
:将消息进行分组。默认显示为折叠 logger.log
日志,当日志记录级别设置为 'verbose'
或 'debug'
时,显示展开的日志logger.status
:写入一条临时消息,并且设置新状态,覆盖上一个状态logger.clear()
:打印水平线。展示形式类似于 logger.log
logger.profile(...)
,logger.profileEnd(...)
:捕获配置文件。当支持 console.profile
API 时,使用其进行输出Runtime logger API 仅应该用作开发工具,不应该包含在 生产模式中。
const logging = require('webpack/lib/logging/runtime')
:直接从 Webpack 中引入即可使用 logger APIlogging.getLogger('name')
:根据名称获取一个 logger 的实例logging.configureDefaultLogger(...)
:重写 logger 的默认配置const logging = require('webpack/lib/logging/runtime');
logging.configureDefaultLogger({
level: 'log',
debug: /something/,
});
logging.hooks.log
:向 logger 中添加 Plugins本章节涵盖了使用 webpack 编译代码的所有方法。在 webpack 打包应用程序时,你可以选择各种模块语法风格,包括 ES6,CommonJS 和 AMD。
尽管 webpack 支持多种模块语法,但我们还是建议尽量使用一致的语法,以此避免一些奇怪的行为和 bug。事实上,当距离最近的 package.json
文件中包含值为 "module"
或 "commonjs"
的 "type"
字段时,webpack 会启用语法一致性检查。请大家在阅读前,注意此情况:
package.json
中为 .mjs
或 .js
设置 "type": "module"
require
,module.exports
或 exports
import './src/App.mjs'
,而非 import './src/App'
(你可以通过设置 Rule.resolve.fullySpecified
来禁用此规则)package.json
中为 .cjs
或 .js
设置 "type": "commonjs"
import
和 export
均不可用package.json
中为 .wasm
设置 "type": "module"
webpack 2 支持原生的 ES6 模块语法,意味着你无须额外引入 babel 这样的工具,就可以使用 import
和 export
。但是注意,如果使用其他的 ES6+ 特性,仍然需要引入 babel。webpack 支持以下的方法:
通过 import
以静态的方式导入另一个通过 export
导出的模块。
import MyModule from './my-module.js';
import { NamedExport } from './other-module.js';
你也通过 import
来引入 Data URI:
import 'data:text/javascript;charset=utf-8;base64,Y29uc29sZS5sb2coJ2lubGluZSAxJyk7';
import {
number,
fn,
} from 'data:text/javascript;charset=utf-8;base64,ZXhwb3J0IGNvbnN0IG51bWJlciA9IDQyOwpleHBvcnQgY29uc3QgZm4gPSAoKSA9PiAiSGVsbG8gd29ybGQiOw==';
默认
导出整个模块或具名导出模块。
// 具名导出
export var Count = 5;
export function Multiply(a, b) {
return a * b;
}
// 默认导出
export default {
// Some data...
};
function(string path):Promise
动态的加载模块。调用 import
的之处,被视为分割点,意思是,被请求的模块和它引用的所有子模块,会分割到一个单独的 chunk 中。
if (module.hot) {
import('lodash').then((_) => {
// Do something with lodash (a.k.a '_')...
});
}
不能使用完全动态的 import 语句,例如 import(foo)
。是因为 foo
可能是系统或项目中任何文件的任何路径。
import()
必须至少包含一些关于模块的路径信息。打包可以限定于一个特定的目录或文件集,以便于在使用动态表达式时 - 包括可能在 import()
调用中请求的每个模块。例如, import(`./locale/${language}.json`)
会把 .locale
目录中的每个 .json
文件打包到新的 chunk 中。在运行时,计算完变量 language
后,就可以使用像 english.json
或 german.json
的任何文件。
// 想象我们有一个从 cookies 或其他存储中获取语言的方法
const language = detectVisitorLanguage();
import(`./locale/${language}.json`).then((module) => {
// do something with the translations
});
内联注释使这一特性得以实现。通过在 import 中添加注释,我们可以进行诸如给 chunk 命名或选择不同模式的操作。
// 单个目标
import(
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
/* webpackExports: ["default", "named"] */
'module'
);
// 多个可能的目标
import(
/* webpackInclude: /\.json$/ */
/* webpackExclude: /\.noimport\.json$/ */
/* webpackChunkName: "my-chunk-name" */
/* webpackMode: "lazy" */
/* webpackPrefetch: true */
/* webpackPreload: true */
`./locale/${language}`
);
import(/* webpackIgnore: true */ 'ignored-module.js');
webpackIgnore
:设置为 true 时,禁用动态导入解析。
webpackChunkName
: 新 chunk 的名称。 从 webpack 2.6.0 开始,占位符 [index]
和 [request]
分别支持递增的数字或实际的解析文件名。 添加此注释后,将单独的给我们的 chunk 命名为 [my-chunk-name].js 而不是 [id].js。
webpackMode
:从 webpack 2.6.0 开始,可以指定以不同的模式解析动态导入。支持以下选项:
'lazy'
(默认值):为每个 import()
导入的模块生成一个可延迟加载(lazy-loadable)的 chunk。'lazy-once'
:生成一个可以满足所有 import()
调用的单个可延迟加载(lazy-loadable)的 chunk。此 chunk 将在第一次 import()
时调用时获取,随后的 import()
则使用相同的网络响应。注意,这种模式仅在部分动态语句中有意义,例如 import(`./locales/${language}.json`)
,其中可能含有多个被请求的模块路径。'eager'
:不会生成额外的 chunk。所有的模块都被当前的 chunk 引入,并且没有额外的网络请求。但是仍会返回一个 resolved 状态的 Promise
。与静态导入相比,在调用 import()
完成之前,该模块不会被执行。'weak'
:尝试加载模块,如果该模块函数已经以其他方式加载,(即另一个 chunk 导入过此模块,或包含模块的脚本被加载)。仍会返回 Promise
, 但是只有在客户端上已经有该 chunk 时才会成功解析。如果该模块不可用,则返回 rejected 状态的 Promise
,且网络请求永远都不会执行。当需要的 chunks 始终在(嵌入在页面中的)初始请求中手动提供,而不是在应用程序导航在最初没有提供的模块导入的情况下触发,这对于通用渲染(SSR)是非常有用的。webpackPrefetch
:告诉浏览器将来可能需要该资源来进行某些导航跳转。查看指南,了解有关更多信息 how webpackPrefetch works。
webpackPreload
:告诉浏览器在当前导航期间可能需要该资源。 查阅指南,了解有关的更多信息 how webpackPreload works。
webpackInclude
:在导入解析(import resolution)过程中,用于匹配的正则表达式。只有匹配到的模块才会被打包。
webpackExclude
:在导入解析(import resolution)过程中,用于匹配的正则表达式。所有匹配到的模块都不会被打包。
webpackExports
: 告知 webpack 只构建指定出口的动态 import()
模块。它可以减小 chunk 的大小。从 webpack 5.0.0-beta.18 起可用。
CommonJS 的目标是为浏览器之外的 JavaScript 指定一个生态系统。webpack 支持以下 CommonJS 的方法:
require
require(dependency: String);
以同步的方式检索其他模块的导出。编译器(compiler)会确保依赖项在输出 bundle 中可用。
var $ = require('jquery');
var myModule = require('my-module');
也可以为 require
启用魔法注释,请参阅 module.parser.javascript.commonjsMagicComments
了解更多。
require.resolve(dependency: String);
以同步的方式获取模块的 ID。编译器(compiler)会确保依赖项在最终输出 bundle 中可用。建议将其视为不透明值,只能与 require.cache[id]
或 __webpack_require__(id)
配合使用(最好避免这种用法)。
有关更多模块的信息,详见 module.id
。
多处引用同一模块,最终只会产生一次模块执行和一次导出。所以,会在运行时(runtime)中会保存一份缓存。删除此缓存,则会产生新的模块执行和新的导出。
var d1 = require('dependency');
require('dependency') === d1;
delete require.cache[require.resolve('dependency')];
require('dependency') !== d1;
// in file.js
require.cache[module.id] === module;
require('./file.js') === module.exports;
delete require.cache[module.id];
require.cache[module.id] === undefined;
require('./file.js') !== module.exports; // 理论上是不相等的;实际运行中,则会导致堆栈溢出
require.cache[module.id] !== module;
require.ensure(
dependencies: String[],
callback: function(require),
errorCallback: function(error),
chunkName: String
)
给定 dependencies
参数,将其对应的文件拆分到一个单独的 bundle 中,此 bundle 会被异步加载。当使用 CommonJS 模块语法时,这是动态加载依赖项的唯一方法。这意味着,可以在模块执行时才允许代码,只有在满足特定条件时才会加载 dependencies
。
var a = require('normal-dep');
if (module.hot) {
require.ensure(['b'], function (require) {
var c = require('c');
// Do something special...
});
}
按照上面指定的顺序,require.ensure
支持以下参数:
dependencies
:字符串数组,声明 callback
回调函数中所需要的所有模块。callback
:当依赖项加载完成后,webpack 将会执行此函数,require
函数的实现,作为参数传入此函数中。当程序运行需要依赖时,可以使用 require()
来加载依赖。函数体可以使用此参数,来进一步执行 require()
模块。errorCallback
:当 webpack 加载依赖失败时会执行此函数。chunkName
:由 require.ensure
创建的 chunk 的名称。通过将相同 chunkName
传递给不同的 require.ensure
调用,我们可以将其代码合并到一个单独的 chunk 中,从而只产生一个浏览器必须加载的 bundle。AMD(Asynchronous Module Definition)是一种定义了用于编写和加载模块接口的 JavaScript 规范。
define([name: String], [dependencies: String[]], factoryMethod: function(...))
如果提供了 dependencies
参数,就会调用 factoryMethod
方法,并(以相同的顺序)导出每个依赖项。如果未提供 dependencies
参数,调用 factoryMethod
方法时会传入 require
, exports
和 module
(用于兼容!)。如果此方法返回一个值,则返回值会作为此模块的导出。由编译器(compiler)来确保依赖项在最终输出的 bundle 中可用。
define(['jquery', 'my-module'], function ($, myModule) {
// 使用 $ 和 myModule 做一些操作...
// 导出一个函数
return function doSomething() {
// ...
};
});
define(value: !Function)
这种方式只将提供的 value
导出。这里的 value
可以是除函数以外的任何值。
define({
answer: 42,
});
require(dependencies: String[], [callback: function(...)])
与 require.ensure
类似,给定 dependencies
参数,将其对应的文件拆分到一个单独的 bundle 中,此 bundle 会被异步加载。然后会调用 callback
回调函数,并传入 dependencies
数组中的每个项导出。
require(['b'], function (b) {
var c = require('c');
});
webpack 内置的 LabeledModulesPlugin
插件,允许你使用下面的方法导出和导入模块:
导出给定的 value
。标签可以出现在函数声明或变量声明之前。函数名或变量名是导出值的标识符。
export: var answer = 42;
export: function method(value) {
// Do something...
};
在当前作用域下,依赖项的所有导出均可用。require
标签可以放置在一个字符串之前。依赖模块必须使用 export
标签导出值。CommonJS 或 AMD 模块无法通过这种方式使用。
some-dependency.js
export: var answer = 42;
export: function method(value) {
// Do something...
};
require: 'some-dependency';
console.log(answer);
method(...);
除了上述模块语法之外,还允许使用一些 webpack 特定的方法:
require.context(
(directory: String),
(includeSubdirs: Boolean) /* 可选的,默认值是 true */,
(filter: RegExp) /* 可选的,默认值是 /^\.\/.*$/,所有文件 */,
(mode: String) /* 可选的, 'sync' | 'eager' | 'weak' | 'lazy' | 'lazy-once',默认值是 'sync' */
)
指定一系列依赖项,通过使用 directory
的路径,以及 includeSubdirs
,filter
选项,进行更细粒度的模块引入,使用 mode
定义加载方式。以此可以很容易的解析模块:
var context = require.context('components', true, /\.html$/);
var componentA = context.resolve('componentA');
如果 mode
设置为 lazy
,基础模块将以异步方式加载:
var context = require.context('locales', true, /\.json$/, 'lazy');
context('localeA').then((locale) => {
// do something with locale
});
mode
的可用模式及说明的完整列表在 import()
文档中进行了描述。
require.include((dependency: String));
引入一个不需要执行的 dependency
,这样可以用于优化输出 chunk 中依赖模块的位置。
require.include('a');
require.ensure(['a', 'b'], function (require) {
/* ... */
});
require.ensure(['a', 'c'], function (require) {
/* ... */
});
这会产生以下输出:
file.js
and a
b
c
不使用 require.include('a')
,输出的两个匿名 chunk 都会有模块 a。
与 require.resolve
类似,但是不会把 module
引入到 bundle 中。这就是所谓的“弱(weak)”依赖。
if (__webpack_modules__[require.resolveWeak('module')]) {
// 当模块可用时,执行一些操作……
}
if (require.cache[require.resolveWeak('module')]) {
// 在模块加载完成之前,执行一些操作……
}
// 你可以像执行其他 require/import 方法一样,
// 执行动态解析上下文 resolves ("context")。
const page = 'Foo';
__webpack_modules__[require.resolveWeak(`./page/${page}`)];
如果模块源码包含无法静态分析的 require,则会发出关键依赖项警告。
示例代码:
someFn(require);
require.bind(null);
require(variable);
本章节涵盖了使用 webpack 编译的代码中所有的变量。模块将通过 module
和其他变量,来访问编译过程中的某些数据。
false
表示该模块正在执行, true
表示同步执行已经完成。
表示 模块热替换(Hot Module Replacement) 是否启用,并给进程提供一个接口。详细说明请查看 模块热替换 API 页面。
当前模块的 ID。
module.id === require.resolve('./file.js');
调用者通过 require
对模块进行调用时返回的值(默认为一个新对象)。
module.exports = function doSomething() {
// Do something...
};
该变量默认值为 module.exports
(即一个对象)。 如果 module.exports
被重写的话, exports
不再会被导出。
exports.someValue = 42;
exports.anObject = {
x: 123,
};
exports.aFunction = function doSomething() {
// Do something
};
出于兼容性考虑,webpack 默认填充了 global
变量。
取决于 node.__dirname
配置选项:
false
: 未定义mock
: 等同于 '/'
true
: node.js __dirname如果在一个被 Parser 解析的表达式内部使用,则配置选项会被当作 true
处理。
返回模块以 file:
开头绝对路径的 URL。
src/index.js
console.log(import.meta.url); // 输出结果类似于 `file:///path/to/your/project/src/index.js`
返回 webpack 的版本
src/index.js
console.log(import.meta.webpack); // output `5` for webpack 5
webpack 特定。module.hot
的一个别名,strict ESM 中可以使用 import.meta.webpackHot
但是不能使用 module.hot
。
返回与 require.context
一样的值,但是仅用于 javascript/auto
和 javascript/esm
。
类型:
(
request: string,
options?: {
recursive?: boolean;
regExp?: RegExp;
include?: RegExp;
exclude?: RegExp;
preload?: boolean | number;
prefetch?: boolean | number;
chunkName?: string;
exports?: string | string[][];
mode?: 'sync' | 'eager' | 'weak' | 'lazy' | 'lazy-once';
}
) => webpack.Context;
可用版本:5.70.0+
示例:
const contextRequire = import.meta.webpackContext('.', {
recursive: false,
regExp: /two/,
mode: 'weak',
exclude: /three/,
});
取决于 node.__filename
配置选项:
false
: 未定义mock
: 等同于 '/index.js'
true
: node.js __filename如果在一个被 Parser 解析的表达式内部使用,则配置选项会被当作 true
处理。
当前模块的资源查询(resource query) 。如果进行了如下的 reqiure
调用,那么查询字符串(query string)在file.js
中可访问。
require('file.js?test');
file.js
__resourceQuery === '?test';
等同于 output.publicPath
配置选项。
原始 require 函数。这个表达式不会被解析器解析为依赖。
__webpack_chunk_load__
(webpack-specific)内部 chunk 载入函数,有一个输入参数:
chunkId
需要载入的 chunk id。当一个 chunk 加载失败时,从备用公共路径加载 chunk 的示例:
const originalLoad = __webpack_chunk_load__;
const publicPaths = ['a', 'b', 'c'];
__webpack_chunk_load__ = async (id) => {
let error;
for (const path of publicPaths) {
__webpack_public_path__ = path;
try {
return await originalLoad(id);
} catch (e) {
error = e;
}
}
throw error;
};
import('./module-a').then((moduleA) => {
// now webpack will use the custom __webpack_chunk_load__ to load chunk
});
它提供对当前 module
的访问。module
在 ESM 严格模式下不可用。
它提供对当前 module
(module.id
) ID 的访问。module
在 ESM 严格模式下不可用。
访问所有模块的内部对象。
这个变量提供对编译过程中(compilation)的 hash 信息的访问。
function (chunkId)
它通过 chunk 的 id 提供 chunk 的文件名。
它是可分配的,允许更改运行时使用的文件名。例如,它可以用来确定加载 chunk 时的最终路径。
const oldFn = __webpack_get_script_filename__;
__webpack_get_script_filename__ = (chunkId) => {
const filename = oldFn(chunkId);
return filename + '.changed';
};
生成一个不会被 webpack 解析的 require
函数。配合全局可以获取到的 require 函数,可以完成一些酷炫操作。
在模块中, __webpack_exports_info__
可以被获取到,以便导出模块用以自我检查:
__webpack_exports_info__
总是 true
当导出模块未被使用时 __webpack_exports_info__.<exportName>.used
为 false
, 否则是 true
__webpack_exports_info__.<exportName>.useInfo
是
false
当导出模块未被使用true
当导出模块被使用null
当导出模块的使用情况取决于运行时的条件undefined
当没有可用信息时__webpack_exports_info__.<exportName>.provideInfo
是
false
当导出模块没有被提供true
当导出模块被提供null
当导出模块的提供情况取决于运行时的条件undefined
当没有可用信息时可以从嵌套的 exports 中得到相关信息: 例如 __webpack_exports_info__.<exportName>.<exportName>.<exportName>.used
Check whether exports can be mangled with __webpack_exports_info__.<name>.canMangle
测试给定的模块是否被 webpack 打包。
if (__webpack_is_included__('./module-a.js')) {
// do something
}
运行时修改 base 的 URI。
类型:string
可用:5.21.0+
示例:
__webpack_base_uri__ = 'https://example.com';
访问当前入口的 runtime id。
这是一个 webpack 特性,并且在 webpack 5.25.0 后可用。
src/index.js
console.log(__webpack_runtime_id__ === 'main');
等同于 debug
配置选项。
Compiler
模块是 webpack 的主要引擎,它通过 CLI 或者 Node API
传递的所有选项创建出一个 compilation 实例。
它扩展(extends)自 Tapable
类,用来注册和调用插件。
大多数面向用户的插件会首先在 Compiler
上注册。
在为 webpack 开发插件时,你可能需要知道每个钩子函数是在哪里调用的。想要了解这些内容,请在 webpack 源码中搜索 hooks.<hook name>.call
。
Compiler
支持可以监控文件系统的 监听(watching) 机制,并且在文件修改时重新编译。
当处于监听模式(watch mode)时,
compiler 会触发诸如 watchRun
, watchClose
和 invalid
等额外的事件。
通常在 开发环境 中使用,
也常常会在 webpack-dev-server
这些工具的底层调用,
由此开发人员无须每次都使用手动方式重新编译。
还可以通过 CLI 进入监听模式。
以下生命周期钩子函数,是由 compiler
暴露,
可以通过如下方式访问:
compiler.hooks.someHook.tap('MyPlugin', (params) => {
/* ... */
});
取决于不同的钩子类型,也可以在某些钩子上访问 tapAsync
和 tapPromise
。
关于钩子类型的描述,请查看 Tapable 文档.
SyncHook
在编译器准备环境时调用,时机就在配置文件中初始化插件之后。
SyncHook
当编译器环境设置完成后,在 environment
hook 后直接调用。
SyncBailHook
在 webpack 选项中的 entry
被处理过之后调用。
compiler.hooks.entryOption.tap('MyPlugin', (context, entry) => {
/* ... */
});
SyncHook
在初始化内部插件集合完成设置之后调用。
compiler
SyncHook
resolver 设置完成之后触发。
compiler
SyncHook
当编译器对象被初始化时调用。
AsyncSeriesHook
在开始执行一次构建之前调用,compiler.run 方法开始执行后立刻进行调用。
compiler
AsyncSeriesHook
在开始读取 records
之前调用。
compiler
AsyncSeriesHook
在监听模式下,一个新的 compilation 触发之后,但在 compilation 实际开始之前执行。
compiler
SyncHook
NormalModuleFactory 创建之后调用。
normalModuleFactory
SyncHook
ContextModuleFactory 创建之后调用。
contextModuleFactory
AsyncSeriesHook
在创建 compilation parameter 之后执行。
compilationParams
初始化 compilationParams
变量的示例如下:
compilationParams = {
normalModuleFactory,
contextModuleFactory,
};
此钩子可用于添加/修改 compilation parameter:
compiler.hooks.beforeCompile.tapAsync('MyPlugin', (params, callback) => {
params['MyPlugin - data'] = 'important stuff my plugin will use later';
callback();
});
SyncHook
beforeCompile
之后立即调用,但在一个新的 compilation 创建之前。这个钩子 不会 被复制到子编译器。
compilationParams
SyncHook
初始化 compilation 时调用,在触发 compilation
事件之前调用。这个钩子 不会 被复制到子编译器。
compilation
, compilationParams
SyncHook
compilation 创建之后执行。
compilation
, compilationParams
AsyncParallelHook
compilation 结束之前执行。这个钩子 不会 被复制到子编译器。
compilation
AsyncSeriesHook
compilation 结束和封印之后执行。
compilation
SyncBailHook
在输出 asset 之前调用。返回一个布尔值,告知是否输出。
compilation
compiler.hooks.shouldEmit.tap('MyPlugin', (compilation) => {
// 返回 true 以输出 output 结果,否则返回 false
return true;
});
AsyncSeriesHook
输出 asset 到 output 目录之前执行。这个钩子 不会 被复制到子编译器。
compilation
AsyncSeriesHook
输出 asset 到 output 目录之后执行。这个钩子 不会 被复制到子编译器。
compilation
AsyncSeriesHook
在 asset 被输出时执行。此钩子可以访问被输出的 asset 的相关信息,例如它的输出路径和字节内容。
file
, info
例如,可以通过 info.content
访问 asset 的内容 buffer:
compiler.hooks.assetEmitted.tap(
'MyPlugin',
(file, { content, source, outputPath, compilation, targetPath }) => {
console.log(content); // <Buffer 66 6f 6f 62 61 72>
}
);
AsyncSeriesHook
在 compilation 完成时执行。这个钩子 不会 被复制到子编译器。
stats
AsyncSeriesHook
This hook allows you to do a one more additional pass of the build.
SyncHook
在 compilation 失败时调用。
error
SyncHook
在一个观察中的 compilation 无效时执行。这个钩子 不会 被复制到子编译器。
fileName
, changeTime
SyncHook
在一个观察中的 compilation 停止时执行。
AsyncSeriesHook
当编译器关闭时调用。
SyncBailHook
在配置中启用 infrastructureLogging
选项 后,允许使用 infrastructure log(基础日志)。
name
, type
, args
SyncBailHook
启用后允许记录到 stats 对象,请参阅 stats.logging
, stats.loggingDebug
和 stats.loggingTrace
选项。
origin
, logEntry
Compilation
模块会被 Compiler
用来创建新的 compilation 对象(或新的 build 对象)。
compilation
实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。
它会对应用程序的依赖图中所有模块,
进行字面上的编译(literal compilation)。
在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、
分块(chunk)、哈希(hash)和重新创建(restore)。
Compilation
类扩展(extend)自 Tapable
,并提供了以下生命周期钩子。
可以按照 compiler 钩子的相同方式来调用 tap:
compilation.hooks.someHook.tap(/* ... */);
和 compiler
用法相同,取决于不同的钩子类型,
所以也可以在某些钩子上访问 tapAsync
和 tapPromise
。
SyncHook
在模块构建开始之前触发,可以用来修改模块。
module
compilation.hooks.buildModule.tap(
'SourceMapDevToolModuleOptionsPlugin',
(module) => {
module.useSourceMap = true;
}
);
SyncHook
在重新构建一个模块之前触发。
module
SyncHook
模块构建失败时执行。
module
error
SyncHook
模块构建成功时执行。
module
AsyncSeriesHook
所有模块都完成构建并且没有错误时执行。
modules
SyncHook
一个模块完成重新构建时执行,在都成功或有错误的情况下。
module
SyncHook
compilation 对象停止接收新的模块时触发。
SyncHook
compilation 对象开始接收新模块时触发。
SyncBailHook
依赖优化开始时触发。
modules
SyncHook
依赖优化之后触发。
modules
SyncHook
优化阶段开始时触发。
SyncBailHook
在模块优化阶段开始时调用。插件可以 tap 此钩子对模块进行优化。
modules
SyncHook
在模块优化完成之后调用。
modules
SyncBailHook
在 chunk 优化阶段开始时调用。插件可以 tap 此钩子对 chunk 执行优化。
chunks
SyncHook
chunk 优化完成之后触发。
chunks
AsyncSeriesHook
在优化依赖树之前调用。插件可以 tap 此钩子执行依赖树优化。
chunks
modules
SyncHook
在依赖树优化成功完成之后调用。
chunks
modules
SyncBailHook
在树优化之后,chunk 模块优化开始时调用。插件可以 tap 此钩子来执行 chunk 模块的优化。
chunks
modules
SyncHook
在 chunk 模块优化成功完成之后调用。
chunks
modules
SyncBailHook
调用来决定是否存储 record。返回任何内容 !== false
将阻止执行所有其他 "record" 钩子(record
, recordModules
, recordChunks
和 recordHash
)。
SyncHook
从 record 中恢复模块信息。
modules
records
SyncHook
在为每个模块分配 id
之前执行。
modules
SyncHook
调用来每个模块分配一个 id
。
modules
SyncHook
在模块 id
优化开始时调用。
modules
SyncHook
在模块 id
优化完成时调用。
modules
SyncHook
从 record 中恢复 chunk 信息。
chunks
records
SyncHook
在为每个 chunk 分配 id
之前执行。
chunks
SyncHook
调用时,会为每个 chunk 分配一个 id
。
chunks
SyncHook
在 chunk id
优化阶段开始时调用。
chunks
SyncHook
chunk id
优化结束之后触发。
chunks
SyncHook
将模块信息存储到 record 中。shouldRecord
返回 truthy 值时触发。
modules
records
SyncHook
将 chunk 存储到 record 中。shouldRecord
返回 truthy 值时触发。
chunks
records
SyncHook
在创建模块哈希(hash)之前。
syncHook
在创建模块哈希(hash)之后。
SyncHook
在 compilation 添加哈希(hash)之前。
SyncHook
在 compilation 添加哈希(hash)之后。
SyncHook
将有关 record 的信息存储到 records
中。仅在 shouldRecord
返回 truthy 值时触发。
records
SyncHook
将 compilation
相关信息存储到 record
中。仅在 shouldRecord
返回 truthy 值时触发。
compilation
records
SyncHook
在创建模块 asset 之前执行。
SyncHook
为这些 chunk 创建其他 asset。
chunks
SyncBailHook
调用以确定是否生成 chunk asset。返回任何 !== false
将允许生成 chunk asset。
SyncHook
在创建 chunk asset 之前。
AsyncSeriesHook
为 compilation 创建额外 asset。 这个钩子可以用来下载图像,例如:
compilation.hooks.additionalAssets.tapAsync('MyPlugin', (callback) => {
download('https://img.shields.io/npm/v/webpack.svg', function (resp) {
if (resp.status === 200) {
compilation.assets['webpack-version.svg'] = toAsset(resp);
callback();
} else {
callback(
new Error('[webpack-example-plugin] Unable to download the image')
);
}
});
});
AsyncSeriesHook
优化所有 chunk asset。asset 存储在 compilation.assets
中。
每个 Chunk
都具有一个 files
属性,其指向由一个 chunk 创建的所有文件。
任何额外 chunk asset 都存储在 compilation.additionalChunkAssets
中。
chunks
Here's an example that adds a banner to each chunk.
compilation.hooks.optimizeChunkAssets.tapAsync(
'MyPlugin',
(chunks, callback) => {
chunks.forEach((chunk) => {
chunk.files.forEach((file) => {
compilation.assets[file] = new ConcatSource(
'/**Sweet Banner**/',
'\n',
compilation.assets[file]
);
});
});
callback();
}
);
SyncHook
chunk asset 已经被优化。
chunks
这里是一个来自 @boopathi 的示例插件,详细地输出每个 chunk 里有什么。
compilation.hooks.afterOptimizeChunkAssets.tap('MyPlugin', (chunks) => {
chunks.forEach((chunk) => {
console.log({
id: chunk.id,
name: chunk.name,
includes: chunk.getModules().map((module) => module.request),
});
});
});
AsyncSeriesHook
优化存储在 compilation.assets
中的所有 asset。
assets
SyncHook
asset 已经优化。
assets
AsyncSeriesHook
asset 处理.
Hook 参数:
name: string
— 插件名称stage: Stage
— a stage to tap into (see the list of supported stages below)additionalAssets?: true | (assets, [callback]) => (void | Promise<void>)
— a callback for additional assets (see below)回调参数:
assets: { [pathname: string]: Source }
— 普通对象,其中 key 是 asset 的路径名,value 是 asset 的数据,具体的代表请参考 Source
。示例:
compilation.hooks.processAssets.tap(
{
name: 'MyPlugin',
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, // see below for more stages
},
(assets) => {
console.log('List of assets and their sizes:');
Object.entries(assets).forEach(([pathname, source]) => {
console.log(`— ${pathname}: ${source.size()} bytes`);
});
}
);
除了 name
和 stage
以外,你还可以传递 additionalAssets
5.8.0+ 选项,此选项可接受 true
或者一个带有 assets
的函数作为参数:
true
- 针对插件后续添加的 asset 执行回调。
在此模式下,回调将被多次调用:一次是在指定阶段之前添加资产时,另一次是后来由插件添加资产时。(在本阶段或下一阶段)。
compilation.hooks.processAssets.tap(
{
name: 'MyPlugin',
stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING,
additionalAssets: true,
},
(assets) => {
// this function will be called multiple times with each bulk of assets
}
);
(assets, [callback]) => (void | Promise<void>)
- 针对插件后续添加的 asset 执行指定的回调(在本阶段或下一阶段)。回调必须遵循所使用的 tap 函数的类型(例如,当与 tapPromise()
一同使用时,它应该返回一个 Promise)。
compilation.hooks.processAssets.tap(
{
name: 'MyPlugin',
stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING,
additionalAssets: (assets) => {
// this function potentially could be called multiple times for assets added on later stages
},
},
(assets) => {
// this function will be called once with assets added by plugins on prior stages
}
);
如下是我们可以使用的 stage 清单(按顺序处理):
PROCESS_ASSETS_STAGE_ADDITIONAL
— 在编译中添加额外的 asset。PROCESS_ASSETS_STAGE_PRE_PROCESS
— asset 进行了基础预处理。PROCESS_ASSETS_STAGE_DERIVED
— 从已有 asset 中获取新的 asset。PROCESS_ASSETS_STAGE_ADDITIONS
— 为现有的 asset 添加额外的内容,例如 banner 或初始代码。PROCESS_ASSETS_STAGE_OPTIMIZE
— 以通用的方式优化已有 asset。PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT
— 优化现有资产的数量,例如,进行合并操作。PROCESS_ASSETS_STAGE_OPTIMIZE_COMPATIBILITY
— 优化现有 asset 兼容性,例如添加 polyfills 或者 vendor prefixes。PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE
— 优化现有 asset 大小,例如进行压缩或者删除空格。PROCESS_ASSETS_STAGE_DEV_TOOLING
— 为 asset 添加开发者工具,例如,提取 source map。PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE
5.8.0+ — 优化已有 asset 数量,例如,通过将 asset 内联到其他 asset 中。PROCESS_ASSETS_STAGE_SUMMARIZE
— 整理现有 asset 列表。PROCESS_ASSETS_STAGE_OPTIMIZE_HASH
— 优化 asset 的 hash 值,例如,生成 asset 内容的真实 hash 值。PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER
— 优化已有 asset 的转换操作,例如对 asset 进行压缩,并作为独立的 asset。PROCESS_ASSETS_STAGE_ANALYSE
— 分析已有 asset。PROCESS_ASSETS_STAGE_REPORT
— 创建用于上报的 asset。"asset info" 元数据不会自动提供给这个 hook。如果必须使用,你需要通过编译实例以及 asset 路径来手动获取这个元数据。这将在未来的 webpack 版本中得到改善。
Example:
compilation.hooks.processAssets.tap(
{
/** … */
},
(assets) => {
Object.entries(assets).forEach(([pathname, source]) => {
const assetInfo = compilation.assetsInfo.get(pathname);
// @todo: do something with "pathname", "source" and "assetInfo"
});
}
);
SyncHook
在 processAssets
hook 无错误执行后调用。
SyncBailHook
调用来决定 compilation 是否需要解除 seal 以引入其他文件。
AsyncSeriesHook
在 needAdditionalSeal
之后立即执行。
SyncHook
触发来为每个 chunk 生成 hash。
chunk
chunkHash
SyncHook
一个模块中的一个 asset 被添加到 compilation 时调用。
module
filename
SyncHook
一个 chunk 中的一个 asset 被添加到 compilation 时调用。
chunk
filename
SyncWaterfallHook
调用以决定 asset 的路径。
path
options
SyncBailHook
调用以决定 asset 在输出后是否需要进一步处理。
SyncHook
子 compiler 设置之后执行。
childCompiler
compilerName
compilerIndex
从 webpack v5 开始,normalModuleLoader
钩子已经删除。现在要访问 loader 请改用 NormalModule.getCompilationHooks(compilation).loader
。
Compiler
使用 ContextModuleFactory
模块从 webpack 独特的 require.context API 生成依赖关系。它会解析请求的目录,为每个文件生成请求,并依据传递来的 regExp 进行过滤。最后匹配成功的依赖关系将被传入 NormalModuleFactory。
ContextModuleFactory
类扩展了 Tapable
并提供了以下的生命周期钩子。
你可以像使用编译器钩子一样使用它们:
ContextModuleFactory.hooks.someHook.tap(/* ... */);
与 compiler
一样,tapAsync
和 tapPromise
是否可用
取决于钩子的类型。
AsyncSeriesWaterfallHook
在解析请求的目录之前调用。请求可以通过返回 false
来忽略。
data
AsyncSeriesWaterfallHook
在请求的目录解析后调用。
data
SyncWaterfallHook
读取目录内容后调用。在递归模式下,也会读取每个子目录。回调参数是一个包含每个目录中所有文件和文件夹名称的数组。
fileNames
AsyncSeriesWaterfallHook
在创建请求之后但依据 regExp 进行过滤之前,为每个文件调用。
request
options
parser
实例,在 compiler
中被发现,是用来解析由 webpack
处理过的每个模块。parser
也是扩展自 tapable
的 webpack 类
并且提供多种 tapable
钩子,
以下示例中,parser
位于 NormalModuleFactory 中,因此需要调用额外钩子
来进行获取:
compiler.hooks.normalModuleFactory.tap('MyPlugin', (factory) => {
factory.hooks.parser
.for('javascript/auto')
.tap('MyPlugin', (parser, options) => {
parser.hooks.someHook.tap(/* ... */);
});
});
和 compiler
用法相同,取决于不同的钩子类型,
也可以在某些钩子上访问 tapAsync
和 tapPromise
。
以下生命周期钩子函数,是由 parser
暴露,可以通过
如下方式访问:
SyncBailHook
Triggered when evaluating an expression consisting in a typeof
of a free variable
identifier
expression
parser.hooks.evaluateTypeof
.for('myIdentifier')
.tap('MyPlugin', (expression) => {
/* ... */
return expressionResult;
});
这会触发 evaluateTypeof
钩子的调用:
const a = typeof myIdentifier;
This won't trigger:
const myIdentifier = 0;
const b = typeof myIdentifier;
SyncBailHook
Called when evaluating an expression.
expressionType
expression
For example:
index.js
const a = new String();
MyPlugin.js
parser.hooks.evaluate.for('NewExpression').tap('MyPlugin', (expression) => {
/* ... */
return expressionResult;
});
Where the expressions types are:
'ArrowFunctionExpression'
'AssignmentExpression'
'AwaitExpression'
'BinaryExpression'
'CallExpression'
'ClassExpression'
'ConditionalExpression'
'FunctionExpression'
'Identifier'
'LogicalExpression'
'MemberExpression'
'NewExpression'
'ObjectExpression'
'SequenceExpression'
'SpreadElement'
'TaggedTemplateExpression'
'TemplateLiteral'
'ThisExpression'
'UnaryExpression'
'UpdateExpression'
SyncBailHook
Called when evaluating an identifier that is a free variable.
identifier
expression
SyncBailHook
Called when evaluating an identifier that is a defined variable.
identifier
expression
SyncBailHook
Called when evaluating a call to a member function of a successfully evaluated expression.
identifier
expression
param
This expression will trigger the hook:
index.js
const a = expression.myFunc();
MyPlugin.js
parser.hooks.evaluateCallExpressionMember
.for('myFunc')
.tap('MyPlugin', (expression, param) => {
/* ... */
return expressionResult;
});
SyncBailHook
General purpose hook that is called for every parsed statement in a code fragment.
statement
parser.hooks.statement.tap('MyPlugin', (statement) => {
/* ... */
});
Where the statement.type
could be:
'BlockStatement'
'VariableDeclaration'
'FunctionDeclaration'
'ReturnStatement'
'ClassDeclaration'
'ExpressionStatement'
'ImportDeclaration'
'ExportAllDeclaration'
'ExportDefaultDeclaration'
'ExportNamedDeclaration'
'IfStatement'
'SwitchStatement'
'ForInStatement'
'ForOfStatement'
'ForStatement'
'WhileStatement'
'DoWhileStatement'
'ThrowStatement'
'TryStatement'
'LabeledStatement'
'WithStatement'
SyncBailHook
Called when parsing an if statement. Same as the statement
hook, but triggered only when statement.type == 'IfStatement'
.
statement
SyncBailHook
Called when parsing statements with a label. Those statements have statement.type === 'LabeledStatement'
.
labelName
statement
SyncBailHook
Called for every import statement in a code fragment. The source
parameter contains the name of the imported file.
statement
source
The following import statement will trigger the hook once:
index.js
import _ from 'lodash';
MyPlugin.js
parser.hooks.import.tap('MyPlugin', (statement, source) => {
// source == 'lodash'
});
SyncBailHook
Called for every specifier of every import
statement.
statement
source
exportName
identifierName
The following import statement will trigger the hook twice:
index.js
import _, { has } from 'lodash';
MyPlugin.js
parser.hooks.importSpecifier.tap(
'MyPlugin',
(statement, source, exportName, identifierName) => {
/* First call
source == 'lodash'
exportName == 'default'
identifierName == '_'
*/
/* Second call
source == 'lodash'
exportName == 'has'
identifierName == 'has'
*/
}
);
SyncBailHook
Called for every export
statement in a code fragment.
statement
SyncBailHook
Called for every export
-import statement eg: export * from 'otherModule';
.
statement
source
SyncBailHook
Called for every export
statement exporting a declaration.
statement
declaration
Those exports will trigger this hook:
export const myVar = 'hello'; // also var, let
export function FunctionName() {}
export class ClassName {}
SyncBailHook
Called for every export
statement exporting an expression e.g.export default expression;
.
statement
declaration
SyncBailHook
Called for every specifier of every export
statement.
statement
identifierName
exportName
index
SyncBailHook
Called for every specifier of every export
-import statement.
statement
source
identifierName
exportName
index
SyncBailHook
Called when parsing a variable declaration.
declaration
SyncBailHook
Called when parsing a variable declaration defined using let
declaration
SyncBailHook
Called when parsing a variable declaration defined using const
declaration
SyncBailHook
Called when parsing a variable declaration defined using var
declaration
SyncBailHook
Triggered before renaming an identifier to determine if the renaming is allowed. This is usually used together with the rename
hook.
identifier
expression
var a = b;
parser.hooks.canRename.for('b').tap('MyPlugin', (expression) => {
// returning true allows renaming
return true;
});
SyncBailHook
Triggered when renaming to get the new identifier. This hook will be called only if canRename
returns true
.
identifier
expression
var a = b;
parser.hooks.rename.for('b').tap('MyPlugin', (expression) => {});
SyncBailHook
Called when parsing an AssignmentExpression
before parsing the assigned expression.
identifier
expression
a += b;
parser.hooks.assigned.for('a').tap('MyPlugin', (expression) => {
// this is called before parsing b
});
SyncBailHook
Called when parsing an AssignmentExpression
before parsing the assign expression.
identifier
expression
a += b;
parser.hooks.assigned.for('a').tap('MyPlugin', (expression) => {
// this is called before parsing a
});
SyncBailHook
Triggered when parsing the typeof
of an identifier
identifier
expression
SyncBailHook
Called when parsing a function call.
identifier
expression
eval(/* something */);
parser.hooks.call.for('eval').tap('MyPlugin', (expression) => {});
SyncBailHook
Triggered when parsing a call to a member function of an object.
objectIdentifier
expression, properties
myObj.anyFunc();
parser.hooks.callMemberChain
.for('myObj')
.tap('MyPlugin', (expression, properties) => {});
SyncBailHook
Invoked when parsing a new
expression.
identifier
expression
new MyClass();
parser.hooks.new.for('MyClass').tap('MyPlugin', (expression) => {});
SyncBailHook
Called when parsing an expression.
identifier
expression
const a = this;
parser.hooks.expression.for('this').tap('MyPlugin', (expression) => {});
SyncBailHook
Called when parsing a ConditionalExpression
e.g. condition ? a : b
expression
SyncBailHook
Get access to the abstract syntax tree (AST) of a code fragment
ast
comments
Compiler
使用 NormalModuleFactory
模块生成各类模块。从入口点开始,此模块会分解每个请求,解析文件内容以查找进一步的请求,然后通过分解所有请求以及解析新的文件来爬取全部文件。在最后阶段,每个依赖项都会成为一个模块实例。
NormalModuleFactory
类扩展了 Tapable
并提供了以下的生命周期钩子。
你可以像使用编译器钩子一样使用它们:
NormalModuleFactory.hooks.someHook.tap(/* ... */);
NormalModuleFactory
创建了可由 HookMaps
访问的 Parser
和 Generator
实例。同时必须传入 identifier
才能使用以下代码:
NormalModuleFactory.hooks.someHook.for('identifier').tap(/* ... */);
与 compiler
一样,tapAsync
和 tapPromise
是否可用
取决于钩子的类型。
AsyncSeriesBailHook
当遇到新的依赖项请求时调用。可以通过返回 false
来忽略依赖项。否则,返回 undefined
以继续。
resolveData
AsyncSeriesBailHook
在初始化解析之前调用。它应该返回 undefined
以继续。
resolveData
AsyncSeriesBailHook
在请求被解析之前调用。可以通过返回 false
来忽略依赖项。返回一个模块实例将结束进程。否则,返回 undefined
以继续。
resolveData
AsyncSeriesBailHook
在解析符合统一资源标志符方案(URI)的请求之前调用。
resolveData
AsyncSeriesBailHook
在请求解析后调用。
resolveData
AsyncSeriesBailHook
在创建 NormalModule
实例之前调用。
createData
resolveData
SyncWaterfallHook
在创建 NormalModule
实例后调用。
module
createData
resolveData
HookMap<SyncBailHook>
在 Parser
实例创建之前调用。parserOptions
是 module.parser 中对应标识符或空对象的选项。
钩子参数:identifier
回调参数:parserOptions
HookMap<SyncHook>
在创建 Parser
实例后触发。
钩子参数:identifier
回调参数:parser
parserOptions
可能的默认标识符:
javascript/auto
javascript/dynamic
javascript/esm
json
webassembly/sync
webassembly/async
asset
HookMap<SyncBailHook>
在 Generator
实例创建之前调用。generatorOptions
是 module.parser 中对应标识符或空对象的选项。
钩子参数:identifier
回调参数:generatorOptions
HookMap<SyncHook>
在 Generator
实例创建之后调用。
钩子参数:identifier
回调参数:generator
generatorOptions
可能的默认标识符:
json
webassembly/sync
webassembly/async
asset
asset/source
asset/resource
asset/inline
Compilation 对象有很多可用的方法和钩子。在此页面,我们将会列举出这些可用的方法和属性。
function
返回当前编译的状态对象。
function (module, cacheGroup)
向当前编译添加一个模块。
参数:
module
- 要添加的模块cacheGroup
- 模块的 cacheGroup
function (module)
通过编译的标识符获取其模块。
参数:
module
- 要获取的模块。标识符是通过编译使用 module.identifier()
方法从模块中提取的。function (module)
尝试通过其标识符搜索模块。
参数:
module
- 要搜索的模块。标识符是通过编译使用 module.identifier()
方法从模块中提取的。function (module, optional, origin, dependencies)
构建给定的模块。
参数:
module
- 要构建的模块。optional
- 可选标志。origin
- 请求此模块构建的原始模块。dependencies
- 要构建模块的可选依赖。function (module, callback)
处理给定模块依赖。
参数:
module
- 要被处理依赖的模块。callback
- 模块依赖处理完成时回调的函数。function (context, entry, name, callback)
为编译添加入口。
参数:
context
- 入口的上下文路径。entry
- 入口依赖。name
- 入口名称。callback
- 添加入口完成之后回调的函数。function (module, thisCallback)
触发模块的重建。
参数:
module
- 要被重建的模块。thisCallback
- 模块重建完成之后调用的函数。function (callback)
完成编译并调用给定的回调。
参数:
callback
- 编译完成之后调用的函数。function (callback)
封闭编译。
参数:
callback
- 封闭编译时回调的函数。function
解除封闭编译。
参数:
callback
- 解除封闭编译时回调的函数。function (module, blocks)
将给定模块的错误和警告添加到编译的错误和警告中。
参数:
module
- 要被报告错误与警告的模块。blocks
- 一组要报告的依赖块。function (groupOptions, module, loc, request)
将模块添加到现有 chunk 组或创建一个新的组。返回一个 chunkGroup
。
参数:
groupOptions
- chunk 组的选项。module
- 引用 chunk 组的模块。loc
- 引用 chunk 组的位置(模块内部)。request
- 引用 chunk 组的请求。function (name)
向 compilation.chunks
创建或添加一个新的 chunk。返回这个 chunk
.
参数:
name
- chunk 的名称。function (module)
为给定的模块及其依赖块递归分配 depth
。
参数:
module
- 要被分配 depth 的模块。function (module, dependency)
返回给定模块对依赖的引用。
参数:
module
- 有问题的模块。dependency
- 要引用的依赖。function (inputChunkGroups)
通过 Module
图创建 Chunk
图。该过程分为两个阶段完成。阶段一:遍历模块图,在 chunkDependencies
中创建一个基础 chunk 图。阶段二:通过基本 chunk 图遍历所有可能的方法并且跟踪可用模块。遍历过程中 processDependenciesBlocksForChunkGroups
将 chunk 相互连接,并将 Blocks
与 Chunks
连接. 当一个 chunk 的所有模块都已经可用且未连接不需要的 chunk 时,它将停止遍历。
参数:
inputChunkGroups
- 被处理的 chunk 组。function (module, block)
移除模块与依赖块之间的关系。
参数:
module
- 要移除的模块关系。block
- 依赖块。function (module, chunk)
删除依赖性原因后,修补模块和 chunk 的关系。被 removeReasonsOfDependencyBlock
自动调用。
参数:
module
- 要修复关系的模块。chunk
- 要修复关系的 chunk。function (block, chunk)
在除去依赖性原因后,从依赖块模块和 chunk 中移除给定的 chunk。会被 removeReasonsOfDependencyBlock
自动调用。
参数:
block
- Chunk
的块连接。chunk
- 从依赖中删除的块。function
function
function
function
function
function (filename, data)
返回插值路径。
参数:
filename
- 用于通过哈希获取资源路径。data
- 数据对象。function (filename, data)
返回插值路径和资源信息。
参数:
filename
- 用于通过哈希获取资源路径。data
- 数据对象。function (name, outputOptions, plugins)
允许在 webpack 中运行另一个 webpack 实例。但是,子编译器会应用不同的设置和配置。他会从父编译器(或者顶级编译器)中复制所有的钩子(hook)和插件(plugin),并且创建一个子 Compiler
实例。 返回值为创建好的 Compiler
实例。
参数:
name
- 子 Compiler
的名称。outputOptions
- 输出选项。plugins
- 将被提供的 webpack 插件。function
function (file, source, assetInfo = {})
参数:
file
- 资源名称。source
- 资源来源。assetInfo
- 附加资源信息。function (file, newSourceOrFunction, assetInfoUpdateOrFunction)
参数:
file
- 资源名称。newSourceOrFunction
- 新资源来源或将旧资源转换为新资源的函数。assetInfoUpdateOrFunction
- 新资源信息或将旧资源转换为新资源的函数。function (file)
参数:
file
—— 资源的文件名function
返回当前编译下所有资源的数组。
function (name)
参数:
name
- 要返回的资源名称。插件是 webpack 生态的关键部分,
它为社区用户提供了一种强有力的方式来直接触及 webpack 的编译过程(compilation process)。
插件能够 hook 到每一个编译(compilation)中发出的关键事件中。
在编译的每个阶段中,插件都拥有对 compiler
对象的完全访问能力,
并且在合适的时机,还可以访问当前的 compilation
对象。
让我们首先从 tapable 工具开始, 它为 webpack 插件接口提供了核心能力的。
这个小型库是 webpack 的一个核心工具,但也可用于其他地方,
以提供类似的插件接口。
在 webpack 中的许多对象都扩展自 Tapable
类。
它对外暴露了 tap
,tapAsync
和 tapPromise
等方法,
插件可以使用这些方法向 webpack 中注入自定义构建的步骤,这些步骤将在构建过程中触发。
请查阅文档了解更多知识。
理解上面的的三种 tap
方法,
以及提供这些方法的钩子(hooks)对于编写插件来说是至关重要的。
那些扩展自 Tapable
的对象(例如:compiler),
以及其提供的钩子(hooks)和每个钩子的类型(例如:同步钩子(SyncHook)
)值得关注。
根据使用不同的钩子(hooks)和 tap
方法,
插件可以以多种不同的方式运行。
这个工作方式与 Tapable 提供的钩子(hooks)密切相关。
compiler hooks 分别记录了 Tapable 内在的钩子,
并指出哪些 tap 方法可用。
所以,依赖于使用的 tap
方法的不同,
插件可能会以不同的方式运行。
例如:当你钩入到 编译(compile)
阶段时,只有同步的 tap
方法可以使用。
compiler.hooks.compile.tap('MyPlugin', (params) => {
console.log('以同步方式触及 compile 钩子。');
});
然而,对于可以使用 AsyncHook
的 run
阶段,
则需使用 tapAsync
或 tapPromise
(以及 tap
)方法。
compiler.hooks.run.tapAsync(
'MyPlugin',
(source, target, routesList, callback) => {
console.log('以异步方式触及运行钩子。');
callback();
}
);
compiler.hooks.run.tapPromise('MyPlugin', (source, target, routesList) => {
return new Promise((resolve) => setTimeout(resolve, 1000)).then(() => {
console.log('以异步的方式触发具有延迟操作的钩子。');
});
});
compiler.hooks.run.tapPromise(
'MyPlugin',
async (source, target, routesList) => {
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log('以异步的方式触发具有延迟操作的钩子。');
}
);
这些需求(story)的含义在于, 我们可以有多种方式 hook 到 compiler 中,可以让各种插件都以合适的方式去运行。
为了便于其他插件的编译过程中可以 tap
到,
你可以这样做:
Create a module-scope WeakMap
for compilation hooks:
const compilationHooks = new WeakMap<Compilation, MyHooks>();
interface MyHooks {
custom: SyncHook<[number, string]>;
}
在插件中创建一个静态方法:
static getCompilationHooks(compilation: Compilation) : MyHooks {
let hooks = compilationHooks.get(compilation);
if(hooks === undefined) {
compilationHooks.set(compilation, hooks = {
custom: new SyncHook()
});
}
return hooks;
}
像下面这样在你的插件中调用钩子函数:
const hooks = MyPlugin.getCompilationHooks(compilation);
hooks.custom.call(1, 'hello');
其他插件也可以访问你的自定义钩子函数:
import MyPlugin from 'my-plugin';
const hooks = MyPlugin.getCompilationHooks(compilation);
hooks.custom.tap('OtherPlugin', (n, s) => {
// magic
});
再次声明,
查看 tapable
文档 来了解更多不同的钩子类(hook class),以及它们是如何工作的。
插件能够通过 ProgressPlugin
这个在默认情况下将信息打印到标准错误输出(stderr)的插件来进行进度报告。如果想要使用这个功能,只需要在使用 webpack CLI 的时候传入 --progress
参数。
如果想要自定义打印输出,只需要传递不同的参数到 ProgressPlugin
的 reportProgress
方法。
如果想要报告进度,插件必须在 tap
到 hook 的时候使用 context: true
选项。
compiler.hooks.emit.tapAsync(
{
name: 'MyPlugin',
context: true,
},
(context, compiler, callback) => {
const reportProgress = context && context.reportProgress;
if (reportProgress) reportProgress(0.95, 'Starting work');
setTimeout(() => {
if (reportProgress) reportProgress(0.95, 'Done work');
callback();
}, 1000);
}
);
reportProgress
方法在被调用的时候会传入以下的参数:
reportProgress(percentage, ...args);
percentage
:此参数未使用。作为代替,ProgressPlugin
插件会基于当前的钩子(hook)计算进度。...args
:任意数量的字符串,这些字符串会传递给 ProgressPlugin
插件并报告给用户。注意:只有 compiler 和 compilation 钩子的子集才支持 reportProgress
方法。请查看 ProgressPlugin
了解更多信息。
日志的 API 在 webpack 4.37 版本后提供支持。当 logging
在 统计配置(stats configuration)
中可用和(或)当 infrastructure logging
可用的时候,插件会通过各自的记录格式(stats,infrastructure)打印信息。
compilation.getLogger('PluginName')
来做记录。这种形式的记录保存在统计数据(Stats)中并做相应的格式化。它能够被用户过滤和导出。compilation.getInfrastructureLogger('PluginName')
来做记录。使用 infrastructure
的形式并不会被保存在统计数据(Stats)中,因此也不会被格式化。它通常直接将记录载入到 console/dashboard/GUI 中。它能够被用户过滤。compilation.getLogger ? compilation.getLogger('PluginName') : console
来检测是否支持记录,以此来在不支持 compilation.getLogger
方法的旧版本 webpack 中提供降级方法。查看 compiler hooks 部分,
了解所有可用的 compiler
钩子以及它们提供的参数的详细列表。
解析器是使用 enhanced-resolve
库创建的。Resolver
类
拓展了 tapable
类,并使用 tapable
来提供了一些钩子。
enhanced-resolve
可以直接用于创建新的解析器,
但是,任何 compiler
实例 都有一些解析器实例,可以
被 tap
进去。
在继续阅读之前,请确保你已经读过
enhanced-resolve
和 tapable
文档。
在 compiler
类中,提供了三种类型的内置解析器:
normal
: 通过绝对或相对路径解析模块。context
: 在给定的上下文中解析模块。loader
: 解析 webpack loader。根据需要,任一个被使用在 compiler
中的内置解析器,
可以通过插件进行定制:
compiler.resolverFactory.hooks.resolver
.for('[type]')
.tap('name', (resolver) => {
// you can tap into resolver.hooks now
resolver.hooks.result.tap('MyPlugin', (result) => {
return result;
});
});
其中,[type]
是上述三个解析器之一。
请参阅 enhanced-resolve
documentation 以获得钩子的完整列表以及它们的介绍。
上述解析器也可以
利用 resolve
or resolveLoader
选项,通过配置文件进行定制。这些选项允许
用户可以通过多种选项来更改解析行为,包括
通过解析 plugins
。
解析器插件,例如:DirectoryNamedPlugin
,可以直接引入
在 resolve.plugins
,而不是直接在 plugins
configuration option 中使用。