web-dev-qa-db-ja.com

angular本番PWAの新しいビルドごとに、サイトが更新されるまで「予期しないトークン<」

私はそのようないくつかの同様の質問があることを知っています angular 2本番ビルド)の後の予期しないトークン は、実際には私の質問に答えません。

基本的に私はangularアプリとそのPWAを持っています。今、初めてサイトにアクセスするときに新しい製品ビルドを行うたびに、次のようなエラーが発生します。

enter image description here

その後、ページを更新するとすぐに、サイトは通常どおり機能します。

これは任意のブラウザ/デバイスで発生します

今私の疑いは、アプリが更新されるたびに、メインのバンドルされたjsファイルが変更され、PWAが古いファイルをキャッシュし、アプリがまだ新しいファイルを使用しようとしていることです。

私のプロジェクトの構造は次のとおりです

他のモジュールを遅延ロードするルートモジュールがあります。ロードされる最初のモジュールは私のアカウントモジュールなので、ユーザーはログインできます

これは私の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 {

}

したがって、技術的にはユーザーが最初にアクセスするモジュールはアカウントモジュールですが、明らかにルートモジュールからリダイレクトする必要があります。

ルートコンポーネントモジュールで行ったのは、Service Workerの更新が必要かどうかを確認するためです...

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

これで、更新があるかどうかを確認してからページを更新するだけですが、動作しません。

これは私の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)"
        ]
      }
    }
  ]
}

ご覧のとおり、この問題を解消するはずのmain.jsチャンクファイルを除外しています...ただし、除外されません

今、私は新しいビルドの後にネットワークタブをチェックすると、これが私が得るものです

enter image description hereこの画像は、main.jsファイルを除外する前のものでした

main.jsから除外した後のngsw.configの呼び出しは次のようになります

enter image description here

次に、私のような歩哨エラーハンドラーを使用してこのエラーをキャッチしてみようと思いました。

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

しかし、これは機能していません、歩哨でエラーが発生していますが、アプリがリロードされませんか?

これでいくつかのテストを行いました。プライベートモードでの新しいビルドの後にサイトにアクセスすると、エラーが発生することはありません。以前にサイトにアクセスしたことがある場合にのみ発生するようです。これがキャッシュの問題だと私に思わせます。

私のアプリケーションはAzureでホストされ、私のアプリケーションはAngular 7.27を使用しています

何か助けていただければ幸いです!

16
Smokey Dawson

この問題は、インストールされているServiceWorkerが原因で発生します。これは干渉するものであり、更新したものも含めて、すべての応答cachesです。

Service Workerの詳細:MDN-Service Workerの使用

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

これがwindow.location.reload()が機能しない理由です。これは、新しい要求を行うためですしかし、この要求はハイジャックされますインストールされているServiceWorkerによって、実際の要求を実サーバーに渡すのではなく実際の応答(更新されたmain.jsとアセット)をフェッチすると、キャッシュされた応答がアプリに返されます。これは、更新された応答ではなく、古いmain.jsであり、そのため失敗します。

より具体的には、ServiceWorkerは、コードを変更するたびに生成されるmain.jsおよびその他のアセットをキャッシュします。 Main.jsは、読み込まれると、アプリケーションの他のチャンク(5.xxxxx.jsなど)を遅延読み込み(http要求を行う)します。

コードに変更を加えた場合、main.js5.xxxxxx.jsチャンクを含むすべてのチャンクが更新されても、ブラウザーは古いmain.jsを実行しています。この古いmain.jsには、存在しない5.xxxxxx.jsの古いペアへの参照があります。この時点で、reload()をプログラムで実行すると、インストールされているServiceWorkerは古いキャッシュされたmain.jsで応答し、存在しないサーバーから古いバージョンの5.xxxxx.jsをレイジーロードしようとするため、404 htmlの応答が返されます。エラー。したがって、'<'トークン例外(実際には、404エラーページのタグの最初の文字です)。

これはネットワークパネルのスクリーンショットから明らかです。スクリーンショットを見ると、main.jsと他のすべてのアセットが(from service worker)から配信されていることがわかります。つまり、これらは古いバージョンのキャッシュであり、現在Webサーバーに存在する実際に更新されたファイルとは異なります。

この問題を修正するには、ServiceWorkernot cachemain.jsに設定する必要があります。代わりに、常にこの要求を実サーバーに渡して、更新されたバージョンのmain.jsをフェッチして、遅延するようにします。現在実際にWebサーバーに存在する更新済みの5.xxxx.jsチャンクをロードします。

この問題を修正するために、ServiceWorkerngsw-config.jsonを具体的にnot cachemain.jsファイル(パターンがmain.xxxxxxx.jsであると想定)に設定する方法を次に示します。

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

ng build --prodを実行してアプリを再構築し、テストします。

更新:

これがブラウザーのキャッシュの問題ではないことを確認するには、最初の引数としてtrueをwindow.location.reload()メソッドに渡します。これは、ブラウザによってキャッシュされたアセットなしでリロードが実行されるように指示します。

root.component.ts

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

更新2:

Angularのドキュメントによると、クライアントのブラウザにすでにインストールされているService Workerはキャッシュされています。 (最近行ったように)Service Workerコードを更新する場合、新しいService Workerコードは、すでにインストールされているクライアントのすべてのブラウザーで更新する必要があります。 Angularドキュメントには定期的なチェックがあると記載されていますが、それは指定されていません。ServiceWorker自体が更新を必要とするかどうかをチェックし、それを行います。 https:/ /angular.io/guide/service-worker-devops#service-worker-updates

おそらく、問題はプロダクションでのみ発生し、開発ではないか、プライベートブラウザーモード(SWがインストールされていないクリーンなブラウザースレート)で発生したと考えられます。

これを確認するには、Chromeの開発者ツールを使用してページで実行されている既存のブラウザのソフトウェアと、インストールする必要のある更新されたソフトウェアコードを比較する必要があります。それらが異なる場合、それはまだ更新されていないことを意味し、それが問題の原因です。

chromeこの問題が発生しているブラウザを使用し、Chrome Dev tools-> Application Tab-> Service Workers(in top of the top of top)]にアクセスして、これを行うことができます =そして、ブラウザに登録されているSWがあります。[ソース]リンクをクリックすると、SWの実際のコードが開きます。コードを行ごとに(またはdiffユーティリティを使用して)、更新する必要のあるSWコードと比較します。実行されています。

11
J. Koutsoumpas

キャッシュヘッダーを再確認してください。 Chromeは常にキャッシュなしでリッスンするとは限りません。最近サイトにアクセスした場合、chromeはキャッシュからindex.htmlをロードします。エラーの原因です。

Index.htmlをno-storeに変更しました。これで問題は解決したようです。 https://gertjans.home.xs4all.nl/javascript/cache-control.html

6
Raisins

私はangular問題の専門家ではありません。私ができる最善のことは、問題の分析とデバッグの支援です。

  1. エラーを解消したい場合(そしてindex.htmlを提供するサーバーを制御できる場合)、これらを設定することで解決できると確信していますhttp応答ヘッダー(- ソース ):
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
  1. ただし、そのようなセットアップは、そもそもPWAの要点を多少上回るものです。 PWAを使用すると、ユーザーはオフラインでもサイトを使用できます。最初にindex.htmlにアクセスできる必要があるため、index.htmlをキャッシュする必要があります。

  2. キャッシングindex.htmlは正しいことです。古い.jsのキャッシュ制御設定と他のアセットに問題がある可能性があります。どの部分がうまくいかないのかはわかりませんが、予想される動作は次のとおりです。これらのアセットはindex.htmlと同じようにキャッシュする必要があります。ブラウザはローカルキャッシュから読み取る必要があるため、404が最初から発生することはありません。

  3. 問題をすばやく「カバー」するためにできることの1つは、以前のバージョンのすべての古い資産もサーバーに保持することです。このように、キャッシュされたindex.html refは古いアセットにアクセスでき、アクセス可能なままです。404がないため、「予期しないトークン<」はありません。

  4. ここで、最初にポイント4を試して、ポイント3での私の理論が正しいことを確認することをお勧めします。また、SwUpdateが正常に動作するかどうかも確認してください。理想的には、次のようになります。

    1. 新しいバージョンのコードを公開する
    2. サイトにアクセスすると、キャッシュされた古いバージョンが表示されます
    3. 古いバージョンのアプリが読み込まれた後、SwUpdateが自動的にページを更新し、新しいバージョンを読み込みます。

要約すると、私の理論は、ポイント3で問題が発生することです。古いアセットは、適切なhttpキャッシュコントロールヘッダーを使用して正しくキャッシュする必要があります(またはangularサービスワーカーには同じことを行う他の方法がありますか?私はその部分に精通していません。それを理解できない場合は、サーバー上で古い資産にアクセスできるようにしてください。

1
hackape

遅延読み込みモジュールのときにこのエラーが発生しました。

Angular /象徴的なPWAであり、インポートの欠落に関連していた:

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

以下も参照してください:

1
Robinyo

これは通常、リクエストされたリソースが404を返し、リクエストされたJSファイルの代わりにタグを含むindex.htmlを受け取ったときに発生します。どういうわけかそうなのでしょうか?スクリプトにバージョニングがあり、再構築すると古いURLなどが無効になるとします。

1
waterplea

この問題には1つの解決策があります。 get https://angular.io/cli/build の場合

アプリを入手angular.jsonファイル。そしてoutputHashing値をすべてメディアに変更します

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