每个新的有角度生产PWA的版本上的“意外令牌<”,直到站点刷新

时间:2019-03-14 05:12:59

标签: angular typescript azure azure-web-sites progressive-web-apps

我知道类似unexpected token after angular 2 production build这样的问题,但实际上并不能回答我的问题。

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

enter image description here

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

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

现在我的怀疑是,每次更新应用程序时,捆绑的主要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块文件,该文件应该消除了此问题... 但不是

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

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

任何帮助将不胜感激!

6 个答案:

答案 0 :(得分:8)

此问题是由安装的ServiceWorker引起的,该window.location.reload()会干扰并缓存所有响应,包括刚刚更新的响应。

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

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

这就是为什么ServiceWorker不起作用的原因,因为它确实发出了新请求,但是此请求已被安装的ServiceWorker劫持,而不是放弃真实请求到真实服务器并获取真实响应(更新的main.js和资产),它只是将缓存的响应返回到您的应用,这是旧的main.js而不是更新的响应,这就是失败的原因。< / p>

更具体地说,main.js正在缓存main.js和其他资产,这些资产在每次更改代码时都会生成。依次加载Main.js时,它会延迟加载(发出http请求)应用程序的其他块(例如5.xxxxx.js)。

当您对代码进行更改时,即使所有块(包括5.xxxxxx.jsmain.js块)都已更新,浏览器仍在运行较旧的main.js。此较旧的5.xxxxxx.js引用了一对不再存在的较旧的ServiceWorker。此时,如果您以编程方式执行reload(),则已安装的main.js会以较旧的缓存5.xxxxx.js进行响应,而后者会依次尝试从服务器上延迟加载'<'的较旧版本不存在,因此得到404 HTML错误的响应。因此,main.js令牌异常(实际上是404错误页面标签的第一个字符)。

网络面板屏幕截图中很明显。如果查看屏幕截图,您会发现(from service worker)和所有其他资产都是从ServiceWorker交付的,这意味着这些是缓存的较旧版本,与您的设备中现在存在的实际更新文件不同。网络服务器。

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

在这里,您可以配置ServiceWorker的{​​{1}}以专门不缓存 main.js文件(假设文件的格式为{{1} })来解决此问题:

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作为第一个参数传递给window.location.reload()方法。这指示浏览器在没有缓存资产的情况下执行重新加载:

root.component.ts

true

更新2:

根据Angular的文档,将缓存已安装在客户端浏览器上的Service Worker。如果您更新Service Worker代码(如最近所做的那样),则必须在已经安装了Service Worker代码的客户端的所有浏览器中更新新的Service Worker代码。 Angular文档指出有一个定期检查,但未指定它,它将检查Service Worker本身是否需要更新并执行更新。 https://angular.io/guide/service-worker-devops#service-worker-updates

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

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

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

答案 1 :(得分:6)

仔细检查您的缓存头。 Chrome浏览器并不总是监听无缓存。如果您最近访问过该网站,则chrome将从缓存中加载index.html。导致错误。

我们已将index.html更改为不存储,这似乎可以解决问题。 https://gertjans.home.xs4all.nl/javascript/cache-control.html

答案 2 :(得分:1)

当请求的资源返回404时,这通常发生在我身上,而不是请求的JS文件,您收到的是index.html,它带有标记,因此带有“ <”标记。可能是这样吗?假设您对脚本进行了版本控制,然后重新构建会使旧的URL无效或类似的东西。

答案 3 :(得分:1)

我不是角度问题的专家。我能做的最好的就是帮助您解决问题,帮助调试。

  1. 如果您只是想消除错误(并且可以控制服务于index.html的服务器),我很确定可以通过设置这些 http响应标头来解决(source):
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. 您可以做的一件事情来快速“发现”问题,只需将先前版本的所有旧资产也保留在服务器上。这样,index.html缓存的旧资产引用仍然可以访问,没有404,因此没有“意外令牌<”。

  4. 现在,我建议您首先尝试点4,以验证我在点3上的理论是正确的。同时检查SwUpdate是否正常工作。理想情况下,您应该看到以下内容:

    1. 您发布较新版本的代码
    2. 您访问该网站,应该看到缓存的旧版本
    3. 加载旧版应用后,SwUpdate会自动为您刷新页面并加载新版。

总而言之,我的理论是在第3点出问题了。应该使用适当的http缓存控制标头来正确地缓存较旧的资产(或者angular服务工作者还有其他做相同事情的方式?我不熟悉该部分)。如果您无法解决问题,则只需将旧资产保留在服务器上即可访问。

答案 4 :(得分:1)

延迟加载模块时,我遇到了此错误。

这是一个Angular / Iconic PWA,与缺少导入有关:

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

另请参阅:

答案 5 :(得分:0)

我对此问题有一个解决方案。为了获得https://angular.io/cli/build

获取您的应用angular.json文件。并将outputHashing值全部更改为媒体

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