共计 5764 个字符,预计需要花费 15 分钟才能阅读完成。
官网链接
提早加载,也称为代码拆分,可让您将 JavaScript 代码分成多个块。后果是当用户拜访第一页时,您不用加载残缺应用程序的所有 JavaScript。相同,只加载给定页面所需的块。在导航到其余页面时,会在须要时加载其余块。
这种办法能够显着改善“交互工夫”,尤其是在低端挪动设施拜访简单 Web 应用程序的状况下。
Spartacus Approach to Lazy Loading
代码拆分是一种必须在应用程序构建时实现的技术。Angular 提供的代码拆分通常是基于路由的,这意味着着陆页有一个块,产品页面有另一个块,依此类推。
因为 Spartacus 次要是 CMS 驱动的,因而无奈在构建时决定每个路由的理论应用逻辑。业务用户最终将通过引入或删除组件来扭转页面构造。这就是为什么须要另一种提早加载办法的起因,Spartacus 通过以下形式提供:
- CMS 组件的提早加载
- CMS 驱动的功能模块提早加载 – CMS-driven lazy loading of feature modules
Defining Dynamic Imports Only in the Main Application
动静导入是一种用于促成提早加载并容许代码拆分的技术,只能在主应用程序 – main application 中应用。无奈在预构建库中定义动静导入。
这是一个可怜的限度,导致必须由客户增加一些利用程序代码。只管自定义代码的数量被限度在最低限度,但咱们将在将来版本的 schematics library 中增加一项性能,以主动增加提早加载模块。
Avoiding Static Imports for Lazy-Loaded Code
为了使代码拆分成为可能,您的动态 JavaScript 代码(主利用程序包)不应该对您想要提早加载的代码进行任何动态导入。如果你真的这么做了,构建器会留神到代码曾经蕴含在内,因而不会为其生成独自的块。这在从库中导入符号的状况下尤其重要。
在撰写本文时(Angular 9 和 Angular 10),将动态导入与动静导入混合用于雷同的库入口点,即便对于不同的符号,也会毁坏该库入口点的提早加载和 tree shaking. 如果您要这样做,它将在构建中动态地蕴含整个入口点。因而,强烈建议您为必须动态加载的代码创立特定的入口点,并为能够提早加载的代码创立独自的入口点。
Configuration in Lazy-Loaded Modules
如果提早加载模块外部提供了额定的配置,Spartacus 会将其合并到全局应用程序配置中,以反对现有组件和服务的提早加载场景。在大多数状况下,尤其是当提早加载的模块次要提供默认配置时,这能够牢靠地工作。然而,如果适度应用它会导致问题,尤其是当两个模块为配置的同一部分提供不同的配置时。能够通过在主应用程序中提供必要的笼罩来修复诸如此类的场景。
这种合并性能是通过默认启用的兼容性机制实现的,但您能够应用 disableConfigUpdates 性能标记禁用它。如果您正在开发必须从提早加载的模块中挂钩到配置的新模块,则应改用 ConfigurationService.unifiedConfig$。此性能在下一节中形容。
Unified Configuration
对立配置提供了一种获取全局配置的办法,该配置包含根配置和来自已加载提早加载模块的配置。
ConfigurationService.unifiedConfig$ 将对立配置公开为每次更改时收回新配置的 observable。例如,每次加载和实例化具备提供的配置的提早加载模块时都会产生这种状况。
所有配置局部都依照严格的程序合并,理论配置始终笼罩默认配置,并且根模块(即 app shell)中定义的配置具备优先权。
以下示例显示了根应用程序和两个提早加载模块中提供的不同配置的合并程序,其中列表中的每个后续项都能够笼罩前一项:
- 默认根配置
- 提早加载模块 1 的默认配置
- 提早加载模块 2 的默认配置
- 提早加载模块 1 的配置
- 提早加载模块 2 的配置
- 根配置(始终优先)
Providers in Lazy-Loaded Modules
提早加载模块中提供的注入令牌对根应用程序中提供的服务不可见。这尤其实用于多提供的令牌,例如 HttpInterceptors、各种处理程序等。
为了加重这个毛病,一些 Spartacus 性能,例如 PageMetaService(应用 PageMetaResolver 令牌)或 ConverterService(次要应用适配器序列化器和规范化器),后盾应用对立注入器。通过这样做,他们能够拜访提早加载的令牌,并能够利用它们来实现全局性能。
对于不依赖于对立注入器的机制(例如,来自大多数非 Spartacus 库的性能,例如外围 Angular 库),建议您始终应用这些类型的令牌事后加载模块。
unified injector
对立注入器提供了一种注入令牌或多提供令牌的办法,同时思考到根注入器和来自提早加载性能的注入器。注入器公开一个可察看的对象,每次对立注入器的状态发生变化时,该察看对象都会为指定的令牌收回一组新的可注入对象。
Avoiding Importing the HttpClientModule in Your Lazy-Loaded Modules
一般来说,HttpClientModule 应该在根应用程序中导入,而不是在库中。例如,如果您将它导入到提早加载的库中,则根库中的所有注入器对于源自提早加载模块的 HTTP 调用都是不可见的。
尽管技术上能够在库中导入 HttpClientModule,但在大多数状况下这不是预期的,并且可能导致难以解释的谬误,因而请记住这一点。
Lazy Loading of CMS Components
Configuration of Lazy Loading CMS Components
CMS 代码的提早加载是通过在 CMS 映射配置中指定动静导入代替动态援用的组件类来实现的。上面是一个例子:
{
cmsComponents: {
SimpleResponsiveBannerComponent: {component: () => import('./lazy/lazy.component').then(m => m.LazyComponent)
}
}
}
Technical Details
CMS 组件映射中对动静导入的反对是应用可定制的组件处理程序(特地是 LazyComponentHandler)实现的。
能够扩大此处理程序以自定义其行为、增加非凡钩子或不同的触发器,或者实现能够选择性地重用现有处理程序的全新处理程序。
Lazy Loading of Modules
- 懒加载不仅是组件代码,还有外围局部(包含 NgRx 状态)
- 在第一次须要时只加载一次性能
- 提供共享的、提早加载的依赖模块
- 当实现被相干性能配置笼罩时,CMS 申请组件会触发功能模块的提早加载。
Configuration of Lazy-Loaded Modules
- 功能模块的动静导入(必须在主应用程序中定义)
- 无关特定性能涵盖哪些 CMS 组件的信息(能够成为库的一部分并动态导入)。此信息以 cmsComponents 键下的字符串数组的模式提供。上面是一个例子:
{
featureModules: {
organization: {module: () =>
import('@spartacus/my-account/organization').then((m) => m.OrganizationModule
),
cmsComponents: [
'OrderApprovalListComponent',
'ManageBudgetsListComponent',
'ManageCostCentersListComponent',
'ManagePermissionsListComponent',
'ManageUnitsListComponent',
'ManageUserGroupsListComponent',
'ManageUsersListComponent',
],
},
},
}
例子:
[图片上传中 …(image.png-c043ed-1625895062179-0)]
Component Mapping Configuration in Lazy-Loaded Modules
提早加载模块中的默认 CMS 映射配置应该以与动态导入模块完全相同的形式定义。
Spartacus 可能从提早加载性能中提取 CMS 组件映射配置,并应用它来解析该性能所涵盖的组件类和工厂。这就是为什么能够并举荐应用在提早加载模块中提供默认 CMS 映射配置的规范形式的起因。因而,完全相同的模块和库入口点能够依据须要动静或动态导入,并且依然能够通过在应用程序中提供配置笼罩来从应用程序级别笼罩提早加载的 CMS 配置。
Defining Shared-Dependency Modules
通过在性能配置的依赖项属性中提供动静导入数组,能够将一些逻辑提取到共享的提早加载模块中,该模块能够定义为功能模块的提早加载依赖项。上面是一个例子:
{
featureModules: {
organization: {module: () =>
import('@spartacus/my-account/organization').then((m) => m.OrganizationModule
),
dependencies: [() =>
import('@spartacus/storefinder/core').then((m) => m.OrganizationModule
),
// ,,
],
},
},
}
当提早加载依赖它的第一个个性时,这种未命名的依赖模块只会被实例化一次。它的提供者为传递给特色模块的组合注入器做出奉献,因而,所有特色服务和组件都能够拜访依赖模块提供的服务。
Combined Injector
任何提早加载的模块都能够从根应用程序注入器和依赖模块注入器注入(即能够拜访)服务和令牌。这是可能的,因为每次实例化具备依赖项的功能模块时都会创立 CombinedInjector。
当一个被提早加载模块笼罩的 CMS 组件被实例化时,它能够注入(即拜访)以下服务:
- ModuleInjector 层次结构,从功能模块注入器开始,包含依赖模块和根注入器
- ElementInjector 层次结构,在每个 DOM 元素上隐式创立
Initializing Lazy Loaded Modules
Spartacus 提供了一个 MODULE_INITIALIZER 来代替 Angular APP_INITIALIZER 来初始化提早加载的模块。
APP_INITIALIZER 机制在任何提早加载产生之前实现应用程序的初始化,因而在加载时可能须要运行初始化逻辑的提早加载性能无奈这样做。
MODULE_INITIALIZER 注入令牌可用于在旨在提早加载的模块中提供初始化函数。MODULE_INITIALIZER 由 Spartacus 提早加载机制反对,因而,应用 MODULE_INITIALIZER 提供的初始化函数将在它们定义的模块被提早加载之前运行。
您能够像配置 APP_INITIALIZER 一样配置 MODULE_INITIALIZER。上面是一个例子:
...
import {MODULE_INITIALIZER} from '@spartacus/core';
...
export function myFactoryFunction(dependencyOne: DependencyOne) {const result = () => {// add initialization logic here};
return result;
}
@NgModule({
providers: [
{
provide: MODULE_INITIALIZER,
useFactory: myFactoryFunction,
deps: [DependencyOne],
multi: true,
},
],
})
export class MyLazyLoadedModule {}
Preparing Libraries to Work with Lazy Loading
Providing Fine-Grained Entry Points in Your Library
在您的库中提供细粒度的入口点。
从雷同的入口点混合动态和动静导入会毁坏提早加载并影响 tree-shaking,因而任何将间接用于动静导入的库都应该公开细粒度的辅助入口点以优化代码拆分。
作为常规,Spartacus 裸露性能的根入口点,例如 @spartacus/orgainzation/administration/root。这种类型的入口点蕴含所有不应或不能提早加载的代码。来自根入口点的模块应该在根应用程序中动态导入,这意味着它们将被事后加载并在主应用程序块中可用。
Separating Static Code from Lazy-Loaded Code
当您应用 Angular Dependency Injection 时,注入器中的提供程序列表在注入器初始化后不应更改。这种范式特地实用于任何多提供的令牌、处理程序,尤其实用于任何 Angular 原生多提供的令牌,例如 HTTP_INTERCEPTOR、APP_INITIALIZER 等。
后果是提早加载模块中的任何多提供令牌对于根或其余提早加载块中提供的模块和服务将不可见,但应用 unified injector 注入的多提供令牌除外。
一些 Spartacus 性能,例如 PageMetaService 或 ConverterService,应用 UnifiedInjector 来理解能够提早加载的令牌,以便全局逻辑(例如 SEO 性能)即便逻辑提早加载该性能也能牢靠地工作。例如,商店定位器页面元解析器能够在应用商店定位器性能的前提下,被提早加载。
Spartacus 配置也是通过提供配置块来定义的,因为兼容机制将配置从提早加载性能奉献到全局配置,因而解决形式略有不同。这种机制能够通过个性标记禁用,未来会默认敞开,以反对对立配置个性。
如果根服务无奈看到提早加载提供程序的问题,则始终能够将此类代码蕴含在事后可用的动态链接模块中。倡议在您的库中创立一个独自的入口点(依照常规,命名为 root,例如 my-library/root),其中蕴含起码的代码,将蕴含在主包中,并且从一开始就可用。