Tapable

webpack 有两个非常重要的类:Compiler 和 Compilation

他们通过注入插件的方式,来监听 webpack 的所有生命周期

插件的注入离不开各种各样的 Hook,这些来自 Tapable 库中的各种 Hook 的实例

Tapable 是官方编写和维护的一个库;

Tapable 管理着需要的 Hook,这些 Hook 可以被应用到插件中

使用过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const { SyncHook } = require("tapable");
class myCompiler {
constructor() {
// 创建hook对象
this.hooks = {
SyncHook: new SyncHook(["name", "age"]),
};
// 注册hook事件
this.hooks.SyncHook.tap("event1", (name, age) => {
console.log("event1 ---- ", name, age);
});
this.hooks.SyncHook.tap("event2", (name, age) => {
console.log("event2 ---- ", name, age);
});
}
}
const compiler = new myCompiler();
// 触发事件
compiler.hooks.SyncHook.call("xxx", 18);

Hook 分类

同步和异步的:

  • 以 sync 开头的,是同步的 Hook
  • 以 async 开头的,两个事件处理回调,不会等待上一次处理回调结束后再执行下一次回调

其他的类别

  • bail:当有返回值时,就不会执行后续的事件触发了
  • Loop:当返回值为 true,就会反复执行该事件,当返回值为 undefined 或者不返回内容,就退出事件
  • Waterfall:当返回值不为 undefined 时,会将这次返回的结果作为下次事件的第一个参数
  • Parallel:并行,会同时执行
  • Series:串行,会等待上一是异步的 Hook

异步 Parallel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const { AsyncParallelHook } = require("tapable");
class myCompiler {
constructor() {
// 创建hook对象
this.hooks = {
SyncHook: new AsyncParallelHook(["name", "age"]),
};
// 注册hook事件
this.hooks.SyncHook.tapAsync("event1", (name, age) => {
setTimeout(() => {
console.log("event1 ---- ", name, age);
}, 3000);
});
this.hooks.SyncHook.tapAsync("event2", (name, age) => {
setTimeout(() => {
console.log("event2 ---- ", name, age);
}, 3000);
});
}
}
const compiler = new myCompiler();
// 触发事件
compiler.hooks.SyncHook.callAsync("xxx", 18);

异步 Series callback

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
const { AsyncSeriesHook } = require("tapable");
class myCompiler {
constructor() {
// 创建hook对象
this.hooks = {
SyncHook: new AsyncSeriesHook(["name", "age"]),
};
// 注册hook事件
this.hooks.SyncHook.tapAsync("event1", (name, age, callback) => {
setTimeout(() => {
console.log("event1 ---- ", name, age);
/**
* AsyncSeriesHook要多一个callback参数,表示执行后面的时间监听
* 不callback默认执行完第一个就会结束
*/
callback();
}, 3000);
});
this.hooks.SyncHook.tapAsync("event2", (name, age) => {
setTimeout(() => {
console.log("event2 ---- ", name, age);
}, 3000);
});
}
}
const compiler = new myCompiler();
// 触发事件
compiler.hooks.SyncHook.callAsync("xxx", 18);

异步的也可以结合 bail loop waterfall 使用

Plugin 执行流程

我们自定义 webpack.config.js,里面的配置会作为 options 参数传递到 webpack 中

如果配置中有 plugins,那么就会通过 options.plugins 拿到

遍历 options.plugibs

  • 如果 plugin 是个函数,就会执行 plugin.call(compiler)
  • 如果 plugin 是个对象(类),就会执行 plugin.apply(compiler)

第一:在 webpack 函数的 createCompiler 方法中,注册了所有的插件;

第二:在注册插件时,会调用插件函数或者插件对象的 apply 方法;

第三:插件方法会接收 compiler 对象,我们可以通过 compiler 对象来注册 Hook 的事件;

第四:某些插件也会传入一个 compilation 的对象,我们也可以监听 compilation 的 Hook 事件

案例 自动上传打包后文件到服务器

基本搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const path = require("path");
const htmlWebpackPlugin = require("html-webpack-plugin");
// 两种导入都可以
// const AutoUploadPlugin = require("./plugins/AutoUploadPlugin");
const { AutoUploadPlugin } = require("./plugins/AutoUploadPlugin");
module.exports = {
mode: "development",
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, "./build"),
filename: "bundle.js",
},
module: {},
plugins: [new htmlWebpackPlugin(), new AutoUploadPlugin()],
};
1
2
3
4
5
6
7
8
class AutoUploadPlugin {
// 会把compiler传递进来
apply(compiler) {
console.log("AutoUploadPlugin执行");
}
}
module.exports = AutoUploadPlugin;
module.exports.AutoUploadPlugin = AutoUploadPlugin;

获取打包后文件夹路径

1
2
3
4
5
6
7
8
9
10
11
12
class AutoUploadPlugin {
// 会把compiler传递进来
apply(compiler) {
compiler.hooks.afterEmit.tapAsync("AutoPlugin", (compilation, callback) => {
// 获取输出文件夹路径
const outputPath = compilation.outputOptions.path;
console.log(outputPath);
});
}
}
module.exports = AutoUploadPlugin;
module.exports.AutoUploadPlugin = AutoUploadPlugin;

连接远程服务器并上传

pnpm add node-ssh

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const { NodeSSH } = require("node-ssh");
class AutoUploadPlugin {
constructor() {
this.ssh = new NodeSSH();
}
// 会把compiler传递进来
apply(compiler) {
compiler.hooks.afterEmit.tapAsync(
"AutoPlugin",
async (compilation, callback) => {
const remotePath = "/root/test/";
// 获取输出文件夹路径
const outputPath = compilation.outputOptions.path;
// 连接服务器
await this.connectServer();
// 删除之前的文件
this.ssh.execCommand(`rm -rf ${remotePath}*`);
//文件上传
await this.upload(outputPath, remotePath);
// 关闭ssh
this.ssh.dospose();
callback();
}
);
}

async connectServer() {
await this.ssh.connect({
host: "xxx",
username: "xxx",
password: "xxx",
});
}
/**
*
* @param {*} localPath 本地路径
* @param {*} remotePath 远程路径
*/
async upload(localPath, remotePath) {
const status = await this.ssh.putDirectory(localPath, remotePath, {
// 递归上传
recursive: true,
// 并发上传
concurrency: 10,
});
if (status) console.log("文件上传成功");
}
}
module.exports = AutoUploadPlugin;
module.exports.AutoUploadPlugin = AutoUploadPlugin;

服务器是有的,但没有配置 Nginx,就不演示了,大致就是这个流程

问题 (配置选项)

现在服务器相关配置是写死的,应该能自定义

webpack.config.js

1
2
3
4
5
6
7
8
9
plugins: [
new htmlWebpackPlugin(),
new AutoUploadPlugin({
host: "1.2.3.4",
username: "xxx",
password: "123456",
remotePath: "/rrot",
}),
],
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
const { NodeSSH } = require("node-ssh");
class AutoUploadPlugin {
constructor(options) {
this.ssh = new NodeSSH();
// 自定义配置参数
this.options = options;
}
// 会把compiler传递进来
apply(compiler) {
compiler.hooks.afterEmit.tapAsync(
"AutoPlugin",
async (compilation, callback) => {
// 获取输出文件夹路径
const outputPath = compilation.outputOptions.path;
// 连接服务器
await this.connectServer();
// 删除之前的文件
this.ssh.execCommand(`rm -rf ${this.options.remotePath}*`);
//文件上传
await this.upload(outputPath, remotePath);
// 关闭ssh
this.ssh.dospose();
callback();
}
);
}

async connectServer() {
const { host, username, password } = this.options;
await this.ssh.connect({
host,
username,
password,
});
}
/**
*
* @param {*} localPath 本地路径
* @param {*} remotePath 远程路径
*/
async upload(localPath, remotePath) {
const status = await this.ssh.putDirectory(localPath, remotePath, {
// 递归上传
recursive: true,
// 并发上传
concurrency: 10,
});
if (status) console.log("文件上传成功");
}
}
module.exports = AutoUploadPlugin;
module.exports.AutoUploadPlugin = AutoUploadPlugin;

有时间要看一下服务器相关配置和 webpack 的源码