Правильное долгосрочное кэширование с помощью Webpack - это проблема, на которую так и не было дан окончательного ответа.

Однако есть открытая проблема на Github:

  • у него 162 комментария
  • скоро исполнится 2 дня рождения
  • много предложений, которые часто усугубляют ситуацию
  • и, вероятно, довольно много хитов Google.

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

tl;dr;

  • Используйте NamedModuleIds
  • Используйте NamedChunkIds
  • Посыпьте немного магии
  • а потом еще немного

Но обо всем по порядку. Что мешает неоптимизированной сборке Webpack по умолчанию быть долговременной кешируемой? Это длинная история. Возьмем.

Мы создадим небольшое приложение с помощью Webpack, дадим ему возможность расти со временем и, таким образом, столкнуться со всевозможными проблемами. И, как и в любом хорошем квесте, мы убьем их всех или что-то в этом роде ...

Основы

Это наша первоначальная неоптимизированная конфигурация Webpack:

и это то, что мы находим в foo.js

Построение этого дает нам вывод Webpack в следующих строках:

Все идет нормально.

Чанки поставщика

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

Мы снова создаем приложение и получаем примерно такой результат:

Не засыпай. Медведь с весельем скоро начнется.

Правильный хеш

Возможно, вы заметили, здесь мы столкнулись с нашей первой проблемой. Блоки main и vendor одинаковы. Любое изменение в основном файле теперь также приведет к недействительности нашего блока поставщика.

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

Запустив нашу сборку снова, мы теперь видим два разных хэша.

Очень хорошо.

У вас проблемы во время выполнения

Изменение чего-либо в основном блоке теперь должно оставлять vendor блок нетронутым. Давайте добавим к нему новую фиктивную строку:

Однако повторный запуск сборки показывает нам, что все снова потеряно:

Но почему? Проблема здесь в деталях:

Поскольку Webpack ведет себя, извлекая preact из main-фрагмента, в него также извлекается среда выполнения Webpack. среда выполнения - это часть Webpack, которая разрешает модули во время выполнения и обрабатывает асинхронную загрузку и многое другое. Заглянув в него, мы видим ссылку на наш main чанк:

К счастью, мы можем это исправить. Если вы добавите CommonsChunkPlugin с именем несуществующего фрагмента в качестве имени точки входа, Webpack извлечет среду выполнения, создаст фрагмент с этим именем и поместит туда среду выполнения. Звучит волшебно? Ну да, я думаю?

Если повернуть волшебную палочку и снова запустить сборку, получаем следующее:

Изменение чего-либо в блоке main теперь изменит только runtime и main блок, блок vendor останется нетронутый.

Добавление дополнительных зависимостей

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

Мы снова запускаем нашу сборку, ожидая, что будет только наша сборка, мы ожидаем, что изменятся только блоки main и vendor. Но как вы уже догадались, это не то, что происходит:

Несмотря на то, что в чанке vendor ничего не изменилось, его хэш снова изменился. Причина опять же в детали. Каждому фрагменту присваивается числовой идентификатор фрагмента. Они даны по порядку, и, поскольку порядок может меняться с каждым новым добавленным импортом, идентификаторы блоков могут изменяться вместе с ними.

Назовите свой кусок

Введите NamedChunksPlugin. Это недавнее дополнение к исходному тексту Webpack (2.4), которое позволяет иметь имена, а не числа для наших чанков:

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

Мы можем снова запустить нашу сборку с добавлением bar.js и без него и увидеть, что хэш фрагмента vendor останется прежним. Ну, за исключением того, что это не так. Глядя на два блока поставщиков, мы видим что-то вроде этого:

Назовите свои модули - извините, здесь нет каламбура :(

По какой-то причине Webpack добавляет эти идентификаторы всех существующих модулей в наш блок вендора. Давайте не будем слишком заботиться о том, почему. К счастью, есть простое решение. ВведитеNamedModulesPlugin.

Он делает то же самое, что и эквивалент named chunk. Вместо использования числовых идентификаторов он использует уникальный путь для сопоставления нашего запроса с модулем.

Благодаря этому изменению хэш vendor теперь всегда остается прежним:

Это оно? Пожалуйста, скажи мне, что все, это чертовски скучно, и все постоянно меняется.

Ну, угадайте, что? ха-ха. Неа.

Люби меня асинхронно

По мере роста наше приложение становится тяжелым. Чтобы предотвратить загрузку всего кода сразу, мы разбиваем его, используя некоторые асинхронные точки разделения. Сначала мы добавляем один:

и вскоре после этого еще одна асинхронная зависимость:

WTF WEBPACK. Почему мой асинхронный блок внезапно получил другое название? И почему у них снова нумерованные идентификаторы? Я думал, что аннулирование кеша было трудным, но вы просто все время аннулируете все :(.

Оказывается, NamedChunkPlugin обрабатывает только куски, у которых есть имя. Это не относится к нашим асинхронным фрагментам. Глупые ленивые разработчики OSS, пфф.

Давай исправим это.

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

Запустив нашу сборку снова, теперь мы можем добавить столько асинхронных фрагментов, сколько захотим. Ранее добавленные сохранят свое имя и хеш:

Дополнительное примечание: вы можете изменить эстетику этих асинхронных имен чанков на что угодно. Мне лень.

Okeeeeh это что теперь?

А как насчет НЕТ

Наследие до того, как стало хипстером - внешние зависимости

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

Что возможно могло пойти не так? Ну все.

эээ да, спасибо jQuery за то, что убили мой кусок продавца? Что ты ... ЧТО?

Оказывается, так же, как и для чанков, NamedModulesPlugin также работает только для обычных модулей. При этом внешний модуль украл id 0 из модуля multi preact. Давайте исправим это раз и навсегда.

Дайте каждому имя

Помимо обычных модулей, Webpack использует множество других модулей. Однако не все они подпадают под действие NormalModulesPlugin. Поэтому нам нужно свернуть свои собственные (может, мне стоит сделать это пакетом? *):

Мы добавляем это в нашу конфигурацию Webpack:

Это работает почти так же, как и сам NormalModulesPlugin, за исключением того, что он использует метод module#identifier для всех модулей, у которых на данный момент нет id.

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

Как видите, id multi preact изменился с [0] на [multi preact].

Итак, это все? Нет. Последний шаг!

Добавление дополнительных точек входа

Приложение продолжает расти, и у нас есть вторая точка входа. Итак, мы выделяем это в новую точку входа и добавляем в нашу конфигурацию Webpack:

Его содержимое выглядит примерно так:

Запуск сборки заставляет нас (╯ ° □ °) ╯︵ ┻━┻.

Теперь сборка дает следующее:

Все изменилось. Производитель, основной файл, все, но почему?

Оказывается, наш блок vendor принимает не только то, что мы указываем, но и все, что мы используем в обеих наших точках входа. Хотя иногда может потребоваться совместно использовать наши общие используемые собственные свернутые модули, они не должны попадать в блок vendor. Может, как и сейчас, мы этого совсем не хотим. Добавление minChunks: Infinity в наш блок vendor commons сообщает Webpack, что нам действительно нужно только то, что мы указали в записи:

И если мы снова запустим нашу сборку:

У нас есть именно то, что мы хотим. Наша окончательная конфигурация Webpack выглядит так:

Вот и все. По крайней мере, насколько я мог думать о крайних случаях. Я очень надеюсь, что это поможет. Если вы все же обнаружите причуды, не стесняйтесь комментировать, и я постараюсь найти для них решение.

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

Последнее важное замечание. Теперь вам, возможно, придется позаботиться об аннулировании кеша. Поскольку Webpack может позволить подключаемым модулям изменять активы после вычисления chunkhash, и эти подключаемые модули могут неправильно обновлять chunkhash, это может привести к тому, что актив сохранит свой хэш, даже если он был фактически изменен. Лично я этого не делал, но всегда хорошо быть осторожным.

На этом пока все, веселитесь! :)