最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Angular Uncaught (in promise): ChunkLoadError: Loading chunk XXXfailed - Stack Overflow

programmeradmin4浏览0评论

I am using angular 12, and I use Service Worker to deploy new versions.

It looks like each time I deploy a new version to production (and for some reason not on staging). Some user receive a bunch of error like

Uncaught (in promise): ChunkLoadError: Loading chunk XXX failed.

There is quite some post about this issue, but it appear to me the solution is case by case.

The difference I have in production compare to stg is just.

I call enableProdMode();

I have the service worker setup like this :

    ServiceWorkerModule.register('ngsw-worker.js', {
      enabled:
        environment.env === Environment.PRODUCTION || environment.env === Environment.STAGING,
      registrationStrategy: 'registerWhenStable:30000',
    }),

And My Config looks like

{
  "$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)"]
      }
    }
  ]
}

And this is how I show the updates (basically a banner that can be ignored) :

@Component({
  selector: 'app-live-update-notifier',
  templateUrl: './live-update-notifierponent.html',
  styleUrls: ['./live-update-notifierponent.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;
  }
}

If you have any clue that could guide to the solution, thank you.

EDIT :

I have a bunch of lazy loaded module all across my app. I thought the reason was that some module where not loaded previous to a new version, and when user navigate to them after deploy they would have this chunk problem. But I tried to do emulate this issue by

  • editing module A and B
  • deploy
  • navigate to module A
  • editing module A and B
  • deploy
  • navigate to old module B from old A, so without updating the version of the APP RES : old B still load normally

so I don't think it s this issue

I am using angular 12, and I use Service Worker to deploy new versions.

It looks like each time I deploy a new version to production (and for some reason not on staging). Some user receive a bunch of error like

Uncaught (in promise): ChunkLoadError: Loading chunk XXX failed.

There is quite some post about this issue, but it appear to me the solution is case by case.

The difference I have in production compare to stg is just.

I call enableProdMode();

I have the service worker setup like this :

    ServiceWorkerModule.register('ngsw-worker.js', {
      enabled:
        environment.env === Environment.PRODUCTION || environment.env === Environment.STAGING,
      registrationStrategy: 'registerWhenStable:30000',
    }),

And My Config looks like

{
  "$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)"]
      }
    }
  ]
}

And this is how I show the updates (basically a banner that can be ignored) :

@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;
  }
}

If you have any clue that could guide to the solution, thank you.

EDIT :

I have a bunch of lazy loaded module all across my app. I thought the reason was that some module where not loaded previous to a new version, and when user navigate to them after deploy they would have this chunk problem. But I tried to do emulate this issue by

  • editing module A and B
  • deploy
  • navigate to module A
  • editing module A and B
  • deploy
  • navigate to old module B from old A, so without updating the version of the APP RES : old B still load normally

so I don't think it s this issue

Share Improve this question edited Oct 28, 2021 at 5:13 Crocsx asked Oct 26, 2021 at 4:10 CrocsxCrocsx 7,60915 gold badges86 silver badges174 bronze badges 5
  • Can you add more verbose error? – Apoorva Chikara Commented Oct 26, 2021 at 5:39
  • @ApoorvaChikara sentry.io/share/issue/32f52037029f43d8b355456ffbfe56de – Crocsx Commented Oct 26, 2021 at 7:04
  • or another sentry.io/share/issue/87e6a59fdc4845f3b31b2abf91aebff4 – Crocsx Commented Oct 26, 2021 at 7:04
  • I know this is because of a mismatch of the hash file that get updated on new deploy, but I do not understand why it happens since It should just ` "installMode": "prefetch",` but not hard delete the cached files – Crocsx Commented Oct 26, 2021 at 7:05
  • I can reproduce it with a quick reload followed by the keyboard command ctrl+shirt+r while hitting the refresh button. Make sure to enable the preserve console log. I'm not sure what end user will do that normally. I noticed that not giving the Angular App enough time to reload fully will cause the issue. – Vyache Commented Oct 11, 2024 at 21:44
Add a comment  | 

2 Answers 2

Reset to default 9 +250

This can be caused by different reasons. But there is a workaround that I used months ago thanks to spock123.

I quote him:

We are of course using the Service Worker service swUpdate to get notifications when a new version is available. However, it seems sometimes not all lazy loaded chunks are updated when there is a new SW version.

Basically the workaround is to reload the application when you face the error bundle not found that causes the error ChunkLoadError.

You have to listen for error globally and if the app faced this error you just reload the page.

Instructions to set global error handler


UPDATE:

I qoute from angular.io:

Handling an unrecoverable state

In some cases, the version of the application used by the service worker to serve a client might be in a broken state that cannot be recovered from without a full page reload.

For example, imagine the following scenario:

  • A user opens the application for the first time and the service worker caches the latest version of the application. Assume the application's cached assets include index.html, main.<main-hash-1>.js and lazy-chunk.<lazy-hash-1>.js.
  • The user closes the application and does not open it for a while.
  • After some time, a new version of the application is deployed to the server. This newer version includes the files index.html, main.<main-hash-2>.js and lazy-chunk.<lazy-hash-2>.js (note that the hashes are different now, because the content of the files changed). The old version is no longer available on the server.
  • In the meantime, the user's browser decides to evict lazy-chunk.<lazy-hash-1>.js from its cache. Browsers might decide to evict specific (or all) resources from a cache in order to reclaim disk space.
  • The user opens the application again. The service worker serves the latest version known to it at this point, namely the old version (index.html and main.<main-hash-1>.js).
  • At some later point, the application requests the lazy bundle, lazy-chunk.<lazy-hash-1>.js.
  • The service worker is unable to find the asset in the cache (remember that the browser evicted it). Nor is it able to retrieve it from the server (because the server now only has lazy-chunk.<lazy-hash-2>.js from the newer version).

In the preceding scenario, the service worker is not able to serve an asset that would normally be cached. That particular application version is broken and there is no way to fix the state of the client without reloading the page. In such cases, the service worker notifies the client by sending an UnrecoverableStateEvent event. Subscribe to SwUpdate#unrecoverable to be notified and handle these errors.

handle-unrecoverable-state.service.ts:

@Injectable()
export class HandleUnrecoverableStateService {
  constructor(updates: SwUpdate) {
    updates.unrecoverable.subscribe(event => {
      notifyUser(
        'An error occurred that we cannot recover from:\n' +
        event.reason +
        '\n\nPlease reload the page.'
      );
    });
  }
}

Configuration for nginx server:

location ~* \.(?:css|js)$ {
    add_header Cache-Control "public, max-age=7200";
}

This will tell the browser to cache the files for 2 hours, hopefully by that time, the browser will have refreshed the page and got the new files.

发布评论

评论列表(0)

  1. 暂无评论