“意外令牌<"在每个新的角度生产PWA版本上,直到站点刷新 [英] 'Unexpected token <' on every new build of angular production PWA until site refresh

查看:58
本文介绍了“意外令牌<"在每个新的角度生产PWA版本上,直到站点刷新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道也有类似的问题,例如在产生角度2之后出现了意外的令牌构建,但实际上并不能回答我的问题.

基本上,我有一个角度应用程序及其PWA,现在每次我第一次访问该网站时,每次进行新的生产构建时,我都会遇到类似的错误

,然后我刷新页面后,该站点将正常运行.

在任何浏览器/设备上都会发生

现在我的怀疑是,每次更新应用程序时,捆绑的主要js文件都会更改,并且PWA已缓存了旧的js文件,而该应用程序仍在尝试使用新的文件.

我的项目结构如下

我有一个根模块,它可以延迟加载其他模块,而第一个加载的模块是我的帐户模块,因此用户可以登录

这是我的 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 {

}

从技术上讲,用户进入的第一个模块是帐户模块,但是显然必须从根模块重定向.

所以我做的是在我的根组件模块中,检查服务工作者是否需要像这样更新...

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块文件,这应该可以消除此问题... 但不是

现在,当我在新建之后检查网络"选项卡时,这就是我得到的

这张图片是在我排除main.js文件之前

这是我从ngsw.config

中排除它后,我的main.js调用的样子

然后我想像这样使用哨兵错误处理程序尝试捕获此错误.

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

任何帮助将不胜感激!

解决方案

问题是由已安装的ServiceWorker引起的,该问题会干扰并缓存所有响应,包括那些响应您刚刚更新了.

有关服务人员的更多信息:MDN-使用服务人员

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时,它会延迟加载(发出http请求)应用程序的其他块(例如5.xxxxx.js).

当您对代码进行更改时,即使所有块(包括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服务器中现在存在的实际更新文件不同./p>

要解决此问题,必须将ServiceWorker配置为不缓存 main.js,并且始终让此请求传递给真实服务器,以便获取任何更新版本的main.js依次将延迟加载现在已实际存在于Web服务器中的更新的5.xxxx.js块.

在这里,您可以配置ServiceWorkerngsw-config.json以专门不缓存 main.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文档指出有一个定期检查,但未指定它,它将检查Service Worker本身是否需要更新并执行更新. https://angular.io/guide/service-worker-devops#服务工人更新

也许只是在生产环境中而不是在开发环境中,或者使用专用浏览器模式(未安装SW的干净浏览器面板)来解决问题,似乎是这种情况.

要验证这一点,您应该将使用Chrome开发人员工具在页面上运行的现有浏览器软件与应安装的更新软件代码进行比较.如果它们不同,则意味着它尚未更新,这就是导致问题的原因.

您可以使用出现此问题的Chrome浏览器,并转到 Chrome开发工具->应用程序"标签->服务工作者"(位于左侧面板顶部),执行此操作在浏览器中注册的软件.单击源"链接,它将打开软件的实际代码.逐行比较该代码(或使用diff实用程序)与应该运行的更新的SW代码.

I know there are some similar questions like so unexpected token after angular 2 production build but it doesnt actually answer my question.

Basically I have an angular app and its a PWA, now everytime I do a new production build the first time I visit the site I get an error like so

and then as soon as I refresh the page, the site works as normal.

This happens on any browser/device

Now my suspicion is that every time the app is updated, the main bundled js file changes, and the PWA has cached the old one and the app is still trying to use the new one.

My Project structure is as follows

I have a root module that lazy loads the other modules now the first module that gets loaded is my account module so the user can log in

this is my 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 {

}

so technically the first module the user gets to is the account module, but obviously it has to be redirected from the root module.

So what I did was in my root component module I check to see if the Service Worker needs updating like so...

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

now this should check to see if there is an update and then just refresh the page.. but it doesn't work.

this is my 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)"
        ]
      }
    }
  ]
}

as you can see I am excluding the main.js chunk file which should eliminate this issue... but it doesn't

Now when I check the network tab after a new build this is what I get

This image was before I excluded the main.js file

this is what my main.js calls look like after I have excluded it from the ngsw.config

I then thought to just try and catch this error using my sentry error handler like so..

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

but this isn't working, I am getting the error in sentry but the app is not reloading??

Now I have done some testing, If you visit the site after a fresh build on private mode you will never get the error, it only seems to happen if you have visited the site before, which is what makes me think its a caching issue

My Application is hosted on Azure and my application is using Angular 7.27

Any help would be appreciated!

解决方案

The issue is caused by the installed ServiceWorker, which is the one that interferes and caches all responses, including the ones that you have just updated.

More information about service workers: MDN - Using Service workers

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

That is why window.location.reload() does not work, because it does make a new request but this request is hijacked by the installed ServiceWorker and instead of letting the real request go through to the real server and fetch the real response (updated main.js and assets), it just returns back to your app a cached response, which is the old main.js and not the updated one and that is why it fails.

More specifically the ServiceWorker is caching the main.js and other assets, which are generated each time you change your code. Main.js when loaded in turn it lazy-loads (makes an http request to) other chunks of your application (e.g. the 5.xxxxx.js).

When you do make a change in your code, and even though all your chunks gets updated, including main.js and the 5.xxxxxx.js chunk, the browser is still running the older main.js. This older main.js has a reference to the older pair of 5.xxxxxx.js which no longer exists. At this point if you do a reload() programatically, the installed ServiceWorker responds with the older cached main.js which tries in turn to lazy-load the older version of 5.xxxxx.js from the server which does not exist and therefore gets a response of an 404 html error. Hence the '<' token exception (it's actually the first character of the tag of the 404 error page).

This is evident from the network panel screenshot. If you examine the screenshot you will see that the main.js and all other assets are delivered from (from service worker), which means that those are the cached older versions and are different from the actual updated files that now exist in your web server.

To fix this issue, the ServiceWorker must be configured to not cache the main.js and instead always let this request to go through to the real server in order to fetch any updated version of main.js that in turn will lazy-load the updated 5.xxxx.js chunk that now actually exist in the web server.

Here is how you can configure the ngsw-config.json of ServiceWorker to specifically not cache the main.js file (assuming it is of the pattern main.xxxxxxx.js) in order to fix this issue:

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

run ng build --prod to rebuild your app and test it.

UPDATE:

To make sure that this is not a browser caching issue, pass true as the first argument to the window.location.reload() method. This instructs the reload to be executed without cached assets by the browser:

root.component.ts

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

UPDATE 2:

According to the documentation of Angular, the Service Worker that is installed already on browsers of clients is cached. If you update the Service Worker code (like you did recently), then the new Service Worker code must be updated in all the browsers of the clients that have it already installed. Angular documentation states that there is a periodic check, but it doesn't specify it, that will check if the Service Worker itself needs an update and will do it. https://angular.io/guide/service-worker-devops#service-worker-updates

Perhaps, the manefistation of the issue only in the production and not in development or with a Private Browser mode (clean browser slate with no SW installed), seems that this is the case.

To verify this, you should compare the existing browser's SW that is running on the page using the developer tools of Chrome, with the updated SW code that should be installed. If they are different then it means that it hasn't been updated yet and that's what's causing the issue.

You can do this using a chrome browser that exhibits this issue and by going to Chrome Dev tools -> Application Tab -> Service Workers (in the top of left panel) and there will be the SW that is registered in the browser. Click the Source link and it will open up the actual Code of the SW. Compare the code line by line (or use a diff utility) with the updated SW code that should be running.

这篇关于“意外令牌&lt;"在每个新的角度生产PWA版本上,直到站点刷新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆