【问题标题】:Angular PWA update is too slowAngular PWA 更新太慢
【发布时间】:2020-04-05 12:35:36
【问题描述】:

我有一个 Angular 8 PWA 应用程序正在生产中。我会定期进行大量更新,因此我需要找到一种方法让用户在打开应用程序时获取这些更新。

如果没有特殊操作,应用将不会更新。如果您在浏览器上打开该应用程序,它将显示您上次打开该应用程序时的版本,即使我已将新捆绑包推送到生产环境。这似乎是 PWA 功能的不幸结果。

我正在使用 AWS Amplify 进行托管。

为了解决这个问题(我曾问过一个关于它的问题here),我尝试使用 swUpdate。

问题是它运行得太慢了。如果有更新,它将重新加载浏览器——但这需要一段时间。用户打开应用程序后通常需要几秒钟才能重新加载。因此,您打开应用程序,4 到 6 秒后应用程序重新加载新版本。

有没有办法让 swUpdate 运行得更快?还是另一种加载新应用版本的方法?

这是我的代码,我认为这是一个简单的实现:

app.component.ts:

import { Component } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {

   constructor(private swUpdate: SwUpdate) {
   title = 'Great App'

   swUpdate.available.subscribe(event => {
        swUpdate.activateUpdate().then(() => {
            window.location.reload();
            console.log('there is an Update! Reloading now.')
      });
    })

   if (!this.swUpdate.isEnabled) {
      console.log('Not going to update');
    }
 }

但这并不能很好地工作,因为重新加载通常会在用户进入应用程序后几秒钟发生(即使在良好的互联网连接情况下)。

我知道我也可以向人们显示一条消息,说“想用新版本刷新吗?”但这并不能解决当前 swUpdate 运行缓慢的根本问题。

【问题讨论】:

  • 使用 checkforupdate 会更快吗?
  • 这种情况经常发生在我身上,这是我最讨厌 PWA 的地方,很多不必要的错误。每次部署都会带来糟糕的用户体验。

标签: javascript angular typescript service-worker aws-amplify


【解决方案1】:

对于仍然遇到此问题的任何人,这是我为减少延迟所做的:

  • 在 APP_INITIALIZER 而不是 AppComponent 中检查更新,以便提前完成检查
  • 使用checkForUpdate 方法

在你的app.module.ts 中添加一个初始化函数:

{ provide: APP_INITIALIZER, useFactory: checkForUpdates, multi: true, deps: [SwUpdate /* Add whatever dependency is required here */] },

这个函数应该是这样的:

export const checkForUpdates = (swUpdate: SwUpdate): (() => Promise<any>) => {
  return (): Promise<void> =>
    new Promise((resolve) => {
      swUpdate.checkForUpdate();
      
      swUpdate.available.subscribe(() => {
          window.location.reload();
      });

      resolve();
    });
};

我不确定是否需要 APP_INITIALIZER 和 checkForUpdate,但这样我将延迟减少到 1-2 秒(而不是 5-10 秒甚至更多)

【讨论】:

    【解决方案2】:

    问题

    Angular 中的 Service Worker 有 4 种不同的注册策略,这决定了它何时向浏览器注册。

    1. registerWhenStable:&lt;timeout&gt;:在应用程序稳定后立即注册(没有挂起的微/宏任务),但不迟于毫秒。如果应用程序在几毫秒后还没有稳定下来(例如,由于重复的异步任务),无论如何都会注册 ServiceWorker。如果省略,则只有在应用稳定后才会注册 ServiceWorker。
    2. registerImmediately:马上注册。
    3. registerWithDelay:&lt;timeout&gt;:注册延迟毫秒。例如,使用 registerWithDelay:5000 在 5 秒后注册 ServiceWorker。如果省略,则默认为 0,这将尽快注册 ServiceWorker,但仍然是异步的,一旦所有待处理的微任务完成。
    4. An Observable factory function:返回 Observable 的函数。该函数将在运行时用于获取和订阅 Observable,并且一旦发出第一个值,就会注册 ServiceWorker。

    注意:Angular 使用registerWhenStable:30000 作为默认。这意味着它将首先等待应用程序稳定,然后它会注册一个 Service Worker(或者如果应用程序在此之前没有稳定,它将在 30 秒后注册 Service Worker)。

    解决方案

    您可以设置registerImmediately 策略,而不是默认的registerWhenStable:30000。然后,您可以例如添加 APP_INITIALIZER 并在其中检查是否有新版本并加载它。在这种情况下,如果有新版本可用,用户将看不到旧版本。

    app.module.ts

    import { APP_INITIALIZER } from '@angular/core';
    import { ServiceWorkerModule, SwUpdate } from '@angular/service-worker';
    ...
    function initializeApp(): Promise<any> {
      return new Promise(async (resolve, reject) => {
        try {
          // Check if Service Worker is supported by the Browser
          if (this.swUpdate.isEnabled) {
            const isNewVersion = await this.swUpdate.checkForUpdate();
            // Check if the new version is available
            if (isNewVersion) {
              const isNewVersionActivated = await this.swUpdate.activateUpdate();
              // Check if the new version is activated and reload the app if it is
              if (isNewVersionActivated) window.location.reload();
              resolve(true);
            }
            resolve(true);
          }
          resolve(true);
        } catch (error) {
          window.location.reload();
        }
      });
    }
    
    ...
    
    @NgModule({
      ...
      imports: [
        ...,
        ServiceWorkerModule.register('ngsw-worker.js', {
          enabled: environment.production,
          registrationStrategy: 'registerImmediately',
        }),
      ],
      providers: [
        ...,
        { provide: APP_INITIALIZER, useFactory: initializeApp, deps: [SwUpdate], multi: true },
      ],
    })
    export class AppModule {}
    

    【讨论】:

      【解决方案3】:

      我使用 Carrm 和 NeNad 的答案在我的 Angular 应用程序中创建了一个页面来管理更新。

      我还使用@saithodev/ts-appversion 包来管理版本。

      稍后,我创建了一个脚本来更新每个构建的应用版本并将其显示在该页面上 (appversionupdate.mjs)。

      然后,在 package.json 中,我将脚本设置为与 npm run 一起使用。

      这样,当我构建和部署项目时,版本会根据需要进行更新。

      我知道 OP 并没有要求所有这些,但它可能对社区有用。

      menu.component.html

      <mat-toolbar color="accent" fxLayout="row" fxLayoutAlign="space-between center" class="top-bar">
          <div fxLayout="row" fxLayoutAlign="start center">
              <h1>Atualização do app</h1>
          </div>
      
      </mat-toolbar>
      
      <main>
          <div>
              <h2>Versão do app instalada: {{versao}}</h2>
          </div>
      
          <h3>Status da atualização</h3>
          <!-- lista sem numeração em HTML -->
          <ul>
              <li *ngFor="let mensagem of mensagens">
                  {{mensagem}}
              </li>
          </ul>
      
          <button mat-raised-button color="accent" (click)="atualizarApp();" class="botao">Atualizar app</button>
      </main>
      

      menu.component.ts

      import { Component, OnInit } from '@angular/core';
      import { SwUpdate } from '@angular/service-worker';
      
      import { versions } from 'src/_versions';
      
      @Component({
        selector: 'app-menu',
        templateUrl: './menu.component.html',
        styleUrls: ['./menu.component.scss']
      })
      export class MenuComponent implements OnInit {
      
        mensagens: string[] = [];
        versao: string = 'Versão ainda não carregada pelo sistema.';
      
        constructor(
          private readonly swUpdate: SwUpdate,
          ) { }
      
        ngOnInit(): void {
          this.atualizarApp();
          this.versao = versions.version;
        }
      
        async atualizarApp() {
          this.mensagens?.push(`${this.getFormattedDate()} Iniciando atualização do app.`);
          try {
            // Check if Service Worker is supported by the Browser
            if (this.swUpdate.isEnabled) {
              this.mensagens?.push(`${this.getFormattedDate()} Verificado que este navegador suporta a atualização do app.`);
              const isNewVersion = await this.swUpdate.checkForUpdate();
              this.mensagens?.push(`${this.getFormattedDate()} Verificando se há nova versão a ser baixada.`);
              // Check if the new version is available
              if (isNewVersion) {
                this.mensagens?.push(`${this.getFormattedDate()} Nova versão do app encontrada. Fazendo download.`);
                const isNewVersionActivated = await this.swUpdate.activateUpdate();
                // Check if the new version is activated and reload the app if it is
                if (isNewVersionActivated) {
                  this.mensagens?.push(`${this.getFormattedDate()} Nova versão baixada e instalada.`);
                  window.location.reload();
                }
              } else {
                this.mensagens?.push(`${this.getFormattedDate()} O Materiale app já está atualizado.`);
              }
            } else {
              this.mensagens?.push(`${this.getFormattedDate()} Verificado que este navegador não suporta a atualização do app automática e outras funcionalidades.`);
              this.mensagens?.push(`${this.getFormattedDate()} Por favor, atualize o navegador para a versão mais recente. Baixe preferencialmente o Google Chrome.`);
            }
      
          } catch (error) {
            this.mensagens?.push(`${this.getFormattedDate()} Houve algum error ao tentar atualizar o app. Mensagem de erro: ${error}`);
            this.mensagens?.push(`${this.getFormattedDate()} É possível que este navegador não seja compatível. Por favor, atualize o navegador para a versão mais recente. Baixe preferencialmente o Google Chrome.`);
            this.mensagens?.push(`${this.getFormattedDate()} Mensagem de erro: ${error}`);
            //window.location.reload();
          }
      
        }
      
        getFormattedDate(): string {
          var date = new Date();
          var str = `${date.getDate()}/${(date.getMonth() + 1)}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
          return str;
      }
      
      }
      

      appversionupdate.mjs

      import fs from 'fs';
      
      //console.log("Atualizado versao do app");
      
      var obj = JSON.parse(fs.readFileSync('package.json', 'utf8'));
      
      var version = obj.version;
      var major = version.split('.')[0];
      var minor = version.split('.')[1];
      var patch = version.split('.')[2];
      
      var minor_updated = Number(minor) +1;
      var version_updated = major + '.' + minor_updated + '.' + patch;
      
      console.log("Nova versao: " + version_updated);
      
      obj.version = version_updated;
      
      let data = JSON.stringify(obj, null, 2);
      
      fs.writeFileSync('package.json', data);
      

      package.json

      {
        "name": "testeapp",
        "version": "0.14.0",
        "scripts": {
          "ng": "ng",
          "prestart": "ts-appversion",
          "start": "ng serve",
          "prebuild": "ts-appversion",
          "build": "ng build",
          "watch": "ng build --watch --configuration development",
          "test": "ng test",
          "bd": "node ./appversionupdate.mjs && npm run build && firebase deploy"
        },
      

      【讨论】:

        猜你喜欢
        • 2019-11-11
        • 1970-01-01
        • 1970-01-01
        • 2018-01-30
        • 2011-09-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多