You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

__WB_MANIFEST注入前压缩导致service-worker.js兼容性异常

问题分析与解决方案:CompressionWebpackPlugin 与 Workbox InjectManifest 执行顺序冲突

最近在配置 Workbox 和 CompressionWebpackPlugin 时遇到了一个棘手的问题:压缩后的 service-worker.js.gz 和最终部署的 service-worker.js 内容不一致,导致 PWA 预缓存功能失效。

问题现象

我在 compression-webpack-plugin 的 filename 回调里加了 console.log 来调试,发现了关键的执行顺序问题:

  • 构建初期,CompressionPlugin 就会触发日志,此时 sw.js 刚被复制到 service-worker.js,但文件里还没有 self.__WB_MANIFEST 这个预缓存标记——这个标记要等 Workbox 的 precacheAndRoute 注入真实的预缓存条目数组。
  • 构建收尾阶段,Workbox 才会把包含资源路径和版本号的预缓存数组注入到 service-worker.js,但这一步不会触发 CompressionPlugin 的日志,说明压缩操作早就完成了。
  • 最终结果就是:压缩版的 service-worker.js.gz 对应的是未注入预缓存信息的“半成品”,而实际运行的是已经注入后的完整文件,两者不匹配,直接导致预缓存逻辑失效。

相关配置

Webpack 插件配置

{
  plugins: [
    // ... 其他插件
    new WorkboxPlugin.InjectManifest({
      swSrc: './src/setup/sw.js',
      swDest: 'service-worker.js',
      exclude: [/\.(gz|br)$/],
      maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
    }),
    // ... 其他插件
    new CompressionPlugin({
      filename(pathData) {
        console.log(pathData)
        return '[path][base].gz'
      },
      algorithm: 'gzip',
      minRatio: 0.8,
      test: /\.(js|css|html|svg|wasm)$/,
    })
  ]
}

package.json 依赖版本

{
  "webpack": "^5.4.0",
  "compression-webpack-plugin": "^6.0.5",
  "workbox-webpack-plugin": "^6.0.0-alpha.3"
}

解决方案

这个问题的核心是插件执行时机不匹配:Workbox 的 InjectManifest 在异步钩子中生成最终的 service-worker.js,而 CompressionPlugin 默认在同步钩子中执行,导致压缩提前完成。这里提供几个可行的解决思路:

1. 升级依赖到稳定版本

你当前使用的 workbox-webpack-plugin 是 alpha 测试版,钩子时机可能存在问题。建议升级到稳定的 v6 版本(比如 ^6.6.0),同时将 compression-webpack-plugin 升级到与 Webpack 5 兼容的版本(比如 ^10.0.0),新版本通常会修复这类钩子顺序的兼容性问题。

2. 调整插件执行顺序与钩子类型

如果不想升级依赖,可以尝试:

  • 确保 CompressionPlugin 位于 WorkboxPlugin.InjectManifest 之后(虽然你已经这么做了,但可以确认是否钩子类型冲突)。
  • 手动自定义插件,在 afterEmit 钩子中专门压缩最终生成的 service-worker.js:
const fs = require('fs');
const zlib = require('zlib');

// 添加到 plugins 数组末尾
{
  apply(compiler) {
    compiler.hooks.afterEmit.tapAsync('CompressServiceWorker', (compilation, callback) => {
      const swFileName = 'service-worker.js';
      const swPath = `${compilation.outputOptions.path}/${swFileName}`;
      const swGzPath = `${swPath}.gz`;

      // 读取原始文件并压缩
      fs.createReadStream(swPath)
        .pipe(zlib.createGzip())
        .pipe(fs.createWriteStream(swGzPath))
        .on('finish', () => {
          // 将压缩后的文件加入编译资产,确保能被正确输出
          compilation.assets[`${swFileName}.gz`] = {
            source: () => fs.readFileSync(swGzPath),
            size: () => fs.statSync(swGzPath).size
          };
          callback();
        })
        .on('error', (err) => {
          console.error('压缩 service-worker.js 失败:', err);
          callback(err);
        });
    });
  }
}

同时,在原有的 CompressionPlugin 配置中添加 exclude: /service-worker\.js$/,避免重复压缩。

3. 验证钩子执行顺序

如果还是不确定问题所在,可以添加一个调试插件,打印钩子执行顺序:

// 添加到 plugins 数组最前面
{
  apply(compiler) {
    compiler.hooks.emit.tap('DebugEmitOrder', () => {
      console.log('=== Emit 阶段开始 ===');
    });
    compiler.hooks.afterEmit.tap('DebugAfterEmitOrder', () => {
      console.log('=== AfterEmit 阶段开始 ===');
    });
  }
}

通过日志可以清晰看到 Workbox 和 CompressionPlugin 各自在哪个阶段执行,进而针对性调整。

内容的提问来源于stack exchange,提问作者amirhe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 20:32:30