如何使用 Angular 延迟加载模块克服加载块失败的问题

Posted

技术标签:

【中文标题】如何使用 Angular 延迟加载模块克服加载块失败的问题【英文标题】:How to overcome loading chunk failed with Angular lazy loaded modules 【发布时间】:2018-08-18 06:59:35 【问题描述】:

如果我对我的 Angular 应用程序进行更改,块名称将在构建时更改,并且旧版本将从 dist 文件夹中删除。部署后,如果用户当前在站点上,然后导航到站点的另一部分,我会收到“加载块失败”错误,因为旧文件不再存在。

我的应用是使用 Angular CLI 构建并使用 webpack 打包的。

有什么办法可以解决这个问题吗?

【问题讨论】:

Angular 现在支持服务工作者,因此您可以使用它们来通知用户该应用程序的新版本可供下载。 我已经想到了,但我对这种方法的担忧是人们在网络上不太熟悉,他们可能会觉得有点奇怪。我的网站是一个电子商务网站,我不想推迟它们。 我同意对于这样一个公共类型的应用程序你不想这样做。抱歉,我不确定什么是好的解决方案。 @dottodot 你能解决这个问题吗?我对反应有同样的问题, @tubu13 不幸的是,我什至尝试使用 cloudfront 将文件缓存几个星期,希望它可以在更新后使它们可用,但这显然不是我的问题在奇怪的情况下仍然会出现错误并且无法确定原因。 【参考方案1】:

以下更新解决方案 首选的解决方案是转换为 PWA


我遇到了同样的问题,并找到了其他答案中未提及的非常简洁的解决方案。

如果块失败,您使用全局错误处理并强制应用重新加载。

import ErrorHandler, Injectable from '@angular/core';

@Injectable()
export class GlobalErrorHandler implements ErrorHandler 
  
  handleError(error: any): void 
   const chunkFailedMessage = /Loading chunk [\d]+ failed/;

    if (chunkFailedMessage.test(error.message)) 
      window.location.reload();
    
  

这是一个简单的解决方案,here (Angular Lazy Routes & loading chunk failed) 是我得到它的文章。


编辑:转换为 PWA

另一种解决方案是将您的 Angular 站点转换为 PWA(渐进式 Web 应用程序)。这样做是添加一个可以缓存所有块的服务工作者。每次网站更新时,都会从后端加载一个新的清单文件,如果有更新的文件,Service Worker 将获取所有新的块并通知用户更新。

Here 是一个关于如何将您当前的 Angular 网站转换为 PWA 的教程。通知用户有新的更新很简单:

import  Injectable  from '@angular/core';
import  SwUpdate  from '@angular/service-worker';

@Injectable()
export class PwaService 
  constructor(private swUpdate: SwUpdate) 
    swUpdate.available.subscribe(event => 
      if (askUserToUpdate()) 
        window.location.reload();
      
    );
  

【讨论】:

捕捉错误的好主意!如果块加载失败仍然存在,这似乎有可能导致重新加载循环? 此答案不完整,重新加载页面不会强制刷新浏览器缓存,因此仅适用于某些浏览器。您应该首先使用 await Promise.all((await caches.keys()).map(caches.delete)) 之类的方式清除缓存 @Guillaume 由于块的散列,没有必要刷新缓存。当你重新加载时,将会有一个新的 main.XXXX.js 引用所有新的散列包。 @Vahid 我更新了我的问题以添加 PWA 解决方案,这将避免任何问题。 我将正则表达式修改为 /Loading chunk [^\s]+ failed/ 因为我的块名称不只是数字。【参考方案2】:

您可以在您的应用程序中使用自定义GlobalErrorHandler,它所做的只是检查错误消息是否加载块 [XX] 失败,并强制应用重新加载并加载新的新块。

使用以下代码创建globalErrorHandler

import  ErrorHandler, Injectable  from "@angular/core";
@Injectable()
export class GlobalErrorHandler implements ErrorHandler 
    handleError(error: any): void 
        const chunkFailedMessage = /Loading chunk [\d]+ failed/;
        if (chunkFailedMessage.test(error.message)) 
            if (confirm("New version available. Load New Version?")) 
                window.location.reload();
            
        
    

创建globalErrorHandler.ts后,应在app.module.ts中定义ErrorHandler

@NgModule(
  providers: [provide: ErrorHandler, useClass: GlobalErrorHandler]
)

【讨论】:

代码运行良好,但在为添加的服务编写单元测试时遇到问题。如果 window.onbeforeunload = jasmine.createSpy();在 beforeeach 块中使用,单元测试循环运行并且永远不会完成。如果 window.onbeforeunload = jasmine.createSpy();未使用,则测试失败并提及“您的某些测试加载了整个页面”。有人可以帮忙吗?【参考方案3】:

您还可以使用服务工作人员通过“轮询/间隔”检测更改,并通知用户应用更新。

https://angular.io/guide/service-worker-getting-started

Service Worker 可以每 1 分钟检测一次,例如是否有新的“资产/您的应用”并向上滑动以通知用户重新加载页面,或者如果您愿意,甚至可以强制加载。

【讨论】:

【参考方案4】:

使用预加载。您可以获得延迟加载的好处,而不会在这种情况下造成麻烦。在不减慢初始加载时间的情况下,所有块将尽可能快地提供给用户。以下是https://vsavkin.com/angular-router-preloading-modules-ba3c75e424cb 的摘录,用于解释其工作原理(请参阅文章中的图表):

首先,我们加载初始包,其中仅包含组件 我们必须引导我们的应用程序。所以它和它一样快 可以。

然后,我们使用这个小包引导应用程序。

此时应用程序正在运行,因此用户可以启动 与之互动。当她这样做时,我们在后台, 预加载其他模块。

最后,当她点击一个指向延迟加载模块的链接时, 导航是即时的。

我们两全其美:初始加载时间与 可以,而且后续导航是即时的。

【讨论】:

鼓励链接到外部资源,但请在链接周围添加上下文,以便您的其他用户了解它是什么以及为什么存在。始终引用重要链接中最相关的部分,以防目标站点无法访问或永久离线。请参阅:How to anwser。【参考方案5】:

清除浏览器缓存。为我工作

【讨论】:

【参考方案6】:

我知道我在这个问题上有点迟到了,但我建议改变你的部署方法。

查看https://immutablewebapps.org/。基本理念是将构建时间资产与运行时变量隔离开来。这提供了很多好处,但最大的好处是:

资产没有缓存问题,因为它们在路由级别被隔离 在开发中经过审查的相同代码在生产中使用 在出现问题时为消费者提供热缓存的即时回退 为先前版本维护创建的块,通过路由隔离避免活动部署与活动用户问题

这也不应该干扰下面@dottodot 提供的** PreloadAllModules** 建议。

【讨论】:

您能否详细说明“将您构建的时间资产与运行时变量隔离开来”,我不明白如何开始在我的应用上应用它。【参考方案7】:

为了这个目的,我在更新后将我的旧块保留了几天。我的应用还包含迷你 SPA,因此当它们四处移动时,它们可能会在页面加载期间获取新版本。

【讨论】:

如果您需要紧急修复某个块,这并不理想。【参考方案8】:

您可以从服务器端发送一些事件来重新加载应用程序。还有一个选项可以在后台预取惰性模块,以便尽快预取它们,而不是等待那些模块的请求。

【讨论】:

实际上我认为使用 PreloadAllModules 是关键,因为这意味着它们会被缓存在浏览器中,因此无论是否上传了更改都没关系,因为它们仍然可以访问缓存的版本。 如果速度对你的用户很重要并且你有一个很大的 SPA,那么预加载所有模块是一个坏主意。分块的好处之一是可以在需要时提取其中的一部分,从而加快应用程序的加载时间。

以上是关于如何使用 Angular 延迟加载模块克服加载块失败的问题的主要内容,如果未能解决你的问题,请参考以下文章

延迟加载功能模块中未知的 Angular http 客户端拦截器

Angular:只有在授权的情况下才从服务器加载延迟加载的模块(使用 JWT)

Angular 9 嵌套延迟加载模块,带有嵌套路由器出口

Angular2延迟加载模块错误'找不到模块'

Angular2 无法使用延迟模块加载的自定义重用策略

延迟加载模块中的 Angular single-spa 延迟加载路由调用未定义的 webpack 错误