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() { this.hooks = { SyncHook: new SyncHook(["name", "age"]), }; 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() { this.hooks = { SyncHook: new AsyncParallelHook(["name", "age"]), }; 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() { this.hooks = { SyncHook: new AsyncSeriesHook(["name", "age"]), }; this.hooks.SyncHook.tapAsync("event1", (name, age, callback) => { setTimeout(() => { console.log("event1 ---- ", name, age);
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"); 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 { 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 { 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(); } 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); this.ssh.dospose(); callback(); } ); }
async connectServer() { await this.ssh.connect({ host: "xxx", username: "xxx", password: "xxx", }); }
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; } 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); this.ssh.dospose(); callback(); } ); }
async connectServer() { const { host, username, password } = this.options; await this.ssh.connect({ host, username, password, }); }
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 的源码