Понимание того, как работает динамический импорт, из простого демонстрационного приложения

Разделение кода и динамический импорт - одна из привлекательных особенностей таких инструментов связывания, как Webpack, Parcel и Bazel.

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

Но задумывались ли вы когда-нибудь о том, как будут выглядеть ваши фрагменты после реальной сборки? Что произойдет, если приложение будет использовать динамический импорт из динамически импортируемых фрагментов? Будет ли мой код разделен на фрагмент, когда он динамически импортируется, но не от других? Мне также были интересны эти сценарии, поэтому я решил создать простое приложение и протестировать их.

Схема приложения

Во-первых, мы создаем несколько маршрутов с использованием персонажей из Resident Evil - одной из самых известных франшиз видеоигр. У каждого персонажа есть собственный слот для инвентаря, и два оружия предварительно загружены. Все используют один и тот же нож, но снаряды разные.

Сборка демонстрационного приложения

Для создания этой демонстрации мы используем Webpack v4, React и React-loadable для загрузки компонента с динамическим импортом.

Исходный код доступен на Github.

Тест №1: Статический импорт с разделением поставщиков

Наш первый тест начинается со статического (обычного) импорта для загрузки символов из маршрутизатора. Кроме того, мы делаем базовое разделение поставщиков из Webpack.

Сейчас мы создаем приложение с помощью плагина Webpack Bundle Analyzer, чтобы понять, как коды разбиваются на части.

Webpack создал два фрагмента файлов javaScript. Один из них - vendors.js, содержащий все ресурсы из папки node_modules. С другой стороны, у нас есть main.js (область синего цвета), который является кодами нашего приложения.

Тест № 2: динамический импорт символов

Результат предыдущего теста кажется удовлетворительным, однако он загрузит все символьные данные, даже если пользователь просмотрел один символ. Это может повлиять на время загрузки и многое другое по мере роста нашего приложения.

Теперь давайте загрузим эти символы с помощью динамического импорта и посмотрим, что мы можем от этого получить.

Как упоминалось ранее, в этой демонстрации мы собираемся использовать React-loadable для динамического импорта компонента реакции. Однако вы получите тот же результат с React.lazy или с другими методами, поддерживающими динамический импорт.

И вот результат.

Похоже, мы успешно разделили персонажей на отдельные куски!

Хм ... но подождите, что-то не так. Давайте подробнее рассмотрим файлы.

Мы видим, что Knife привязан к каждому персонажу, а также к файлу с именем Inventory Window, который он работает как контейнер для предметов. Это происходит потому, что каждый персонаж импортирует эти модули. В результате код дублируется, и приложение снова загружает их, даже если эти конкретные модули уже доступны из других ресурсов.

Тест № 3: Создание разделяемого чанка

Из последнего теста мы смогли разделить персонажа с помощью динамического импорта. Тем не менее, есть несколько дублированных модулей, размещенных на каждом персонаже, и мы хотели бы сделать их многоразовыми. К счастью, это можно очень легко сделать из Webpack с помощью SplitChunksPlugin.

Мы собираемся добавить новую кеш-группу default в опции splitChunk. minChunks установлен как 2, и это займет модуль, который используется более одного раза для всех символов. Вариантов, которые мы здесь используем, очень минимально, но этого будет достаточно для того, чего мы пытаемся достичь.

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

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

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

Тест №4: динамический импорт оружия

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

Теперь один из наших персонажей Леон хочет использовать Магнум для стрельбы по зомби.

Хорошо, я думаю, это не проблема, поскольку у нас уже есть разделяемый кусок. Давайте добавим Магнум в его инвентарь.

Как и ожидалось, код Magnum теперь стал частью default. Кроме того, Клэр не держит никакого оружия, потому что все оружие, имеющееся в ее инвентаре, уже спрятано в блоке default. Кажется, наш код в значительной степени оптимизирован, не так ли?

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

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

Хм ... что здесь произошло? Наш комплект ничуть не изменился. Это потому, что мы динамически импортируем из модуля, который также динамически импортируется?

О, мы забыли, что у Клэр тоже есть Магнум. Давайте быстро обновим ее код, чтобы использовать динамический импорт.

Наконец, код Magnum отделился от блока по умолчанию. Это объясняет, что модуль следует импортировать динамически из всего кода, иначе сборщик не сможет правильно разделить код.

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

Судя по демонстрационному видео, XHR происходит в такой последовательности:

1. Main
- vendor
- main
2. Leon
- default
- Leon
- Handgun
- Magnum
3. Claire
- default (ignored, using cached module fetched through Leon)
- Claire
- Magnum (ignored, using cached module fetched through Leon)
4. Chris
- default (ignored, using cached module fetched through Leon)
- Chris
- Shotgun

Выводы

На основе последних 4 тестов мы протестировали и научились разделять код из разных сценариев. Разделение кода дает много преимуществ по сравнению с одним файлом пакета из этого примера.

Однако приложение, использующее динамический импорт, должно быть тщательно спроектировано и сбалансировано в зависимости от того, что ему действительно нужно. Хотя динамический импорт может сократить время загрузки страницы, частая асинхронная загрузка может повлиять на взаимодействие с пользователем. Чтобы обойти эту проблему, подумайте об использовании предварительной выборки / предварительной загрузки и используйте совместно используемые блоки для хранения кода, глобально используемого в приложении.

Спасибо, что уделили время, чтобы прочитать эту статью. Удачного кодирования!

Ознакомьтесь с кодом: Github, Demo