В первой статье цикла об исследовании инструментов для анализа производительности сайта мы рассмотрели вкладку Performance. В этом материале познакомимся с таким пакетом как Webpack Bundle Analyzer. В качестве тестового проекта использована сборка на базе Next.js 15-й версии с app router.

Общие сведения

Webpack Bundle Analyzer — это npm пакет, который может наглядно продемонстрировать:

  • как ваш проект разбивается на части;

  • сколько эти части весят;

  • какие npm-пакеты загружаются вместе с вашим кодом внутри бандлов.

Почему этот инструмент так важен при разработке сайтов, особенно высоконагруженных и больших?

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

Рассматриваемый пакет поможет frontend-разработчикам найти неоптимизированные части своего приложения, которые негативно влияют на скорость загрузки страниц.

От слов к делу

Перейдем к детальному обзору Webpack Bundle Analyzer. Так как для примера взят проект, реализованный на Next.js, то для анализа бандлов использован адаптированный пакет next/bundle-analyzer. Этот пакет предоставляет тот же самый набор функций, что и оригинальный.

Начнем с установки и быстрой настройки. Для этого надо добавить пакет как dev зависимость.

yarn add -D @next/bundle-analyzer

После этого в файл package.json можно для удобства дописать следующую строку:

"build:analyze": "ANALYZE=true yarn run build"

Чтобы команда начала работать, необходимо модернизировать файл конфига next.config.js.

const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({...});

Теперь проектготов к анализу, для этого запустим нашу добавленную команду и посмотрим на те отчеты, которые сгенерировал анализатор:

  • nodejs — отчет по серверной части приложения;

  • client — отчет по клиентской части;

  • edge — отчет о бандлах, которые используются в middleware.

Рис. 1 - Общий вид отчета о серверной части кода
Рис. 1 - Общий вид отчета о серверной части кода

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

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

  • stat — искомый вес файла до применения каких‑либо минификаций и сжатий;

  • parsed — вес файла после применения какого‑либо способа минификации кода;

  • gzipped — вес файла после применения алгоритма сжатия.

Рис. 2 - Общий отчет о клиентской части кода
Рис. 2 - Общий отчет о клиентской части кода

Последние два пункта (parsed и gzipped) можно также увидеть во вкладке devtools Network в колонке size. Первое число в красной рамке является значением, которое загружается по сети (gzipped), а второе число — это вес кода этого бандла после парсинга (parsed).

Рис. 3 - Размер файла страницы
Рис. 3 - Размер файла страницы

Базовый пример использования

После того как мы познакомились с функционалом пакета, приступим к изучению проекта. Начнем с самого просто примера — поиск больших внешних зависимостей.

Рассмотрим один из отчетов клиентской части тестового проекта.

Рис. 4 - Пример отчета с проблемным lodash.js
Рис. 4 - Пример отчета с проблемным lodash.js

Из рисунка 4 видно, что у нас есть огромный пакет lodash.js, который выделен в отдельный чанк. Если мы посмотрим на импорт этого пакета в проекте, то найдем лишь 1 вхождение, где используется функция isNumber. Проблема в том, что в этом месте lodash импортируется и используется в следующем виде:

import _ from 'lodash';

В этот файл подключается вся библиотека целиком, хотя используется лишь одна функция: _.isNumber(...). В таком случае наш проект раздувается понапрасну.

Варианты решения:

  • убрать эту библиотеку целиком и написать свой аналог самостоятельно — вариант является хорошим выбором, когда мы говорим о каких-то несложных вещах;

  • заменить на более легковесную библиотеку. Например, есть библиотека moment.js для работы с датами, она имеет больший размер и ее можно заменить похожими библиотеками меньшего веса (date-fns);

  • импортировать в компонент только ту функцию, которую собираетесь использовать - подходит для таких библиотек как lodash, которые внутри себя имеют отдельные модули с функциями.

Рис. 5 - Размер библиотеки moment,js
Рис. 5 - Размер библиотеки moment,js

Для решения проблемы в тестовом проекте применим последний способ. Импортируем только функцию isNumber.

import isNumber from 'lodash/isNumber';

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

Рис. 6 - Пример отчета с сокращенном размером пакета lodash
Рис. 6 - Пример отчета с сокращенном размером пакета lodash

Размер бандла существенно сократился.

stat

parsed

gzipped

import _ from 'lodash';

531.35 кБ

67.83 кБ

24.34 кБ

import isNumber from 'lodash/isNumber';

4.48 кБ

899 Б

418 Б

Из таблицы 1 мы видим, что размеры сократились довольно сильно. Также это можно заметить и по результатам сборки проекта.

Рис. 7 - Вариации сборок проекта
Рис. 7 - Вариации сборок проекта

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

import { isNumber } from 'lodash';

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

Динамический импорт

По мимо примера с внешними зависимостями, стоит также рассмотреть и динамический импорт компонентов. Для начала посмотрим, что происходит при импорте компонентов в клиентские и серверные.

Начнем с варианта, когда оба компонента клиентские.

Рис. 8 - Чанк страницы без использования динамического импорта
Рис. 8 - Чанк страницы без использования динамического импорта

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

Рис. 9 - Зависимости компонента Test
Рис. 9 - Зависимости компонента Test

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

Рис. 10 - Запросы без использования динамического импорта
Рис. 10 - Запросы без использования динамического импорта

Теперь рассмотрим вариант, когда компонент Test импортируется динамически.

Рис. 11 - Бандл страницы при использовании динамического импорта
Рис. 11 - Бандл страницы при использовании динамического импорта

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

Рис. 12 - Чанк компонента Test
Рис. 12 - Чанк компонента Test

Следовательно, компонент подгружается только тогда, когда нам это нужно. Это легко проверить, сравнив запросы, которые вызываются при загрузке страницы.

Рис. 13 - Запросы после использования динамического импорта
Рис. 13 - Запросы после использования динамического импорта

Нетрудно заметить, что у нас нет запроса на чанк с зависимостями компонента Test, как и самого компонента. Значит мы смогли уменьшить изначальный вес кода страницы.

В ходе тестирования различных способов импорта было выяснено, что свойство ssr: false не оказывало влияния на чанки. Также стоит заметить, что при импорте компонента в серверный компонент, этот компонент все равно попадал в бандл страницы.

Нужно обратить внимание, что при использовании динамического импорта, мы подключаем модуль dynamic из next, который тоже имеет свой вес и который также попадает в бандл страницы. Поэтому не стоит использовать динамический импорт повсеместно, особенно если импортируемый компонент весит мало. В таком случае мы можем наоборот ухудшить ситуацию, увеличив размер страницы.

В заключении о динамическом импорте стоит сказать, что dynamic лучше использовать при импорте клиентского компонента в клиентский. А также обращать внимание на размер компонента, загрузку которого хотите отложить.

Общее заключение о пакете

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

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

Благодарю за внимание!

Комментарии (0)