web-dev-qa-db-fra.com

'Jeton inattendu <' sur chaque nouvelle version de angular production PWA jusqu'à l'actualisation du site

Je sais qu'il y a des questions similaires comme ça jeton inattendu après angular 2 production mais cela ne répond pas réellement à ma question.

Fondamentalement, j'ai une angular et c'est une PWA, maintenant chaque fois que je fais une nouvelle production, la première fois que je visite le site, je reçois une erreur comme ça

enter image description here

puis dès que je rafraîchis la page, le site fonctionne normalement.

Cela se produit sur n'importe quel navigateur/appareil

Maintenant, je soupçonne que chaque fois que l'application est mise à jour, le fichier js groupé principal change, et le PWA a mis en cache l'ancien et l'application essaie toujours d'utiliser le nouveau.

La structure de mon projet est la suivante

J'ai un module racine qui charge paresseusement les autres modules maintenant le premier module qui est chargé est mon module de compte afin que l'utilisateur puisse se connecter

c'est mon root-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';

const routes: Routes = [
    { path: '', redirectTo: '/account/login', pathMatch: 'full' },
    {
        path: 'account',
        loadChildren: 'account/account.module#AccountModule',
        data: { preload: true }
    }
];

@NgModule({
    imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })],
    exports: [RouterModule],
    providers: []
})
export class RootRoutingModule {

}

donc, techniquement, le premier module auquel l'utilisateur accède est le module de compte, mais il doit évidemment être redirigé depuis le module racine.

Donc, ce que j'ai fait était dans mon module de composant racine, je vérifie si le Service Worker a besoin d'une mise à jour comme ça ...

root.component.ts

@import { SwUpdate } from '@angular/service-worker'
...

export class...

constructor(
    private _sw: SwUpdate
) {
    if (this._sw.isEnabled) {
        this._sw.available
            .subscribe(() => {
                this._sw.activateUpdate()
                    .then(() => {
                        window.location.reload(true);
                    });
            });
    }
}     

maintenant, cela devrait vérifier s'il y a une mise à jour, puis actualiser la page .. mais cela ne fonctionne pas.

c'est mon ngsw-config.json

{
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "App",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/*.css",
          "/*.js",
          "!/main*.js"
        ]
      }
    }, {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }
    }
  ]
}

comme vous pouvez le voir, j'exclus le main.js fichier de morceau qui devrait éliminer ce problème ... mais pas

Maintenant, quand je vérifie l'onglet réseau après une nouvelle version, c'est ce que j'obtiens

enter image description here Cette image était avant d'exclure le fichier main.js

c'est ce que mon main.js les appels ressemblent après que je les ai exclus du ngsw.config

enter image description here

J'ai ensuite pensé à essayer d'attraper cette erreur en utilisant mon gestionnaire d'erreurs sentinelle comme ça ..

export class RavenErrorHandler implements ErrorHandler {
  handleError(err: any): void {
    console.log('error', err);
    if (err.toLowerCase().includes('token <')) {
        window.location.reload(true);
    } else {
        Raven.captureException(err);
    }
  }
}

mais cela ne fonctionne pas, je reçois l'erreur en sentinelle mais l'application ne se recharge pas ??

Maintenant, j'ai fait des tests.Si vous visitez le site après une nouvelle version en mode privé, vous n'obtiendrez jamais l'erreur, cela ne semble se produire que si vous avez déjà visité le site, ce qui me fait penser que c'est un problème de mise en cache

Mon application est hébergée sur Azure et mon application utilise Angular 7.27

Toute aide serait appréciée!

16
Smokey Dawson

Le problème est causé par le ServiceWorker installé, qui est celui qui interfère et met en cache toutes les réponses, y compris celles que vous venez de actualisé.

Plus d'informations sur les travailleurs de service: MDN - Utilisation des travailleurs de service

https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers

C'est pourquoi window.location.reload() ne fonctionne pas, car il fait une nouvelle requête mais cette requête est détournée par le ServiceWorker et au lieu de laisser la vraie demande passer au vrai serveur et récupérer la vraie réponse (main.js et actifs mis à jour), il retourne simplement à votre application une réponse mise en cache, qui est l'ancien main.js et non celui mis à jour et c'est pourquoi il échoue.

Plus précisément, le ServiceWorker met en cache le main.js et d'autres éléments, générés chaque fois que vous modifiez votre code. Main.js lorsqu'il est chargé à son tour, il charge paresseusement (fait une demande http à) d'autres morceaux de votre application (par exemple le 5.xxxxx.js).

Lorsque vous apportez une modification à votre code, et même si tous vos morceaux sont mis à jour, y compris main.js et le 5.xxxxxx.js morceau, le navigateur exécute toujours l'ancien main.js. Cette ancienne main.js fait référence à l'ancienne paire de 5.xxxxxx.js qui n'existe plus. À ce stade, si vous effectuez un rechargement () par programme, le ServiceWorker installé répond avec le plus ancien main.js qui essaie à son tour de charger paresseusement l'ancienne version de 5.xxxxx.js du serveur qui n'existe pas et obtient donc une réponse d'une erreur 404 html. D'où le '<' exception de jeton (c'est en fait le premier caractère de la balise de la page d'erreur 404).

Ceci est évident à partir de la capture d'écran du panneau réseau . Si vous examinez la capture d'écran, vous verrez que le main.js et tous les autres actifs sont livrés à partir de (from service worker), ce qui signifie qu'il s'agit des anciennes versions mises en cache et différentes des fichiers mis à jour réels qui existent maintenant sur votre serveur Web.

Pour résoudre ce problème, le ServiceWorker doit être configuré pour pas mettre en cache le main.js et au lieu de cela, laissez toujours cette demande passer au serveur réel afin de récupérer toute version mise à jour de main.js qui à son tour chargera paresseusement la mise à jour 5.xxxx.js bloc qui existe actuellement sur le serveur Web.

Voici comment configurer le ngsw-config.json de ServiceWorker pour spécifiquement ne pas mettre en cache le main.js fichier (en supposant qu'il soit du modèle main.xxxxxxx.js) afin de résoudre ce problème:

{
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "App",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/*.css",
          "/*.js",
          "/!main*.js"
        ]
      }
    }, {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }
    }
  ]
}

courir ng build --prod pour reconstruire votre application et la tester.

MISE À JOUR:

Pour vous assurer qu'il ne s'agit pas d'un problème de mise en cache du navigateur, passez true comme premier argument à la méthode window.location.reload (). Cela demande au rechargement d'être exécuté sans actifs mis en cache par le navigateur:

root.component.ts

                    .then(() => {
                        window.location.reload(true);
                    });

MISE À JOUR 2:

Selon la documentation d'Angular, le Service Worker déjà installé sur les navigateurs des clients est mis en cache. Si vous mettez à jour le code Service Worker (comme vous l'avez fait récemment), le nouveau code Service Worker doit être mis à jour dans tous les navigateurs des clients qui l'ont déjà installé. Angular indique qu'il y a un contrôle périodique, mais il ne le précise pas, qui vérifiera si le technicien de service lui-même a besoin d'une mise à jour et le fera. https://angular.io/guide/service-worker-devops#service-worker-updates

Peut-être que la gestion du problème uniquement dans la production et non en développement ou avec un mode de navigateur privé (ardoise de navigateur propre sans logiciel SW installé), semble que ce soit le cas.

Pour vérifier cela, vous devez comparer le logiciel de navigation existant qui s'exécute sur la page à l'aide des outils de développement de Chrome, avec le code de logiciel mis à jour qui doit être installé. S'ils sont différents, cela signifie qu'il n'a pas encore été mis à jour et c'est ce qui cause le problème.

Vous pouvez le faire en utilisant un navigateur chrome qui présente ce problème et en allant dans Outils Chrome Dev -> onglet Application -> Service Workers (en haut de panneau de gauche) et il y aura le SW qui est enregistré dans le navigateur. Cliquez sur le lien Source et il ouvrira le code réel du SW. Comparez le code ligne par ligne (ou utilisez un utilitaire diff) avec le code SW mis à jour qui devrait être en cours d'exécution.

11
J. Koutsoumpas

Vérifiez vos en-têtes de cache. Chrome n'écoute pas toujours sans cache. Si vous avez visité le site récemment chrome chargera index.html à partir du cache. Causant l'erreur.

Nous avons changé notre index.html en no-store et cela a semblé résoudre le problème. https://gertjans.home.xs4all.nl/javascript/cache-control.html

6
Raisins

Je ne suis pas un expert pour angular. Le mieux que je puisse faire est de vous aider à résoudre le problème, à vous aider à déboguer.

  1. Si vous voulez juste que l'erreur disparaisse (et que vous avez le contrôle sur le serveur qui sert votre index.html), Je suis sûr que cela peut être résolu en définissant ces en-têtes de réponse http ( source ):
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
  1. Cependant, une telle configuration bat plus ou moins le point de PWA en premier lieu. PWA permet aux utilisateurs d'utiliser votre site même hors ligne, ils doivent pouvoir accéder à index.html d'abord, donc index.html doit être mis en cache.

  2. Mise en cache index.html est la bonne chose à faire. Il s'agit probablement de votre paramètre de contrôle du cache pour votre ancien .js et d'autres éléments présentent des problèmes. Je ne sais pas quelle partie va mal, mais le comportement attendu est: ces actifs doivent être mis en cache comme index.html, 404 ne devrait jamais arriver en premier lieu car le navigateur doit lire à partir du cache local.

  3. Une chose que vous pouvez faire pour "masquer" rapidement le problème est simplement de conserver tous les anciens actifs de la version précédente sur le serveur. De cette façon, mis en cache index.html ref aux anciens actifs, ils restent accessibles, pas de 404 donc pas de "token inattendu <".

  4. Maintenant, je vous suggère d'essayer d'abord le point 4, pour vérifier que ma théorie au point 3 est correcte. Vérifiez également si SwUpdate fonctionne correctement. Idéalement, vous devriez voir ce qui suit:

    1. vous publiez une version plus récente du code
    2. vous visitez le site, vous devriez voir l'ancienne version mise en cache
    3. une fois l'ancienne version de l'application chargée, SwUpdate actualise automatiquement la page pour vous et charge la nouvelle version.

Pour résumer, ma théorie est que les choses tournent mal au point 3. Les actifs plus anciens devraient être correctement mis en cache, en utilisant les en-têtes de contrôle de cache http appropriés (ou angular service worker has other way of do same things? Je ne connais pas cette partie.) Si vous ne pouvez pas le comprendre, alors gardez simplement les anciens actifs accessibles sur le serveur.

1
hackape

J'ai rencontré cette erreur lorsque modules de chargement paresseux .

C'était un Angular/Iconic PWA et était lié à une importation manquante:

import { SharedModule } from '@shared/shared.module';

Regarde aussi:

1
Robinyo

Cela se produit généralement pour moi lorsque la ressource demandée renvoie 404 et au lieu du fichier JS demandé, vous recevez votre index.html qui a des balises, d'où le jeton '<'. Cela pourrait-il être en quelque sorte le cas? Supposons que vous ayez des versions sur vos scripts et que la reconstruction invalide les anciennes URL ou quelque chose comme ça.

1
waterplea

J'ai une solution pour ce problème. Pour obtenir https://angular.io/cli/build

obtenez votre application angular.json fichier. Et changez la valeur de sortie

           "architect": {
                "build": {
                    ...
                    "configurations": {
                        "production": {
                            ...
                            "outputHashing": "media", // "all"
                        }
                    }
                }
            }
0
Jai Kumaresh