自定义 webpack 插件需实现含 apply 方法的类或对象,在其中监听 compiler 或 compilation 钩子;compiler 全局单例,适合 run/done 等全局事件,compilation 每次构建新建,用于操作模块、chunk 和 assets;资源通过 compilation.assets 读写,须返回符合 source()/size() 接口的新对象;异步逻辑应使用 tapasync 或 tappromise 并正确处理回调或 promise。

编写自定义 Webpack 插件,核心是实现一个具有 apply 方法的对象或类,并在其中监听 Webpack 生命周期钩子(如 compilation、emit)来注入逻辑。
插件的基本结构:函数式写法
最简形式是一个带 apply 方法的函数或对象:
class MyPlugin {
apply(compiler) {
// 监听编译初始化阶段
compiler.hooks.run.tap('MyPlugin', (compilation) => {
console.log('Webpack 开始构建');
});
// 监听资源生成完成、即将写入文件系统前
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
// 遍历所有生成的资源
for (const filename in compilation.assets) {
if (filename.endsWith('.js')) {
const source = compilation.assets[filename].source();
// 示例:给每个 JS 文件末尾添加版权注释
compilation.assets[filename] = {
source: () => source + '\n\n// © 2024 MyCompany',
size: () => source.length + 25
};
}
}
callback();
});
}
}
// 在 webpack.config.js 中使用
module.exports = {
plugins: [new MyPlugin()]
};
区分 compiler 和 compilation 钩子
Webpack 插件中两个关键上下文:
-
compiler:代表整个 Webpack 实例,生命周期长(一次构建只创建一次),适合监听全局事件(
run、done、watchRun) -
compilation:代表一次具体的构建过程(开发模式下每次保存都会新建),包含模块、chunk、asset 等信息,钩子如
buildModule、succeedModule、optimizeChunks、emit都在此层级
常用写法是在 compiler.hooks.compilation.tap 中获取 compilation 实例,再对其挂载更细粒度的钩子:
compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
compilation.hooks.optimizeChunkAssets.tapAsync('MyPlugin', (chunks, callback) => {
chunks.forEach(chunk => {
chunk.files.forEach(filename => {
if (filename.endsWith('.js')) {
// 修改 JS chunk 的内容(需通过 compilation.assets 操作)
}
});
});
callback();
});
});
操作资源:读取、修改、新增文件
打包产物(assets)都存在 compilation.assets 对象中,类型为 { [filename]: { source(), size() } }。你可以:
-
读取内容:
compilation.assets[filename].source() - 替换内容:直接赋值新的 asset 对象(如上例)
-
新增文件:直接往
compilation.assets添加新 key,例如生成version.json -
删除文件:用
delete compilation.assets[filename]
注意:不要直接修改原始 asset 的 source 字符串(它是只读的),必须返回一个符合 source() 和 size() 接口的新对象。
异步与错误处理:正确使用 tapAsync / tapPromise
当插件逻辑含异步操作(如读写文件、请求 API),需选对钩子类型并调用回调或返回 Promise:
-
tap→ 同步钩子 -
tapAsync→ 异步钩子,最后一个参数是callback(err?) -
tapPromise→ 返回 Promise,Webpack 会 await 它
示例(异步生成版本文件):
compiler.hooks.emit.tapAsync('MyPlugin', async (compilation, callback) => {
try {
const version = await fetchVersionFromCI(); // 假设是异步获取
compilation.assets['version.json'] = {
source: () => JSON.stringify({ version }, null, 2),
size: () => 30
};
callback();
} catch (err) {
callback(err);
}
});
不复杂但容易忽略:确保插件名称唯一(tap 第一个参数)、合理选择钩子时机、避免意外覆盖已有 asset 或引发内存泄漏。调试时可打印 compilation.getStats().toJson() 查看当前状态。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











