Angular12使用Service Worker部署生产环境后出现ChunkLoadError问题求助
问题描述
我使用Angular 12,通过Service Worker来部署新版本。
每次我向生产环境部署新版本时都会出现该问题(但不知为何staging环境无此问题),部分用户会收到一系列如下报错:
Uncaught (in promise): ChunkLoadError: Loading chunk XXX failed.
关于这个问题的相关帖子不少,但看起来解决方案需要结合具体场景定制。
我这边生产环境和stg环境的差异只有以下几点:
- 生产环境调用了
enableProdMode();
我的service worker注册配置如下:
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.env === Environment.PRODUCTION || environment.env === Environment.STAGING, registrationStrategy: 'registerWhenStable:30000', }),
对应的ngsw配置文件内容如下:
{ "$schema": "../../node_modules/@angular/service-worker/config/schema.json", "index": "/index.html", "assetGroups": [ { "name": "app", "installMode": "prefetch", "resources": { "files": ["/favicon.ico", "/index.html", "/manifest.webmanifest", "/*.css", "/*.js"] } }, { "name": "assets", "installMode": "lazy", "updateMode": "lazy", "resources": { "files": ["/assets/**", "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"] } } ] }
我的版本更新提示逻辑如下(本质是一个可以被用户忽略的提示横幅):
@Component({ selector: 'app-live-update-notifier', templateUrl: './live-update-notifier.component.html', styleUrls: ['./live-update-notifier.component.scss'], }) export class LiveUpdateNotifierComponent implements OnDestroy { hasUpdate = false; isIgnored = false; private subscription = new SubSink(); constructor( @Inject(DOCUMENT) private document: Document, private swUpdate: SwUpdate, private appRef: ApplicationRef, private changeDetectorRef: ChangeDetectorRef ) { const appIsStable$ = this.appRef.isStable.pipe(first((isStable) => isStable === true)); const everySixHours$ = interval(6 * 60 * 60 * 1000); const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$); if (swUpdate.isEnabled) { this.subscription.add( everySixHoursOnceAppIsStable$.subscribe(() => swUpdate.checkForUpdate()), this.swUpdate.available.subscribe((evt) => { if (evt.type === 'UPDATE_AVAILABLE') { this.hasUpdate = true; this.changeDetectorRef.detectChanges(); } }) ); } } ngOnDestroy(): void { this.subscription.unsubscribe(); } updateApp() { this.document.location.reload(); } skipNotification() { this.isIgnored = true; } }
补充说明:
我的应用中存在大量懒加载模块,我最初怀疑问题原因是部分模块在新版本部署前未被加载,用户部署后访问对应模块时就会出现chunk加载错误。但我通过以下步骤尝试复现该问题:
- 修改模块A和模块B
- 部署版本
- 访问模块A
- 再次修改模块A和模块B
- 部署新版本
- 从旧版本的模块A页面跳转访问旧版本的模块B,不更新应用版本,结果:旧版本的模块B依然可以正常加载
因此我认为懒加载模块未提前加载不是该问题的诱因。
希望能得到相关的解决思路指引,非常感谢。
解决思路
1. 优先检查生产环境静态资源缓存配置
这是生产和staging环境最常见的差异点,重点检查三类文件的缓存规则:
ngsw-worker.js、ngsw.json、index.html这三个文件绝对不能设置强缓存,必须配置为Cache-Control: no-cache或者max-age=0,如果这几个文件被CDN/浏览器强缓存,Service Worker会长期无法检测到版本更新,用户一直使用旧版本应用,旧版本chunk被清理后就会触发加载错误。- 带hash的chunk资源建议设置1年以上的强缓存,部署时不要删除旧版本的chunk资源,至少保留24小时以上,给还在使用旧版本的用户预留缓冲时间。
2. 调整Service Worker注册时机
当前你使用的registerWhenStable:30000 是应用稳定后30秒才注册Service Worker,如果用户在这30秒内跳转访问懒加载模块,刚好赶上新版本部署,旧chunk已经被清理,此时请求没有被Service Worker拦截,直接访问服务器就会报错。可以根据业务场景调整注册时机,比如改为应用稳定后立即注册,或者在首屏加载完成后就提前注册。
3. 新增全局ChunkLoadError捕获降级
不管什么原因触发chunk加载失败,都可以在全局错误处理器中统一捕获降级,避免用户直接看到报错:
@Injectable() export class GlobalErrorHandler extends ErrorHandler { handleError(error: any): void { if (error?.message?.includes('ChunkLoadError') || error?.originalError?.message?.includes('ChunkLoadError')) { // 加标记避免重复刷新死循环 if (!sessionStorage.getItem('hasReloadedForChunkError')) { sessionStorage.setItem('hasReloadedForChunkError', 'true'); window.location.reload(); return; } } // 其他错误的处理逻辑 super.handleError(error); } }
使用时在AppModule的providers中注册即可:
{ provide: ErrorHandler, useClass: GlobalErrorHandler }
4. 优化版本更新提示逻辑
当前的更新提示用户可以手动忽略,对于高优先级的版本更新,可以增加强制更新逻辑,或者把6小时的检查周期缩短到更短的时间,比如1小时,更快感知到版本更新。
内容的提问来源于stack exchange,提问作者Crocsx

