为什么我的Webpack插件在emit阶段无法修改输出文件内容?
我按网上的教程写了个Webpack插件,想在emit阶段给JS文件添加版权信息。代码看起来没问题,但生成的文件内容完全没变,这是怎么回事啊?
我写了这样的插件逻辑:
class MyPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
Object.keys(compilation.assets).forEach(file => {
if (/.js$/.test(file)) {
const source = compilation.assets[file].source()
const newSource = '// Copyright 2024n' + source
compilation.assets[file] = {
source: () => newSource,
size: () => newSource.length
}
}
callback()
})
})
}
}
然后在webpack.config.js里注册了插件,执行构建后文件确实生成了,但检查文件内容发现版权信息根本没有被添加进去。用console.log(newSource)测试过,字符串拼接是成功的,这到底是哪里出错了?
首先,callback() 被你放在了 forEach 里面循环执行,这会导致它在第一个文件处理完就直接调用了,后面的文件根本没处理完。更严重的是,异步钩子还没处理完你就提前结束了,Webpack 就认为 emit 阶段完成了,导致修改不生效。
其次,你直接给 assets[file] 赋了一个自定义对象,虽然实现了 source 和 size 方法,但缺少一些必要的属性,Webpack 内部可能不会正确识别这种 asset。
正确的做法应该是等所有文件处理完后再统一调用 callback,并且使用 webpack 提供的 RawSource 来包装内容。把你的插件改成这样:
注意两点:第一,RawSource 在新版 Webpack 中位于
compiler.webpack.sources.RawSource,别自己手动造轮子;第二,callback 必须在所有异步操作结束后调用,不能放循环里。另外建议你在本地调试时加个 console.log(Object.keys(compilation.assets)) 看看实际输出的文件名,有时候打包出来的 JS 不叫你预期的名字,比如加了 hash 的情况,容易导致正则匹配失效。我之前在服务端做自动化构建时也踩过这坑,看着逻辑没问题就是没改到,结果发现文件名是 app.abc123.js,正则没匹配上。