返回博客首页
← 所有文章

使用 Angular 延迟加载优化应用加载时间

2016 年 12 月 2 日 — 作者: Stanimira Vlaeva

在开发移动应用时,您应该始终注意性能并进行优化。在这篇文章中,我将向您介绍一种非常有效的模式,用于优化使用 Angular 时应用程序的加载时间。这就是“Angular 延迟加载”。

您现在可以观看 2018 年 6 月在 YouTube 上发布的关于 Angular/Vue.js 代码共享网络研讨会 的完整视频!
使用 Angular 开发移动应用可能会导致应用程序文件过大,从而影响启动时间。幸运的是,Angular 路由器提供了一个名为延迟加载的强大功能,可以帮助我们减少初始加载时间。

什么是延迟加载?

使用延迟加载,我们可以将应用程序拆分为功能模块,并按需加载它们。主要优点是,我们只加载用户在启动屏幕上期望看到的内容。其他模块只有在用户导航到其路由时才会加载。

查看 Victor Savkin 的这篇博文,获取有关延迟加载的更深入信息。

在 NativeScript 中使用延迟加载

Angular 内置的模块加载器使用 SystemJS。但是,在开发 NativeScript 应用时,我们不需要设置 SystemJS 来创建延迟加载的模块。我们可以使用 NativeScript 模块加载器(随 NativeScript Angular 分发,推荐的方式)、编写自己的模块加载器,或者完全不使用模块加载器进行延迟加载。

在接下来的部分中,我们将使用简单的 lazyNinjas 应用,它有两个模块 - HomeModule(未延迟加载)和 NinjasModule(延迟加载)。我们将在接下来的部分中介绍启用延迟加载的不同方法。花点时间熟悉一下忍者 - 从 github 下载应用程序并运行它

提供 NativeScript 模块加载器

我们可以通过在根 NgModule 中提供另一个模块加载器来替换默认的应用程序模块加载器。对于 NativeScript 应用程序,我们建议使用 NSModuleFactoryLoader

现在,让我们看一下路由器配置

`routes` 数组是我们应用程序的实际路由器配置。
首先,我们使用 ES2015 展开运算符('...')添加 HomeModule 路由。

...homeRoutes,

然后,我们注册延迟加载的模块。当用户导航到 '/ninjas' 时,Angular 将加载位于 './ninjas/ninjas.module' 中的 NinjasModule。

 
   {
        path: "ninjas",
        loadChildren: "./ninjas/ninjas.module#NinjasModule",
   }

在文件 "./ninjas/ninjas.module.ts" 中,我们定义了 NinjasModule 类,以及它的路由。我们使用 `NativeScriptRouterModule` 的 `forChild` 方法导入路由。以下是示例:

就这样!我们建议在使用 NativeScript 应用程序时使用这种延迟加载方法。
如果我们想要嵌套延迟加载模块,我们应该在所有包含延迟加载路由的 NgModule 中提供模块加载器。作为参考,请查看 lazyNinjas 存储库中的 nested-lazy-modules 分支。
在接下来的部分中,我们将介绍两种在更复杂场景中可以使用的方法。

为 `loadChildren` 属性提供回调函数

还记得路由器配置中的 loadChildren 方法吗?不记得了?这里再次展示:

 
const routes = [
        ...homeRoutes,
        {
              path: "ninjas",
              loadChildren: "./ninjas/ninjas.module#NinjasModule"
        }
];

我们使用它来指定 NinjasModule 的路径。在内部,模块加载器会获取该字符串并使用它从文件中加载模块。但是,我们可以传递回调函数而不是字符串。例如:

loadChildren: () => require("./ninjas/ninjas.module.js")["NinjasModule"]

让我们一步步看一下发生了什么。

首先,在 "./ninjas/ninjas.module.ts" 文件中,我们导出了 NgModule。这被转译成以下 JavaScript 代码:

这使得我们可以像在传递给 `loadChildren` 属性的回调函数中一样,获取模块对象(exports)并查询 "NinjasModule"。

loadChildren: () => require("./ninjas/ninjas.module.js")["NinjasModule"]

我们也可以省略文件的扩展名,并将上面的代码写成:

loadChildren: () => require("./ninjas/ninjas.module")["NinjasModule"]

您可以在 callback-loading 分支中找到应用程序的这个版本。

提供自定义模块加载器

与其为每个 `loadChildren` 属性传递回调函数,不如将加载逻辑提取到模块加载器中。这正是我们建议使用 NativeScript 模块加载器的原因。但是,我们也可以创建自己的加载器并使用它。

让我们看一下在我们的简单应用程序中使用的 NinjaModuleLoader

加载器实现了 `NgModuleFactoryLoader`,它只有一个方法 - `load`,它带有一个参数 - 路径,我们将将其传递给 `loadChildren` 属性。我们将采用内置模块加载器使用的相同语法。如 Angular 文档中所述,“延迟加载模块的位置是一个字符串,而不是一个类型... 该字符串标识模块文件和模块类,后者与前者用 # 分隔。”

我们应该更改路由器配置中的所有 `loadChildren` 属性。幸运的是,我们只有一个

loadChildren: "./ninjas/ninjas.module#NinjasModule"

NinjasModuleLoader 的私有 `splitPath` 方法用于提取模块文件位置和导出的模块名称。然后,我们可以像在回调函数中一样简单地获取模块

let loadedModule = require(modulePath)[exportName];

提取加载逻辑允许我们检查模块是否存在,如果不存在,则抛出有意义的错误消息。

if (!loadedModule) {
   throw new Error(`Cannot find "${exportName}" in "${modulePath}"`);
}
如果成功获取模块,我们将使用 Angular 编译器异步编译它
return this.compiler.compileModuleAsync(loadedModule);

最后,我们需要在根模块中注册我们的模块加载器**。**

您可以在 custom-module-loader 分支中找到应用程序的最终版本。

* 请注意,您可以为每个 NgModule 使用不同的模块加载器!

延迟加载的优势

对于具有延迟加载模块的真实 NativeScript 应用程序,您可以查看我们的 SDK 示例。它有超过 100 个不同的组件,每个组件都有自己的路由,以及大约 15 个功能模块。下表展示了有无延迟加载的启动时间。

平台

设备

无延迟加载

有延迟加载

Android

Nexus 5

~13 秒

~4 秒

iOS

iPhone 5s

~12 秒

~3 秒


对于我们的 2.5 版本,我们正在努力启用 提前编译Webpack 2,并使用 树摇混淆,这将显著提高加载时间。请在接下来的几周内关注更新。