Plugin

由于 loader 作用范围是很明显的,所以就有了它的边界,那么超出这个边界的事情我们就可以使用 Plugin 去实现,这样 webpack 就可以适用用各种各样的场景,如 loader 很困难直接对多种规则的内容进行最后的整合然后输出,而且 loader 之间有顺序规则之类,如果规则与规则之间有关联且要按某种顺序进行执行,那么就很难实现,而这是使用 Plugin 实现是最好的。

Webpack 通过 Plugin 机制让其更加灵活,以适应各种应用场景。

一旦我们打开了 webpack 编译器和每个单独编译的大门,我们可以使用引擎做的事情是无限可能的。我们可以重新格式化存在的文件、创建派生文件、完全伪造一个新文件。

编一个 Plugin

TestPlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class TestPlugin {
constructor(doneCallback, failCallback) {
this.doneCallback = doneCallback;
this.failCallback = failCallback;
}
apply(compiler) {
compiler.hooks.emit.tapAsync("生成md", (compilation, callback) => {
var filelist = "构成生成的文件:\n\n";
for (var filename in compilation.assets) {
filelist += "- " + filename + "\n";
}

compilation.assets["filelist.md"] = {
source: function () {
return filelist;
},
size: function () {
return filelist.length;
},
};

callback();
});
compiler.hooks.done.tap("完成成功", (stats) => {
this.doneCallback(stats);
});
compiler.hooks.failed.tap("完成失败", (err) => {
this.failCallback(err);
});
}
}

module.exports = TestPlugin;

webpack.config.js

1
2
3
4
5
6
7
8
9
10
plugins: [
new TestPlugin(
(p) => {
console.log("w完成-", p);
},
() => {
console.log("w失败-", p);
}
),
],

Webpack 启动后,在读取配置的过程中会先执行 new TestPlugin(options) 初始化一个 TestPlugin 获得其实例。
在初始化 compiler 对象后,再调用 TestPlugin.apply(compiler) 给插件实例传入 compiler 对象。
插件实例在获取到 compiler 对象后,就可以通过 compiler.plugin(事件名称, 回调函数) 监听到 Webpack 广播出来的事件。
并且可以通过 compiler 对象去操作 Webpack。
但 Compiler 和 Compilation 都继承自 Tapable,所以我们可以使用 compilation.hooks.someHook.tap(…)等监听到广播出来的事件。
但 apply 与 plugin 自定义比较简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 广播出事件
* event-name 为事件名称,注意不要和现有的事件重名
* params 为附带的参数
*/
compiler.apply('event-name',params);

/**
* 监听名称为 event-name 的事件,当 event-name 事件发生时,函数就会被执行。
* 同时函数中的 params 参数为广播事件时附带的参数。
*/
compiler.plugin('event-name',function(params) {

});

调试

不管是 npm 还是 webpack 都是基于 nodejs 的工具。所以最终是调试 node。

npm 方式调试

package.json 文件 scripts 项中添加一个 key 为 debug 的配置,内容如下

1
2
3
"scripts": {
"debug": "node --inspect-brk=5858 ./node_modules/webpack/bin/webpack"
}

vscode Ctrl+shift+D切换为调试,选择Add Configuration。vscode 会自动生成一个 launch.json 文件,将文件的内容调整为以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "build",
"stopOnEntry": false,
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "debug"],
"port": 5858
}
]
}

runtimeExecutable 要运行的 shell,runtimeArgs shell 运行时需要的的参数,这个类似 Node.js 的 child_process 模块的 spawn。

其中端口 port 配置需要和 inspect-brk 配置的端口保持一致。stopOnEntry 为 true 表示在运行的第一行代码中添加断点,点击开始调试按钮,即可进入如下界面

然后我们就可以在代码的行数上点击出现小红点,就为打上断点了。

运行 node 调试

由于上面的调试方式需要在 package 里配置,program 将要进行调试的程序的路径,但 program 可以把运行路径写入,那么就可以直接触发 webpack 而进入调试,不用在项目加入任何配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "webpack",
"stopOnEntry": false,
"program": "${workspaceFolder}/node_modules/webpack/bin/webpack"
}
]
}

当然我们还可以把调试放到 chrome 里,这个就不在这里配置了。

所谓的调试都是在 node 上进行了,而已都需要 --inspect-brk开启出一个调试服务。

一些常用配置说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
// 配置名称,将会在启动配置的下拉菜单中显示
"name": "C++ Launch (GDB)",
// 配置类型,这里只能为cppdbg
"type": "cppdbg",
// 请求配置类型,可以为launch(启动)或attach(附加)
"request": "launch",
// 调试器启动类型,这里只能为Local
"launchOptionType": "Local",
// 生成目标架构,一般为x86或x64,
// 可以为x86, arm, arm64, mips, x64, amd64, x86_64
"targetArchitecture": "x86",
// 将要进行调试的程序的路径
"program": "${workspaceRoot}",
// miDebugger的路径,注意这里要与MinGw的路径对应
"miDebuggerPath":"D:\\mingw\\bin\\gdb.exe",
// 程序调试时传递给程序的命令行参数,一般设为空即可
"args": [],
// 设为true时程序将暂停在程序入口处,一般设置为false
"stopAtEntry": false,
// 调试程序时的工作目录,一般为${workspaceRoot}即代码所在目录
"cwd": "${workspaceRoot}",
// 调试时是否显示控制台窗口,一般设置为true显示控制台
"externalConsole": true,
// 调试会话开始前执行的任务,一般为编译程序,c++为g++, c为gcc
"preLaunchTask": "g++"  
//传递给运行时可执行文件的可选参数。
"runtimeArgs":[]

}
1
2
3
4
5
6
7
8
9
10
11
${workspaceRoot} VS Code当前打开的文件夹

${file} 当前打开的文件

${relativeFile} 相对于workspaceRoot的相对路径

${fileBasename} 当前打开文件的文件名

${fileDirname} 所在的文件夹,是绝对路径

${fileExtname} 当前打开文件的拓展名,如.json

github 例子

资料

  1. 这篇资料讲的很详细,上面就不进行详细了。
    Webpack 原理-编写 Plugin
    如何开发一个 plugin 例子

  2. 以下都是钩子的 API,不懂的可以进行查询
    webpack 官网-Tapable
    webpack 官网-compiler API
    webpack 官网-compilation API

  3. 以下都是 vscode 调试相关的,比如怎么调试 webpack 插件或 loader
    vscode 调试 webpack
    vscode 中 launch.json 部分配置项解释
    VS Code 调试