延遲加載,也稱為代碼拆分,可讓您将 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 映射配置中指定動态導入代替靜态引用的元件類來實作的。 下面是一個例子:

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 元件分開。
功能的某些部分使用非常頻繁(例如每個頁面都使用的購物車圖示元件),而有些僅在某些特定場景中使用(例如購物車摘要)。為了獲得最佳體驗,建議根據使用情況将功能拆分為邏輯部分,并為最少、最需要的部分建立單獨的入口點(和功能),并為不太常用的代碼建立另一個入口點。