ionic3.x LazyLDoad 实现方法的探究
什么是 Lazy Load
LazyLoad 就是懒加载,Angular2 对懒加载提供了很好的支持,只需要在根模块的路由上写一个
1 | // app.module.ts |
只要提供 loadChildren 字段应用初始化的时候就不会加载 report
这个模块,而是会在用到它的时候才加载它,这会缩减应用初始化时需要加载的文件大小,以此来提升应用的启动速度。
LazyLoad Page 在 ionic3.x 中的用法
step1:
为需要懒加载的组件添加 IonicPage
装饰器,例如这个 HomePage
组件
1 | // home-page.ts |
step2:
为这个组件添加一个 IonicPageModule
1 | // home-page.module.ts |
Angular 4.x Lazy Load 的实现方式
1. 使用 SystemJS 实现(不使用 webpack 的方式)
1 | // packages/core/src/linker/system_js_ng_module_factory_loader.ts |
如果应用是基于 SystemJS 而不是使用 angular-cli 来构建的,这里直接调用 System.import
来异步加载所需要的模块。
2. 使用 @ngtools/webpack
实现
webpack 其实是支持把 System.import()
作为一个代码切分点,但仅支持某些动态情景(如 System.import('routes/' + module + '.js')
),webpack会根据 'routes/' + module + '.js'
里的 routes/
和 .js
等静态信息推测所有情景,并生成对应的 context module 负责加载这些动态模块。不过对于纯表达式(如 System.import(module)
),没有任何已知信息,webpack 就没有办法切分代码了,而这也就是我们遇到的情况。既然 webpack 自身无法预处理,那就是由开发者来告诉 webpack 如何处理,这就是 @ngtools/webpack
的处理思路,它同过分析 loadChildren
的值,抽取需要懒加载的模块信息,打包这些模块,并生成对应的 context module,建立映射对象,映射对象大概是下面这个样子:
1 | var map = { |
动将这些文件打包成单独的代码块,并用即使执行函数对它们进行包装,System.import
会被webpack 替换为 __webpack_require__
方法。在运行时通过这个方法来加载所需要的模块。
Ionic 3.x 基于 ionic-cli 的实现方式
deep link system
deep link system 是一个用于在 ionic App 中进行导航的系统,他会维护 app 内部的页面的名称和组件的对应关系,
首先创建一个 NgModule
1 | // my-page.module.ts |
然后为页面组件添加 @ionicPage 装饰器,
1 | // my-page.ts |
ionic-cli 会自动为这个页面创建一个 deep link,链接的名称默认与组件名称一致,我们在 App 中可以使用 ‘MyPage’ 字符串进行页面导航。
1 | @Component({ |
懒加载正是基于这种方式实现的,下面来说一下过程
构建过程
遍历所有的 ts 文件
解析 ts 文件的语法树,获取语法树中的 class declaration 节点
遍历 class declaration 节点上的 decorators 节点取得 IonicPage 节点
解析 IonicPage 装饰器的元数据,(一个页面中只允许存在一个 Ionicpage 装饰器)
根据被 IonicPage 所在的文件名,按照 ionic-cli 定义的规则获得 Component 对应的 PageModule 文件路径
如果是 AOT 模式则将 Module 的文件名修改为
.ngfactory.ts
将路径信息和 Module 的元数据(
DeepLinkDecoratorData
类型)添加到deepLinkConfigEntries
数组中将这个数组中的信息转换成以下格式的字符串,并缓存
1
2
3
4
5
6
7
8
9
10
11{
links: [
{
loadChildren: '../pages/xyz/xyz-home/xyz-home.module#XyzPageModule',
name: 'XyzPage',
segment: 'xyz-home',
priority: 'low',
defaultHistory: []
}
]
}将这段代码插入到 app.module.ts 中
1 | import {NgModule} from '@angular/core'; |
这个对象被当做 IonicModule.forRoot
的第三个参数 deepLinkConfig
传入
,这个参数在后面会被当做一个 ValueProvider
(DeepLinkConfigToken:OpaqueToken) 加入到 IonicModule
中
10.在 webpack 阶段,猜测:使用了和 @ngtools/webpack
工具类似的方法,讲需要懒加载的代码切分成几个单独的文件,以供运行时动态加载。
运行时
Ionic3.x 中页面路由采用了一种全新的方式,使用了一个叫做 DeepLinker 的服务来维护页面的路由信息
具体的操作如下:
当我们调用 NavController.push
方法,并且传入一个字符串(Page 的名称)的时候,ionic 就会加载这个页面组件
push 会触发
NavControllerBase
中_loadLazyLoading
的调用,调用时会将页面名称以及参数等信息传入该方法在
_loadLazyLoading
方法中会调用一个叫做convertToViews
的方法,这个方法会将传入的 PageName 转换为一个可以使用的 Component。1
2
3
4
5
6
7
8
9
10
11NavControllerBase.prototype._loadLazyLoading = function (ti) {
var _this = this;
var /** @type {?} */ insertViews = ti.insertViews;
if (insertViews) {
(void 0) /* assert */;
return convertToViews(this._linker, insertViews).then(function (viewControllers) {
...
});
}
return Promise.resolve();
};如果传入的是 PageName 字符串,会调用
getComponent
函数将字符串对应的 Component 所在的 js 文件加载进来。getComponent
函数使用deeplinker
中的getComponentFromName
方法获取这个页面对应的路径。路径保存在上文提到的linker
对象的loadChildren
属性中。使用 SystemJS 提供的
System.import
方法来动态加载这些文件,webpack2.x 支持System.import
的模块,它会将System.import
替换为__webpack_require__
(但据说在 Webpack3.x 中已经废弃了对 System.import 的支持),同时被单独打包的模块会被即使执行函数表达式(IIFE)包裹,__webpack_require__
这个方法执行后会创建一个 JSONP 请求,也就是在 Header 中插入一个 script 标签使浏览器请求需要的脚本,并立即执行该脚本,这样就得到了需要的模块。
Next Step
- 上述内容只研究了页面的懒加载方式,至于 Pipe,Directive 以及其他非页面的 Component 的懒加载实现方式还有待实践和研究。
- 构建时代码切分的处理还没有找到,看生成的代码中有着和
@ngtools/webpack
生成的代码中相似的结构
1 | // src lazy 代码块 |
所以推测ionic-cli 同样是实现了和 @ngtools/webpack
类似的能力。