[Перевод] Оптимизация Angular-приложений

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

xjpqajkqroo6_ovnkv7s5-iczig.png

В материале, перевод которого мы публикуем сегодня, будут раскрыты шесть направлений оптимизации Angular-приложений.

1. Ленивая загрузка и оптимизация основного бандла


Если при подготовки продакшн-версии приложения ленивая загрузка не используется, то, скорее всего, в папке dist вы увидите следующие файлы.

polyfills.js
scripts.js
runtime.js
styles.css
main.js


Файл polyfills.js позволяет обеспечить совместимость приложения, написанного с использованием свежих возможностей веб-технологий, с различными браузерами.

Файл script.js содержит скрипты, описанные в разделе scripts файла angular.json. Вот простой пример такого раздела.

"scripts": [
   "myScript.js",
]


Файл runtime.js — это загрузчик Webpack. Он содержит средства Webpack, необходимые для загрузки других файлов.

Файл styles.css содержит стили, объявленные в разделе styles файла angular.json. Вот пример этого раздела.

"styles": [
  "src/styles.css",
  "src/my-custom.css"
],


Файл main.js хранит весь код приложения, включая компоненты (TS, HTML и CSS-код), конвейеры, директивы, сервисы и импортированные модули (включая модули сторонних разработчиков).

По мере роста и развития приложения растёт и размер файла main.js. Это может превратиться в проблему, так как, для того чтобы сформировать страницу, браузеру, кроме решения прочих задач по визуализации данных, нужно загрузить и разобрать файл main.js. Если этот файл достаточно велик, его обработка окажется непростой задачей не только для мобильных, но и для настольных браузеров.

Легче всего решить эту проблему путём разделения приложения на несколько модулей, при работе с которыми используется методика ленивой загрузки. При таком подходе Angular генерирует для каждого модуля отдельный файл, который не будет загружаться до тех пор, пока в нём не возникнет необходимости (обычно — при активации некоего маршрута).

Для того чтобы продемонстрировать применение методики ленивой загрузки, было создано два компонента — app.component и second.component. Оба они находятся в модуле app.module, ленивая загрузка при работе с ними не применяется.

Компонент app.component крайне прост. Он выводит две кнопки, одна из которых отвечает за переход к second.component, а вторая ведёт обратно к app.component.

38fba16b9ab19b462ac0e46a4c0d8aca.png


Компонент App

В шаблоне компонента Second содержится очень большой фрагмент текста объёмом примерно 1 Мб.

020de14260117543f978d87c9a89c23a.png


Компонент Second

Так как методика ленивой загрузки здесь не применяется, у нашего приложения будет файл main.js большого размера, содержащий код app.component и second.component.

Если открыть инструменты разработчика Chrome и посмотреть на закладку Network, можно оценить размеры файла main.js. А именно, это — 1.3 Мб.

0b2babc5be0ab47b9254c1d8af87c8de.png


Анализ размеров файла средствами инструментов разработчика Chrome

Проблема тут заключается в том, что большую часть времени при работе с нашим проектом пользователь будет находиться на его главной странице, а не на какой-то другой, поэтому загружать весь код в виде единого файла — не лучшая идея. Код второго компонента можно вынести в отдельный модуль, который будет загружаться только в том случае, если пользователь перейдёт на соответствующую страницу. Это выражается в значительном уменьшении файла main.js, что даёт очень быструю первую загрузку главной страницы сайта.

При использовании методики ленивой загрузки после завершения процесса сборки проекта будет создан файл наподобие 4.386205799sfghe4.js. Именно тут будет храниться код, который не загрузится при первой загрузке сайта. Как результат, если теперь открыть сайт и проанализировать его, окажется, что размеры main.js составляют лишь 266 Кб.

721d89f544d30d37693de7d9cc190399.png


Уменьшение размеров main.js

Большой дополнительный файл размером 1 Мб загружается только после перехода на соответствующую страницу.

b588ec6cb1c63fb67623b561fde85860.png


Загрузка дополнительного файла

Ленивую загрузку мы применили, но нельзя сказать, что такое решение нас полностью устраивает. Дело в том, что такой подход замедляет первый переход пользователя к странице, для вывода которой нужен отдельный большой файл. К счастью, Angular предоставляет средство для решения этой проблемы. А именно, речь идёт о технологии PreloadingStrategy.

Пользуясь ей, мы можем сообщить фреймворку о том, чтобы, после того, как будет загружен и обработан главный модуль (файл main.js), он загрузил бы, в фоновом режиме, другие модули. В результате, когда пользователь перейдёт на страницу, для отображения которой раньше требовалась загрузка большого файла, окажется, что этот файл уже загружен. Вот пример кода, обеспечивающего предварительную загрузку всех модулей.

import { PreloadAllModules, RouterModule } from '@angular/router';
RouterModule.forRoot(
[
 {
    path: 'second',
    loadChildren: './second/second.module#SecondModule'
 } 
], {preloadingStrategy: PreloadAllModules})


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

2. Анализ бандлов с использованием Webpack Bundle Analyzer


Если даже после разделения логики проекта на множество модулей оказывается, что main.js всё ещё слишком велик (для приложений малого и среднего размера автор этого материала предлагает считать большим файл в 1 Мб), можно продолжить оптимизацию приложения с помощью Webpack Bundle Analyzer. Этот npm-пакет позволяет визуализировать результаты работы Webpack в виде древовидной структуры, поддерживающей изменение масштаба просмотра. Для того чтобы воспользоваться Webpack Bundle Analyzer, установим его в Angular-проект в качестве зависимости разработки.

npm install --save-dev webpack-bundle-analyzer


Затем модифицируем раздел script файла package.json, добавив в него следующий текст.

"bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/stats.json"


Обратите внимание на то, что адрес файла dist/stats.json может быть в вашем проекте другим. Например, если у вас готовые файлы бандла оказываются в папке dist/browser, вам нужно будет переписать вышеупомянутую строчку так: dist/browser/stats.json.

Теперь запустим анализатор.

npm run bundle-report


После выполнения этой команды будет сформирована продакшн-сборка приложения и будут выведены статистические данные по каждому бандлу. Вот как выглядит результат визуализации этих данных.

1984d25b162761a21041333e2350bd00.png


Анализ проекта средствами Webpack Bundle Analyzer

Теперь можно проанализировать состав каждого бандла. Это весьма полезный инструмент, который позволяет выявлять зависимости, без которых можно обойтись.

3. Создание нескольких маленьких модулей для совместного использования


Модули, которые совместно используют разные части приложения, способствуют реализации принципа DRY, но иногда даже такие модули, по мере развития приложения, становятся всё больше и больше. Например, если у нас имеется некий модуль SharedModule, содержащий множество других модулей, компонентов, конвейеров, импорт подобного модуля в app.module увеличит размер бандла main.js, так как подобный ход приведёт не только к импорту того, что нужно main.js, но и всего того что есть в SharedModule. Для того чтобы избежать подобной ситуации, можно создать ещё один модуль, нечто вроде HomeSharedModule, предназначенный для совместного использования главным модулем и его компонентами.

Наличие в проекте нескольких модулей, предназначенных для совместного использования, лучше наличия всего одного такого модуля, обычно имеющего большие размеры.

4. Использование методики ленивой загрузки для изображений, которые не видны на странице


При первой загрузке главной страницы приложения может оказаться так, что на ней есть изображения, которые пользователю не видны (они находятся за пределами области просмотра). Для того чтобы их увидеть, надо прокрутить страницу. Но эти невидимые изображения загружаются при загрузке страницы. Если их окажется много — это повлияет на скорость загрузки страницы. Для того чтобы справиться с этой проблемой, можно применить к изображениям методику ленивой загрузки, загружая их лишь тогда, когда пользователь до них доберётся. Есть один полезный JS-инструмент, Intersection Observer, который позволяет легко реализовать ленивую загрузку изображений. Более того, в целях повторного использования, на его основе можно создать соответствующую директиву. Подробности об этом можно почитать здесь.

5. Использование виртуальной прокрутки для длинных списков


В седьмой версии Angular есть возможность использования виртуальной прокрутки. Эта технология загружает элементы в DOM и выгружает их, основываясь на том, какая часть списка видна пользователю. Это значительно ускоряет работу приложений, в которых используются длинные списки.

94362de27756c8707fd5e7f3b5f31ba9.png


На страницу выводятся лишь видимые элементы списка

6. Использование для работы со шрифтами стратегии FOUT вместо стратегии FOIT


На многих сайтах используются нестандартные шрифты. Выглядят они обычно весьма привлекательно, но их применение создаёт дополнительную нагрузку на браузер, так как ему приходится загружать эти шрифты и готовить их к работе. При использовании нестандартных шрифтов, скажем, загружаемых из стороннего сервиса вроде Google Fonts, возможны следующие два сценария:

  1. Браузер загружает шрифт, обрабатывает его и только после этого выводит текст. До тех пор, пока шрифт не будет готов к работе, текст, набранный этим шрифтом, будет невидимым. Это называют FOIT (Flash of invisible text).
  2. Браузер изначально выводит текст с использованием стандартного шрифта, при этом выполняя загрузку внешнего шрифта. Когда этот шрифт будет готов к работе, стандартный шрифт меняется на этот особый шрифт. В результате оказывается, что текст на странице будет выведен стандартным шрифтом до тех пор, пока не будет загружен особый шрифт, после чего текст будет выведен повторно, но уже новым шрифтом. Это называется FOUT (Flash of unstyled text).


Большинство браузеров при работе с нестандартными шрифтами используют стратегию FOIT, стратегия FOUT применяется лишь в Internet Explorer. Для того чтобы вместо FOIT использовать FOUT, можно применить дескриптор font-display для @font-face, и сообщить браузеру о том, хотим ли мы, чтобы текст сначала выводился бы стандартным шрифтом, а потом — нашим, или нас устроит некий период невидимости текста. Если вас интересует тема шрифтов — взгляните на этот материал. В частности, здесь можно найти сведения об особенностях работы шрифтов и рекомендации, касающиеся выбора стратегии FOIT или FOUT.

Итоги


Здесь мы рассмотрели несколько методик оптимизации Angular-приложений. На самом деле, их существует гораздо больше. В частности, речь идёт о серверном рендеринге, об использовании сервис-воркеров, об AMP-страницах. Целесообразность оптимизации и выбор её методик зависят от конкретного проекта — от его особенностей и целей.

Уважаемые читатели! Какие подходы вы используете для оптимизации Angular-приложений?

1ba550d25e8846ce8805de564da6aa63.png

© Habrahabr.ru