在NX工作区的Angular Webpack项目中集成Workbox的构建优化问询
我正尝试将项目中的ServiceWorker从@angular/service-worker切换为Workbox,但相关文档缺少优质示例。我了解Workbox是框架无关工具,但当前构建流程较为繁琐。请问是否有更精简的实现方式?或者我当前的做法是否符合最佳实践?
我的项目结构如下:
apps ⨽ myApp ⊦ sw.ts ← My Workbox SW template ⊦ tsconfig.workbox.json ← Only needed because of my extra webpack build step ⊦ webpack.config.js ← Config of the extra build step ⊦ workbox-build.js ← Config for the workbox-cli when injecting ⊦ project.json ← NX version of the angular cli config dist ⨽ apps ⨽ myApp
sw.ts
import { clientsClaim, skipWaiting } from 'workbox-core'; import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching'; declare const self: ServiceWorkerGlobalScope; skipWaiting(); clientsClaim(); cleanupOutdatedCaches(); precacheAndRoute(self.__WB_MANIFEST); // Will be filled in build time
tsconfig.workbox.json:
{ "compilerOptions": { "typeRoots": ["./typings"], "module": "esnext", "moduleResolution": "node", "importHelpers": true, "target": "es2015", "lib": ["esnext", "webworker"] }, "files": ["./sw.ts"] }
webpack.config.js:
const path = require('path'); module.exports = { mode: 'production', output: { path: path.resolve(__dirname, '../../dist/apps/myApp'), filename: 'sw.js', }, module: { rules: [ { test: /\.ts$/, loader: 'ts-loader', options: { configFile: 'tsconfig.workbox.json' }, }, ], }, resolve: { extensions: ['.js', '.ts'] }, };
workbox-build.js:
const { injectManifest } = require('workbox-build'); const workboxConfig = { globDirectory: 'dist/apps/myApp', globPatterns: ['**/*.{css,eot,html,ico,jpg,js,json,png,svg,ttf,txt,webmanifest,woff,woff2,webm,xml}'], globFollow: true, // follow symlinks globStrict: true, // fail on error globIgnores: [ `**/*-es5.js*`, 'sw.js', ], // Allows to avoid using cache busting for Angular files because Angular already takes care of that! dontCacheBustURLsMatching: new RegExp('.+.[a-f0-9]{20}..+'), maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, // 10MB swSrc: 'dist/apps/myApp/sw.js', swDest: 'dist/apps/myApp/sw.js', }; // Calling the method and output the result injectManifest(workboxConfig).then(({ count, size }) => { console.log(`Generated ${workboxConfig.swDest}, which will precache ${count} files, ${size} bytes.`); });
我的package.json中有如下脚本:
"build:wb": "webpack ./apps/myApp/sw.ts --config ./apps/myApp/webpack.config.js && node ./apps/myApp/workbox-build.js"
该脚本会执行两个任务:1)编译sw.ts;2)将静态资源作为预缓存注入编译后的sw.js。但我认为这种方案较为杂乱。
Angular本身已使用Webpack构建,能否将该构建流程集成到project.json中,使用自定义Webpack配置在Angular Webpack构建完成后自动执行?是否必须在Angular构建完成后手动触发这一额外的构建步骤?
我设想的理想方案是:在project.json中引用自定义Webpack配置,使其作为Angular构建流程的最后一步执行。该Webpack配置会先编译sw.ts,然后收集Angular构建生成的静态文件,将这些文件的预缓存注入编译后的sw.ts,并将结果输出到dist目录中,与Angular编译产物放在一起。这样可以省去额外的tsconfig文件和单独的构建脚本,让整个构建流程更易理解。
一、精简流程,集成到NX/Angular构建链
你当前的做法逻辑通顺,但确实可以进一步精简——核心是把Workbox的编译、预缓存注入整合到Angular的构建流程中,删掉冗余的独立配置和脚本,让整个流程自动化。
1. 移除冗余文件
直接删除tsconfig.workbox.json、独立的webpack.config.js和workbox-build.js,将所有逻辑整合到一个自定义Webpack配置中。
2. 创建集成式Webpack配置(webpack.workbox.js)
在apps/myApp下新建该文件,同时处理TS编译和Workbox预缓存注入:
const path = require('path'); const { injectManifest } = require('workbox-build'); module.exports = { mode: 'production', entry: './sw.ts', output: { path: path.resolve(__dirname, '../../dist/apps/myApp'), filename: 'sw.js', }, module: { rules: [ { test: /\.ts$/, use: [ { loader: 'ts-loader', options: { compilerOptions: { typeRoots: ['./typings'], module: 'esnext', moduleResolution: 'node', importHelpers: true, target: 'es2015', lib: ['esnext', 'webworker'] } } } ] } ] }, resolve: { extensions: ['.js', '.ts'] }, plugins: [ { apply: (compiler) => { // Webpack编译完成后自动执行Workbox预缓存注入 compiler.hooks.afterEmit.tapAsync('WorkboxInject', (compilation, callback) => { injectManifest({ globDirectory: path.resolve(__dirname, '../../dist/apps/myApp'), globPatterns: ['**/*.{css,eot,html,ico,jpg,js,json,png,svg,ttf,txt,webmanifest,woff,woff2,webm,xml}'], globFollow: true, globStrict: true, globIgnores: [ `**/*-es5.js*`, 'sw.js', ], dontCacheBustURLsMatching: new RegExp('.+.[a-f0-9]{20}..+'), maximumFileSizeToCacheInBytes: 10 * 1024 * 1024, swSrc: path.resolve(__dirname, '../../dist/apps/myApp/sw.js'), swDest: path.resolve(__dirname, '../../dist/apps/myApp/sw.js'), }).then(({ count, size }) => { console.log(`Workbox: 预缓存 ${count} 个文件,总大小 ${size} 字节`); callback(); }).catch(err => { console.error('Workbox注入失败:', err); callback(err); }); }); } } ] };
3. 修改project.json,绑定构建流程
在project.json的targets中新增build-sw目标,并让主build目标自动依赖它:
{ "targets": { "build": { // 保留原有的build配置 "dependsOn": ["build-sw"] }, "build-sw": { "executor": "@nrwl/webpack:webpack", "options": { "webpackConfig": "apps/myApp/webpack.workbox.js", "outputPath": "dist/apps/myApp" } } } }
4. 调整脚本(可选)
如果需要单独触发SW构建,可修改package.json脚本:
"build:sw": "nx run myApp:build-sw"
二、关键优化说明
- 合并两步操作:通过Webpack的
afterEmit钩子,在SW编译完成后立即执行预缓存注入,无需分两次调用脚本; - 复用TS配置:直接在
ts-loader中指定WebWorker所需的编译参数,省去独立的tsconfig.workbox.json; - 自动化流程:通过NX的
dependsOn让主构建自动触发SW构建,无需手动执行额外命令,流程更连贯。
三、关于最佳实践
你最初的手动集成是Workbox的标准用法,本身没有问题,但集成到框架构建链中是更优的实践:
- 减少手动操作,降低出错概率;
- 构建逻辑更集中,团队成员无需额外学习独立脚本;
- 便于后续扩展(比如添加路由缓存、离线页面等功能)。
内容的提问来源于stack exchange,提问作者Øystein Amundsen

