Найти в Дзене

Разделение кода (на чанки) в Webpack

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

Для начала, создадим папку src и добавим туда три файла, index.js и два других (предположим foo.js и baz.js), динамически импортируемых в первый. Затем добавим минимальный файл конфигурации:

// webpack.config.js
module.exports = {
entry: './src/index.js',
output: {
// про contenthash как нибудь в другой раз
filename: '[name].[contenthash].js',
},
};

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

Запускаем сборку командой webpack --mode=production и видим папку dist в которой появилось три файла: main.js, foo.js и baz.js. Из чего понимаем, что некоторая настройка разделения кода в Webpack по умолчанию уже есть (на самом деле их там много).

Также видим что файлы минифицированны, так как мы указали --mode=production. Давайте, на время изучения, выключим эту оптимизацию что-бы лучше понять во что Webpack собирает наш код.

module.exports = {
/* ... */
optimization: {
minimize: false,
},
};

Собираем проект ещё раз (теперь без минификации) и видим что помимо нашего кода в файлах (в папке dist) содержится дополнительный сервисный код. В динамически загружаемых файлах он выглядит примерно так:

(self["..."] = self["..."] || [])
.push([[/* chunk id */], {
[/* module id */]: (() => {
/* код модуля */
})
}]);

А вот в main.js его побольше, но оно и понятно, всё же это основной файл. Нас же интересует карта, в которой описано какой чанк (по chunk id) в каком файле лежит. Выглядит это примерно вот так:

return "" +
{ "386": "baz", "957": "foo" }[chunkId] + "." +
{ "386": "22...33", "957": "cf...d0" }[chunkId] +
".js";

Из всего этого понимаем, что каждому файлу присваивает некоторый chunk id. И вот тут давайте остановимся поподробнее. Если почитать документацию, то окажется что можно настраивать то, как именно это id будет формироваться. Но почему это важно? Дело в том, что код проекта может доставляться на сервер по несколько раз в сутки, но при этом меняться должно только то что мы действительно поменяли, что бы браузер пользователя не скачивал тот же самый код заново. Резонный вопрос — а разве может поменяться то, что мы не меняли? Да, может.

Если у вас всё ещё Webpack 4, то по умолчанию эти id — обычные порядковые номера. В итоге, может получиться так что вы поменяли одну строку исходного кода, а по факту изменилось половина файлов (в папке dist), потому что изменились id. Однако, начиная с 5 версии это поведение изменили, и теперь эти id не меняются между сборками.

Есть ещё один момент, почему файлы могут измениться.

Давайте вернёмся к нашему примеру и изменим исходный код в файле foo.js. Собираем проект, и видим что поменялся не только foo.js, но и main.js. Всё дело в той самой карте, т.к. обновился foo.js, соответственно обновился и его contenthash, а он записывается в main.js. И этот момент тоже можно настроить:

optimization: {
/* ... */
runtimeChunk: true,
},

Благодаря чему весь сервисный код, в том числе и карта, будут собираться в отдельный небольшой файл (чанк). Соответственно, теперь при изменении какого-то файла в исходном коде, в сборке поменяется только файл, где этот код лежит и runtime чанк.

Выводы:

  • Включите runtimeChunk.
  • Если вы поменяли значение optimization.chunkIds или moduleIds — проверьте чтобы значение соответствовало deterministic.
  • Если вы всё ещё собираете проект с помощью Webpack 4, то пора переходить на 5.
  • Если перейти на 5 ну ни как не получается — добавьте HashedModuleIdsPlugin.
  • Если у вас Webpack 5, проверьте что вы не используете HashedModuleIdsPlugin (если конечно не было веской причины его добавить).

Больше информации в Telegram канале https://t.me/around_dev.