Оптимизация приложений на
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