为什么我的Webpack插件在emit阶段无法修改输出文件内容?

博文 阅读 31

我按网上的教程写了个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)测试过,字符串拼接是成功的,这到底是哪里出错了?

我来解答 赞 5 收藏
二维码
手机扫码查看
1 条解答
长孙卫红
你这个插件的问题出在两处,一个是回调调用的位置不对,另一个是资源对象的实现方式有问题。

首先,callback() 被你放在了 forEach 里面循环执行,这会导致它在第一个文件处理完就直接调用了,后面的文件根本没处理完。更严重的是,异步钩子还没处理完你就提前结束了,Webpack 就认为 emit 阶段完成了,导致修改不生效。

其次,你直接给 assets[file] 赋了一个自定义对象,虽然实现了 source 和 size 方法,但缺少一些必要的属性,Webpack 内部可能不会正确识别这种 asset。

正确的做法应该是等所有文件处理完后再统一调用 callback,并且使用 webpack 提供的 RawSource 来包装内容。把你的插件改成这样:

class MyPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
const assets = compilation.assets
for (const file in assets) {
if (!/.js$/.test(file)) continue
const source = assets[file].source()
const newSource = '// Copyright 2024n' + source
// 使用 webpack 的源码包装器
assets[file] = new compiler.webpack.sources.RawSource(newSource)
}
// 所有操作完成后只调一次 callback
callback()
})
}
}


注意两点:第一,RawSource 在新版 Webpack 中位于 compiler.webpack.sources.RawSource,别自己手动造轮子;第二,callback 必须在所有异步操作结束后调用,不能放循环里。

另外建议你在本地调试时加个 console.log(Object.keys(compilation.assets)) 看看实际输出的文件名,有时候打包出来的 JS 不叫你预期的名字,比如加了 hash 的情况,容易导致正则匹配失效。我之前在服务端做自动化构建时也踩过这坑,看着逻辑没问题就是没改到,结果发现文件名是 app.abc123.js,正则没匹配上。
点赞 3
2026-02-09 20:01