Оптимизация времени загрузки приложения NativeScript с «ленивой загрузкой» Angular 2

Оптимизация времени загрузки приложения NativeScript с "ленивой загрузкой" Angular 2

При разработке мобильного приложения вы всегда должны обращать внимание на производительность и всегда оптимизировать её. Сегодня мы покажем как оптимизировать время загрузки приложений с Angular с «ленивой загрузкой Angular».

При разработке мобильных приложений на Angular 2, в результате у вас может получиться очень большой размер файла, при этом приложение будет долго загружаться на устройстве. К счастью, у роутера Angular есть удобная функция, называемая «ленивая загрузка», с которой можно сильно уменьшить время первой загрузки приложения.

С ленивой загрузкой мы можем разделить приложение на функциональные модули и вызывать их только при необходимости. Суть в том, что вначале мы можем показать пользователю только то, что он ожидает увидеть на экране. Остальные модули постепенно подгрузятся позже, когда пользователь будет вызывать их.

Оптимизация времени загрузки приложения NativeScript с "ленивой загрузкой" Angular 2

Использование ленивой загрузки с 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 функциональными модулями. В таблице ниже можно увидеть время первого старта приложения с и без ленивой загрузки.

Оптимизация времени загрузки приложения NativeScript с "ленивой загрузкой" Angular 2

По материалам Optimizing app loading time with Angular 2 Lazy Loading

Leave a Comment