Webpack 插件就是一个具有apply方法的对象,apply方法会被webpack compiler调用,并且可以在整个编译生命周期内访问compiler对象。
以下是一个简单的Webpack插件的示例,这个插件的目的是在打包完成后输出一段文字:
/*定义一个插件*/
class MyPlugin {
// 在构造方法中获取用户给该插件传入的配置
constructor(options){
this.options = options;
}
// 应用插件的方法
apply(compiler) {
// 在指定的生命周期钩子函数添加回调函数
compiler.hooks.done.tap('MyPlugin', (stats) => {
console.log('整个Webpack打包结束');
});
}
}
module.exports = MyPlugin;
接下来,我们在webpack配置文件require我们自定义的插件,然后添加到plugins数组中:
const MyPlugin = require('./MyPlugin.js');
module.exports = {
//...
plugins: [
new MyPlugin({
name: 'MyPlugin'
}),
],
};
这样,在Webpack打包结束时,我们就会在console中看到 “整个Webpack打包结束” 的输出,这说明我们的插件成功运行了。
自定义插件的实现可以根据实际业务需要进行更复杂的操作,包括操作编译(compilation)对象上的文件,监听各种编译事件等。这种灵活性使得Webpack可以进行各种各样的自定义扩展。
Webpack 编译对象(compilation)中包含当前的模块资源、编译生成资源、模块文件改变标志、文件依赖等内容。在 Webpack 的编译生命周期中,不同的生命周期的回调函数会接收到编译对象参数,这样我们就可以在插件内部对这些对象进行操作。
例如,你可以在 emit 阶段,遍历所有待写入的文件,进行一些自定义的修改:
class MyPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
// 遍历 compilation 对象中所有待写入的文件
for (let filename in compilation.assets) {
// 获取文件内容
let content = compilation.assets[filename].source();
// 执行一些自定义操作,比如改变某个文件的内容
if (filename === 'target.js') {
content = content.replace('var a = 1;', 'var a = 2;')
}
// 再把处理后的内容写回去
compilation.assets[filename] = {
source() {
return content
},
size() {
return content.length
}
}
}
callback();
});
}
}
在这个例子中,我们使用 emit 钩子遍历所有待写入的文件,找到名称为 target.js 的文件,然后替换它的内容。 compilation.assets 是一个键值对集合,键是文件名,值是代表文件内容的对象。文件内容对象包括 source 方法(返回文件内容)和 size 方法(返回文件内容大小)。所以在替换文件内容后,我们需要把修改后的内容和内容的大小重新设置回 compilation.assets 中,否则无法正确地写入文件。
Webpack 提供的插件机制相当灵活和强大,你可以在插件中对编译过程的几乎每个步骤进行干预和操作,而不仅仅是操作文件,还可以读取或修改模块,依赖,chunks 等内容。使用这种方式可以对构建过程进行高度自定义,实现各种需求。这就是 Webpack 能成为前端构建工具的一大原因。