Оптимизация приложений на

Angular:

Максим Сальников

Angular GDE

        легче,

    быстрее,

удобнее

Как создавать Angular-приложения, которые загружаются и работают быстро

Чтобы радовать пользователей

(и нас самих)

Максим Сальников

@webmaxru

  • Google Developer Expert, Microsoft MVP

  • Организатор Mobile / Web / PWA митапов в Осло и Лондоне

  • Организатор конференций Mobile Era и ngVikings

Full-stack разработчик "приложений из будущего" в ForgeRock

  • Как работает?

  • Как начать?

  • Подробности?

Быстродействие веб-приложения

Ширина

интернет-канала

Быстродействие

устройства

}

}

Генерация бандлов меньшего размера

Оптимизации для ускорения UI

Оптимизация загрузки

  • Уменьшить общий размер бандла

  • Разбить бандл на части и сначала загружать только то, что действительно нужно для старта

  • Кешировать полученные ресурсы для их дальнейшего использования

Использование prod-режима

$ ng build --prod
  • Включает компиляцию Ahead of Time

  • Включает Build Optimizer

  • Регистрирует Angular Service Worker (если подключен)

  • Выполняет enableProdMode()

Рецепт #1

Как начать

$ ng build --prod --source-map
$ npx source-map-explorer main.bundle.js

Использование lazy loading

  • Загрузка только необходимого для запуска приложения кода

  • "Защита" модулей от загрузки через CanLoad  

  • Предзагрузка всех модулей (за исключением защищенных с помощью CanLoad) через стратегию PreloadAllModules

  • 100% гибкость при использовании собственной PreloadingStrategy

Рецепт #2

Как начать

const appRoutes: Routes = [{
  path: 'tweets',
  loadChildren: 'tweets/tweets.module#TweetsModule'
}]
const tweetsRoutes: Routes = [{
  path: '',
  component: TweetFeedsComponent
}];

app-routing.module.ts

tweets/tweets-routing.module.ts

Стратегия предзагрузки

@NgModule({
  imports: [RouterModule.forRoot(appRoutes, {
    preloadingStrategy: PreloadAllModules
  })],
  exports: [RouterModule]
})

app-routing.module.ts

Создание            

  • Application shell

  • Стратегии для динамического кеширования

  • Web App Manifest

Рецепт #3

$ ng add @angular/pwa
$ ng build --prod

}

  • Онлайн: загрузка ресурсов из Cache Storage

  • Офлайн: аналогично

Приложение

Браузер

Приложение

Service worker

Cache Storage

Браузер

Конфигурирование NGSW

src/ngsw-config.json

{
  "assetGroups": [
    Уместные значения по умолчанию для Application Shell
  ],
  "dataGroups": [
    Настройки для динамического кеширования (API, CDN, ...)
  ],
  ...
}

Динамическое кеширование

ngsw-config.js / dataGroups

{
    "name": "myApi",
    "urls": [
      "/api/archive/**"
    ],






}
    "cacheConfig": {
      "strategy": "performance",
      "maxSize": 100,
      "maxAge": "365d"
    }

}

  • performance: cache-first

  • freshness: network-first

Некоторые нюансы

  • При старте приложения ресурсы Application Shell загружаются дважды

  • Крайне рекомендуется уведомлять пользователя о доступности новой версии приложения

  • Существуют альтернативные Angular Service Worker решения

  • Работает исключительно как часть стратегии прогрессивного улучшения

Поддержка Service Worker API

Browser

Desktop

Mobile

OS

Server-side rendering

  • Ускоренная первая загрузка приложения

  • Ссылки с предпросмотром в социальных сетях

  • Лучше подходит для поисковых систем

Рецепт #4

Bootstrap

Загрузка HTML

First meaningful paint

Bootstrap

Загрузка HTML

First meaningful paint

Без SSR

SSR

<app>Загружаем...</app>

Загружаем...

Как начать

Официально 

$ ng add @ng-toolkit/universal
$ npm run build:prod && npm run server
$ ng add @nguniversal/express-engine
$ npm run build:ssr && npm run serve:ssr

От сообщества

Обнаружение изменений Angular

Данные

Обнов. DOM

События

}

Change detection

}

Отрисовка UI

Запуск

Готово

}

60FPS / 17ms

Стратегии оптимизации кода

  • Сделать каждый цикл обнаружения изменений быстрее

  • Уменьшить количество циклов обнаружения изменений

  • Обновлять DOM как можно реже

Использование pure pipes

<span>{{ relativeDate(tweet.createdAt) }}</span>

Вычисляется при каждом цикле CD

Рецепт #5

<span>{{ tweet.createdAt | relativeDate }}</span>

Вычисляется только если значение изменилось

Вызов метода

Использование pipe

Как начать

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
    name: 'relativeDate',
    pure: true
})
export class RelativeDatePipe implements PipeTransform {
  transform(date) {
    // Трансформации...
    return newDate;
  }
}

Мемоизация

 Cохранение результатов выполнения функций для предотвращения повторных вычислений

Lodash Memoize

Как начать

import { memoize } from 'lodash-decorators';
...

@memoize()
relativeDate(date) {
  // Вычисления
  return newDate;
}

Использование стратегии OnPush

Рецепт #6

  • Каждый раз

  • При любом событии

  • В каждом компоненте

}

По умолчанию

Использование стратегии OnPush

Рецепт #6

  • Каждый раз

  • При любом событии

  • В каждом компоненте

}

По умолчанию

Использование стратегии OnPush

Рецепт #6

  • Каждый раз

  • При любом событии

  • В каждом компоненте

}

По умолчанию

Использование стратегии OnPush

Рецепт #6

  • Каждый раз

  • При любом событии

  • В каждом компоненте

}

По умолчанию

Как начать

import { ..., ChangeDetectionStrategy } from '@angular/core';

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush
})
  • Изменение ссылки в @Input

  • Событие в компоненте или его потомках

  • Явный запуск обнаружения изменений

  • Приход следующего значения Observable, используемой в шаблоне через async pipe

ChangeDetectionStrategy.Default

ChangeDetectionStrategy.OnPush

Собственная стратегия CD

  • Отключение CD для компонента

  • Запуск кода вне Angular CD

Рецепт #7

Как начать

import { ..., ChangeDetectorRef } from '@angular/core';

class RealTimeList {
  constructor(private ref: ChangeDetectorRef) {
    ref.detach();
    setInterval(() => {
      this.ref.detectChanges();
    }, 1000);
  }
}
  • detach() / reattach()

  • detectChanges() / tick() / markForCheck()

Как начать

import { ..., NgZone } from '@angular/core';

class MyComponent implements OnChanges {

  constructor(private _ngZone: NgZone ) {}

  log() {
    this._ngZone.runOutsideAngular(() => {
      // Код здесь не вызовет CD
    }}));
  }
}

Добавление trackBy в ngFor

  • Операции с DOM медленные

  • При иммутабельном подходе каждый раз создается новый набор элементов DOM

Рецепт #8

Как начать

export class TweetListComponent implements OnInit {
  ...

  trackById(index, tweet) {
      return tweet.id;
  }
}
<div *ngFor="let tweet of tweets; trackBy: trackById">

Быстродействие приложения

Сеть

UI

}

}

  • Production режим

  • Lazy loading

  • Service worker

  • Server-side rendering

  • Pure pipes

  • Мемоизация

  • Стратегии CD

  • Использование trackBy

Спасибо!

@webmaxru

Максим Сальников

Вопросы?

Оптимизация приложений на Angular: легче, быстрее, удобнее

By Maxim Salnikov

Оптимизация приложений на Angular: легче, быстрее, удобнее

Благодаря огромному количеству добавляемых улучшений внутри Angular, приложения на нем становятся все производительнее с каждой новой версией фреймворка даже без дополнительных усилий с нашей стороны. Тем не менее главная ответственность за быстродействие приложения все же на нас. Давайте рассмотрим некоторые приемы, позволяющие нам помочь фреймворку собирать приложения, которые браузер сможет загружать быстрее и исполнять быстрее. Все ради удобства наших пользователей!

  • 1,985