在 webpack 中, plugin 主要做的事情围绕着整一个构建的过程。基于 tapable 的钩子机制,开发者可以在众多构建环节中注册相关的事件,依托于 webpack 提供的构建上下文,来对打包结果进行一些处理
plugin简要介绍
每一个 plugin 都是一个类,主要关注这个类的两个方法,一个是构造函数 constructor ,还有一个是 apply 方法。在 constructor 中可以获得配置 plugin 时传入的参数,而 apply 方法则是 webpack 会调用的方法。每个插件都有两个重要的钩子,一个是compiler钩子,还有一个是compilation钩子。
complier 是 webpack 构建启动时产生的,只有一个,它可以访问构建的各种配置等等。complier hooks有三种类型,分别是同步钩子、异步钩子、异步 promise 钩子。
compilation 是对资源的一次构建,可以有多个,它可以访问构建过程中的资源。compilation主要有以下属性:
modules 可以访问所有模块,打包的每一个文件都是一个模块。
chunks chunk 即是多个 modules 组成而来的一个代码块。入口文件引入的资源组成一个 chunk,通过代码分割的模块又是另外的 chunk。
assets 可以访问本次打包生成所有文件的结果。
hooks 可以注册 tapable 的不同种类 Hook,用于在 compilation 编译模块阶段进行逻辑添加以及修改。
下面以 complier 为例,介绍钩子事件时如何注册的。
代码示例:
js
// 同步插件
class aosPlugin {
apply(compiler) {
compiler.hooks.xxx.tap('aosPlugin',()=>{
console.log('aosPlugin')
})
}
}
//异步插件
class aosPlugin2 {
apply(compiler) {
compiler.hooks.xxx.tap('aosPlugin2', (complication, callback) => {
console.log('aosPlugin2')
callback()
})
}
}
实现一个插件
这个插件主要做下面几件事情:
• 注册 emit 钩子
• 获取即将输出的资源,筛选出 html 文件
• 把注释内容添加到文件中
• 输出资源
开发一个 plugin ,首先要想清楚的问题是,这个插件是在什么时候执行的。在我学习以及开发的过程中, emit (资源输出前)和 afterEmit (资源输出后)这两个钩子是最经常用到的。当然我的意思不是这两个钩子就是开发 plugin 最常用到的两个钩子,而是想说我们想开发一个 plugin ,必须先想好执行的时机,选择好注册事件的钩子
这个钩子要做的是,对于我们输出的资源,希望打上一些注释。我们上次在 loader 也做过同样的事情,但是 loader 处理过后的资源可能会被合并、压缩。对于注释可能会被删掉
代码示例:
js
const path = require("path");
const os = require("os");
function getLocalIP() {
const interfaces = os.networkInterfaces();
for (const interfaceName in interfaces) {
let interfacess = interfaces[interfaceName];
for (let i = 0; i < interfacess.length; i++) {
const { address, family, internal } = interfacess[i];
// 过滤出IPv4地址,并排除内部地址
if (family === "IPv4" && !internal) {
return address;
}
}
}
return null; // 无法获取IP时返回null
}
class MyPlugin {
apply(compiler) {
compiler.hooks.emit.tap("MyPlugin", (compilation) => {
const { assets } = compilation;
const files = Object.keys(assets).filter((filename) => {
const exts = ["html"];
const arr = filename.split(".");
const fileExt = arr[arr.length - 1];
return exts.includes(fileExt);
});
const prefix = `
<!--
date: ${new Date().toLocaleString()}
IP: ${getLocalIP() || "unknown"}
程序出Bug了?
∩∩
(´・ω・)
_| ⊃/(___
/ └-(___/
 ̄ ̄ ̄ ̄ ̄ ̄ ̄
算了反正不是我写的
⊂⌒/ヽ-、_
/⊂_/____ /
 ̄ ̄ ̄ ̄ ̄ ̄ ̄
-->
`;
files.forEach((file) => {
const source = assets[file].source();
const newContent = prefix + source;
assets[file] = {
source() {
return newContent;
},
size() {
return newContent.length;
},
};
});
});
}
}
module.exports = MyPlugin;
使用plugin
vue.config.js文件中添加plugin
js
const MyPlugin = require("./plugin");
module.exports = {
...
configureWebpack: {
plugins: [
new MyPlugin(),
],
},
};
webpack 输出阶段会发生的事件钩子
should-emit 所有需要输出的文件已经生成,询问插件有哪些文件需要输出,有哪些不需要输出
emit 确定好要输出哪些文件后,执行文件输出,可以在这里获取和修改输出的内容
after-emit 文件输出完毕
done 成功完成一次完整的编译和输出流程
failed 如果在编译和输出的流程遇到异常,导致 webpack 退出,就会直接跳转到本步骤,插件可以在本事件中获取具体错误原因