При разработке мобильного приложения вы всегда должны обращать внимание на производительность и всегда оптимизировать её. Сегодня мы покажем как оптимизировать время загрузки приложений с Angular с «ленивой загрузкой Angular».
При разработке мобильных приложений на Angular 2, в результате у вас может получиться очень большой размер файла, при этом приложение будет долго загружаться на устройстве. К счастью, у роутера Angular есть удобная функция, называемая «ленивая загрузка», с которой можно сильно уменьшить время первой загрузки приложения.
С ленивой загрузкой мы можем разделить приложение на функциональные модули и вызывать их только при необходимости. Суть в том, что вначале мы можем показать пользователю только то, что он ожидает увидеть на экране. Остальные модули постепенно подгрузятся позже, когда пользователь будет вызывать их.
Использование ленивой загрузки с NativeScript
Встроенный в Angular 2 модуль загрузки использует SystemJS. Но при разработке приложений NativeScript оптимальнее использовать свой загрузчик модулей.
Далее мы рассмотрим приложение lazyNinjas, в котором есть два модуля — HomeModule (без ленивой загрузки) и NinjasModule (с ленивой загрузкой). В репозитории этого приложения есть две ветки — callback-loading и custom-module-loader, и в следующих двух разделах мы рассмотрим оба этих подхода к разработке. Уделите минуту этому приложению, чтобы знать о чём пойдёт речь далее — скачайте его с github и запустите.
Обратный вызов в свойство `loadChildren`
Рассмотрим конфигурацию нашего роутера:
// app-routing.ts
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { routes as homeRoutes } from "./home/home.routes";
const routes = [
...homeRoutes,
{
path: "ninjas",
loadChildren: () => require("./ninjas/ninjas.module")["NinjasModule"]
}
];
export const routing = NativeScriptRouterModule.forRoot(routes);
Массив `routes` это реальная конфигурация роутера в нашем приложении. Сначала мы добавили роуты модуля HomeModule с помощью оператора ‘…’ . Затем зарегистрировали лениво-загруженный NinjasModule. Обратим внимание на значение, передаваемое нами в свойство `loadChildren`:
loadChildren: () => require("./ninjas/ninjas.module.js")["NinjasModule"]
Здесь мы передаём ему обратный вызов. Рассмотрим подробнее, что будет дальше.
Сначала в файле «./ninjas/ninjas.module.ts» мы описали NinjasModule с его роутами, затем импортировали их методом `forChild` из `NativeScriptRouterModule`. Вот как он выглядит:
import { NgModule } from "@angular/core";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { NinjasComponent } from "./ninjas.component";
import { routes } from "./ninjas.routes";
@NgModule({
imports: [
NativeScriptRouterModule,
NativeScriptRouterModule.forChild(routes)
],
declarations: [NinjasComponent]
})
export class NinjasModule { }
Помните, что в экспортированном объекте есть всего один элемент — `NinjasModule`.
loadChildren: () => require("./ninjas/ninjas.module.js")["NinjasModule"]
Мы можем опустить расширение файла и записать это так:
loadChildren: () => require("./ninjas/ninjas.module")["NinjasModule"]
Эта версия приложения находится в ветке callback-loading.
Свой загрузчик модулей
Вместо передачи обратного вызова каждому свойству `loadChildren`, мы можем вынести механику загрузки в отдельный загрузчик. Затем мы можем его использовать вместо встроенного на базе SystemJS.
Посмотрим на NinjaModuleLoader:
// ninja-module-loader.ts
import {
Injectable,
Compiler,
NgModuleFactory,
NgModuleFactoryLoader
} from "@angular/core";
import { path, knownFolders } from "file-system";
const SEPARATOR = "#";
@Injectable()
export class NinjaModuleFactoryLoader implements NgModuleFactoryLoader {
constructor(private compiler: Compiler) {
}
load(path: string): Promise
let {modulePath, exportName} = this.splitPath(path);
let loadedModule = require(modulePath)[exportName];
if (!loadedModule) {
throw new Error(`Cannot find "${exportName}" in "${modulePath}"`);
}
return this.compiler.compileModuleAsync(loadedModule);
}
private splitPath(path: string): {modulePath: string, exportName: string} {
let [modulePath, exportName] = path.split(SEPARATOR);
modulePath = getAbsolutePath(modulePath);
if (typeof exportName === "undefined") {
exportName = "default";
}
return {modulePath, exportName};
}
}
function getAbsolutePath(relativePath: string) {
return path.normalize(path.join(knownFolders.currentApp().path, relativePath));
}
В этом загрузчике реализован `NgModuleFactoryLoader` с единственным методом — `load` с одним параметром — путём, который мы передаём свойству `loadChildren`.
Мы должны изменить свойство `loadChildren` в конфигурации роутера:
loadChildren: "./ninjas/ninjas.module#NinjasModule"
В частном методе `splitPath` в NinjasModuleLoader мы храним расположение файла модуля и экспортируемое имя модуля. Затем мы можем запросить нужный модуль так же, как в функции обратного вызова:
let loadedModule = require(modulePath)[exportName];
Вынос логики загрузки позволяет нам проверять существование модуля и выдавать соответствующее сообщение в противном случае.
if (!loadedModule) {
throw new Error(`Cannot find "${exportName}" in "${modulePath}"`);
}
Если модуль успешно загружен, асинхронно скомпилируем его компилятором Angular:
return this.compiler.compileModuleAsync(loadedModule);
Теперь нам нужно зарегистрировать наш загрузчик в главном модуле*.
// app.module.ts
import { NgModule, NgModuleFactoryLoader } from "@angular/core";
...
import { NinjaModuleLoader } from "./ninja-module-loader";
@NgModule({
...
providers: [
{ provide: NgModuleFactoryLoader, useClass: NinjaModuleLoader }
]
})
export class AppModule { }
Финальную версию приложения вы можете взять в ветке custom-module-loader.
* У вас может быть разный загрузчик модулей в каждом NgModule!
Реальные приложения NativeScript с модулем ленивой загрузки вы можете взять в наших примерах SDK. В них более 100 различных компонент, каждый со своим роутом и ~15 функциональными модулями. В таблице ниже можно увидеть время первого старта приложения с и без ленивой загрузки.
По материалам Optimizing app loading time with Angular 2 Lazy Loading
Разработчик: java, kotlin, c#, javascript, dart, 1C, python, php.
Пишите: @ighar. Buy me a coffee, please :).