SAP 电商云 Spartacus UI 4.1 版本的延迟加载技术介绍

延迟加载,也称为代码拆分,可让您将 JavaScript 代码分成多个块。 结果是当用户访问第一页时,您不必加载完整应用程序的所有 JavaScript。 相反,只加载给定页面所需的块。 在浏览店面时,会在真正需要使用到某些功能时,再加载这些功能块的实现模块。

这种方法可以显着改善“交互时间”,尤其是在低端移动设备访问复杂 Web 应用程序的情况下。

Spartacus Approach to Lazy Loading

代码拆分是一种必须在应用程序构建时完成的技术。 Angular 提供的代码拆分通常是基于路由的,这意味着着陆页有一个块,产品页面有另一个块,依此类推。 由于 Spartacus 主要是 CMS 驱动的,因此无法在构建时决定每个路由的实际应用逻辑。 业务用户最终将通过引入或删除组件来改变页面结构。 这就是为什么需要另一种延迟加载方法的原因,Spartacus 支持以下两种不同粒度的延迟加载技术:

  • CMS 组件的延迟加载
  • CMS 驱动的功能模块延迟加载

Defining Dynamic Imports Only in the Main Application

动态导入是一种用于促进延迟加载并允许代码拆分的技术,只能在类型为 Application 的 Angular 应用程序中使用。无法在预构建库(类型为 Library )中定义动态导入。

这是一个不幸的限制,导致必须由客户添加一些应用程序代码。 尽管自定义代码的数量被限制在最低限度,但我们将在未来版本的原理图库中添加一项功能,以自动添加延迟加载模块。

Avoiding Static Imports for Lazy-Loaded Code

为了使代码拆分成为可能,您的静态 JavaScript 代码(主应用程序包)不应该对您想要延迟加载的代码进行任何静态导入。一个例子就是 QuickOrderService. 构建器会注意到代码已经包含在内,因此不会为其生成单独的块。 这在从库中导入 symbol 的情况下尤其重要。

至少在 Angular 9 和 Angular 10 里,将静态导入与动态导入混合用于相同的库入口点,即使对于不同的 symbol,也会破坏该库入口点的延迟加载和 tree shaking 优化。 如果您要这样做,它将在构建中静态地包含整个入口点。 因此,强烈建议您为必须静态加载的代码创建特定的入口点,并为可以延迟加载的代码创建单独的入口点。

Configuration in Lazy-Loaded Modules

如果在延迟加载模块内部提供了额外的配置,Spartacus 会将其合并到全局应用程序配置中,以支持现有组件和服务的延迟加载场景。 在大多数情况下,尤其是当延迟加载的模块主要提供默认配置时,这可以可靠地工作。 但是,如果过度使用它会导致问题,尤其是当两个模块为配置的同一部分提供不同的配置时。 可以通过在主应用程序中提供必要的覆盖来修复诸如此类的场景。

这种合并功能是通过默认启用的兼容性机制实现的,但您可以使用 disableConfigUpdates 功能标志禁用它。 如果您正在开发必须从延迟加载的模块中挂钩到配置的新模块,则应改用 ConfigurationService.unifiedConfig$。

Providers in Lazy-Loaded Modules

延迟加载模块中提供的注入令牌对根应用程序中提供的服务不可见。

这尤其适用于 multi-provided 的令牌,例如 HttpInterceptors、各种处理程序等。

为了减轻这个缺点,一些 Spartacus 功能,例如 PageMetaService(使用 PageMetaResolver 令牌)或 ConverterService(主要使用适配器序列化器和规范化器),在我们的实现里使用 unified 注入器。 只有这样做,根应用程序才可以访问延迟加载的令牌,并可以利用它们来实现全局功能。

对于不依赖于统一注入器的机制(例如,来自大多数非 Spartacus 库的功能,例如核心 Angular 库),建议您始终使用 eager 模式加载包含了这些令牌的模块。

Unified Injector

统一注入器提供了一种注入令牌或多提供令牌的方法,这种方法同时考虑到根注入器和来自延迟加载功能的注入器。 注入器公开一个可观察的对象,每次统一注入器的状态发生变化时,该观察对象都会为指定的令牌发出一组新的可注入对象。

Avoiding Importing the HttpClientModule in Your Lazy-Loaded Modules

一般来说,HttpClientModule 应该在根应用程序中导入,而不是在库中。 例如,如果您将它导入到延迟加载的库中,则根库中的所有注入器对于源自延迟加载模块的 HTTP 调用都是不可见的。

虽然技术上可以在库中导入 HttpClientModule ,但在大多数情况下这不是预期的,并且可能会导致难以解释的错误,因此请记住这一点。

Lazy Loading of CMS Components

CMS 代码的延迟加载是通过在 CMS 映射配置中指定动态导入代替静态引用的组件类来实现的。 下面是一个例子:

{
  cmsComponents: {
    SimpleResponsiveBannerComponent: {
      component: () => import('./lazy/lazy.component').then(m => m.LazyComponent)
    }
  }
}

Lazy Loading of Modules

CMS 驱动的功能模块延迟加载允许以下内容:

  • 懒加载不仅是组件代码,还有核心部分(包括NgRx状态)
  • 在第一次需要时只加载一次功能提供共享的、延迟加载的依赖模块
  • 当实现被相关功能配置覆盖时,CMS 请求组件会触发功能模块的延迟加载。

Exposing Smart Proxy Facades From lazy loaded Features

代理外观提供了一种灵活的方式来从延迟加载的功能模块中公开核心功能,这样使用这些外观的组件就不必知道它是否延迟加载以及是否需要初始化。

对代理外观的方法或属性的任何访问都会触发所有相关功能的延迟加载和初始化。

Combined Injector

任何延迟加载的模块都可以从根应用程序注入器和依赖模块注入器注入(即可以访问)服务和令牌。 这是可能的,因为每次实例化具有依赖项的功能模块时都会创建 CombinedInjector。

当一个被延迟加载模块覆盖的 CMS 组件被实例化时,它可以注入(即访问)以下服务:

  • ModuleInjector 层次结构,从功能模块注入器开始,包括依赖模块和根注入器
  • ElementInjector 层次结构,它是在每个 DOM 元素上隐式创建的

Preparing Libraries to Work with Lazy Loading

Providing Fine-Grained Entry Points in Your Library

从相同的入口点混合静态和动态导入会破坏延迟加载并影响摇树,因此任何将直接用于动态导入的库都应该公开细粒度的辅助入口点以优化代码拆分。

作为惯例,Spartacus 公开功能的根入口点(root entry points),例如 @spartacus/orgainzation/administration/root。 这种类型的入口点包含所有不应或不能延迟加载的代码,比如 Proxy Facade 层。来自根入口点的模块应该在根应用程序中静态导入,这意味着它们将被预先加载并在主应用程序块中可用。

有关对 Angular 库中辅助入口点的支持的更多信息,请参阅 GitHub 上 ng-packagr 文档中的辅助入口点。

Separating Static Code from Lazy-Loaded Code

当您使用 Angular Dependency Injection 时,注入器中的提供程序列表不应在注入器初始化后更改。这种范式特别适用于任何多提供的令牌、处理程序,尤其适用于任何 Angular 原生多提供的令牌,例如 HTTP_INTERCEPTOR、APP_INITIALIZER 等。

结果是延迟加载模块中的任何多提供令牌对于根或其他延迟加载块中提供的模块和服务将不可见,但使用注入的多提供令牌除外统一喷油器。

一些 Spartacus 功能,例如 PageMetaService 或 ConverterService,使用 UnifiedInjector 来了解可以延迟加载的令牌,以便全局逻辑(例如 SEO 功能)即使逻辑延迟加载该功能也能可靠地工作。例如,商店定位器页面元解析器可以使用商店定位器功能延迟加载。

Spartacus 配置也是通过提供配置块来定义的,由于兼容机制将配置从延迟加载功能贡献到全局配置,因此处理方式略有不同。这种机制可以通过功能标志禁用,将来会默认关闭,以支持统一配置功能。

如果根服务无法看到延迟加载提供程序的问题,则始终可以将此类代码包含在预先可用的静态链接模块中。建议在您的库中创建一个单独的入口点(按照惯例,命名为 root,例如 my-library/root),其中包含最少的代码,将包含在主包中,并且从一开始就可用。

Simple Strategies for Optimized and Balanced Code Splitting

每个业务功能都略有不同,但总的来说,出于折中考虑,可以按照以下几点进行优化:

  • 大多数功能都提供了一系列 UI 组件,如果您访问一个组件,很可能很快也会需要其他组件。为了最大限度地减少网络流量和不必要的粒度开销,您可以考虑在同一个 chunk 中捆绑多个组件。

  • 功能的大多数 UI 组件通常需要核心逻辑。何谓核心?例如 Facade 的实现类即 Service, 以及适配器。在这种情况下,您可以考虑使用 lazy loading 机制来加载 core 模块。

  • 一个特性的一些核心服务也可能被其他 feature 频繁使用。在这种情况下,您可以考虑将核心代码与 UI 组件分开。
    功能的某些部分使用非常频繁(例如每个页面都使用的购物车图标组件),而有些仅在某些特定场景中使用(例如购物车摘要)。为了获得最佳体验,建议根据使用情况将功能拆分为逻辑部分,并为最少、最需要的部分创建单独的入口点(和功能),并为不太常用的代码创建另一个入口点。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容