Оптимизация времени загрузки приложения 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<NgModuleFactory<any>> {
        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
0 комментариев
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.