Создайте предсказуемую схему постоянного кэширования на основе webpack4[.3+]
本文针对的是`immutable content+long max-age`类型的web缓存。
校验缓存及service worker的处理方案后续有时间再更新。
Излишне говорить о преимуществах веб-кэширования, поскольку веб-пакет — это ведро рек и озер, как это сделать Предсказуемое долгосрочное кэширование с помощью Webpack вызвало головную боль у инженеров по настройке.
До Webpack 4.3 было довольно много статей о том, как с этим бороться (см. ссылку) Здесь я хочу провести дополнительное исследование на месте.
вопрос
Проблема возникает, когда бизнес разработан и готов к запуску.🤡:
- Как сделать так, чтобы ресурсы с разным контентом имели уникальные идентификаторы (хеш-значения)?
- Если бизнес-код будет изменен и переупакован, будут ли изменены значения идентификаторов всех ресурсов?
- Если я хочу стабилизировать значение хеш-функции, как я могу гарантировать, что измененные имена файлов будут сведены к минимуму?
- Повлияют ли изменения в ресурсах, таких как css/wasm, на хеш-значение чанков?
- Если изменится порядок ссылок в бизнесе, изменится ли хеш-значение чанка? Должно ли это быть?
- Хорошо ли поддерживаются файлы для динамического импорта?
- Повлияет ли добавление или удаление нескольких файлов записей на существующее значение хеш-функции?
Не отказывайтесь от лечения 🍷 Некоторые версии этой статьи на момент тестирования:
Node.js: v10.8.0
Webpack: v4.17.1
TL;DR
- ВЕБ-ПАК@4.3
contenthash
Очень круто и удобно 🌈 - использовать
HashedModuleIdsPlugin
Стабильный идентификатор модуля. Плагин будет генерировать четырехзначный хэш в качестве идентификатора модуля на основе относительного пути модуля, что рекомендуется для производственных сред 🎁 - использовать
HashedModuleIdsPlugin
Стабильный chunkId. - webpack@5 будет иметь готовый постоянный кеш (это официальная идея 😅, webapck4 известен как нулевая конфигурация, и еще рождается большое количество старших конфигураторов)
Ресурсы, требующие долгосрочного кэширования
-
Медиа-ресурсы, такие как изображения и шрифты медиа-ресурсы могут быть использованы
file-loader
Генерировать хеш-значение на основе содержимого ресурса, сотрудничать сurl-loader
При необходимости его можно встроить в формат base64, здесь особо нечего сказать. -
css Если ресурс css специально не обрабатывается, он будет напрямую занесен в файл js, в продакшене мы обычно используем
mini-css-extract-plugin
Извлечь в отдельный файл или в строку. -
js Обработка js-файлов доставляет гораздо больше хлопот. Как единственный входной ресурс, js управляет другими модулями и вводит бесконечные вопросы. Это наша следующая задача.
тип хэша webpack4
тип хеша | описывать |
---|---|
hash | The hash of the module identifier |
chunkhash | The hash of the chunk |
contenthash (веб-пакет > 4.3.0) | The hash of the content(only) |
contenthash должен быть более важной функцией,Разработчики ядра веб-пакета считают, что это может полностью заменить чанкхэш.(Видетьissue#2096), возможно, измените хэш содержимого в webpack5 на[hash]
.
Так в чем же между ними разница?
Проще говоря, когда чанк содержит css и wasm, при изменении css хеш-код также изменится, что приведет к изменению хеш-значения чанка; если используется contenthash,Изменения CSS не повлияют на хеш-значение чанков., потому что он генерируется на основе js-содержимого чанка.
Достаточно знать, что видов очень много, начнем с самого простого примера 🚴♂️.
каштаны
дальше будетproduction mode
Следующий тест (если вы не знаете новый режим режима webpack4, перейдите на страницудокументация по режиму webpackБар).
Стратегия распаковки будет рассмотрена за один раз, и позже будет время подробно поговорить о проблемах, связанных с распаковкой ~
1. Простой хэш
Самый простой файл конфигурации выглядит следующим образом,
// webapck.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
mode:'production',
entry: {
index: './src/index.js',
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].[hash].js',
},
};
входной файлindex.js
Это просто:
// index.js
console.log('hello webapck🐸')
Результат упаковки:
В этом примере используетсяname + hash
Именование файлов, потому что хэш основан наmodule identifier
Генерируется, что означает, что пока в бизнесе есть небольшие изменения, значение хеш-функции будет меняться, см. следующий пример.
2. Добавьте поставщика
Добавим немного сложности.
@серый вПредварительное исследование хэш-стабильности WebpackИнтересный пример показан на , давайте попробуем.
Теперь добавим модуль a.js в файл записи:
// index.js
import './a';
console.log('hello webpack🐸');
Модуль a представляет метод идентификации в lodash:
// a.js
import {identity} from 'lodash';
identity();
Затем измените файл конфигурации веб-пакета, чтобы извлечь файлы поставщика и файл manifest. Еще одна вещь, которую нужно сказать здесь, runtimeChunk очень мал, и можно предвидеть, что серьезных изменений в размере не будет, поэтому вы можете рассмотреть возможность его встраивания в html.
// webapck.config.js
...
module.exports = {
...
// 使用splitChunks默认策略拆包,同时提取runtime
optimization: {
runtimeChunk: true,
splitChunks: {
chunks: 'all'
}
},
};
Результат упаковки:
[хэш] проблема
Я думаю, вы заметили, что после того, как приведенный выше образ упакован, все файлы имеют одинаковое значение хеш-функции. Что это значит?
Каждый раз, когда бизнес-итерация переходит в онлайн, клиенту необходимо снова получать статические ресурсы.Поскольку значение хеша каждый раз меняется, все предыдущие кеши становятся недействительными.😬.
Итак, если мы хотим сделать постоянное кэширование, мы точно не будем его использовать.[hash]
.
3. А как насчет чанкхеша?
До webpack 4.3 мы могли выбирать только chunkhash для идентификации модуля, но если эта штука не очень стабильна, то конфигураторы используют всевозможные черные технологии, чтобы сделать значение хэша максимально стабильным.
В чем разница между недавно выпущенным contenthash и chunkhash😳?
Взгляните на следующие примеры.
использовать чанкхэш
мы будем[hash]
заменить[chunkhash]
, посмотрите на результат упаковки:
индекс, поставщики и среда выполнения имеют разные хеш-значения,so far so good.
Давайте продолжим с серым примером, добавив модуль b.js в index.js, а модуль b имеет только одну строку кода:
// index.js
import './b'; // 增加了b.js
import './a';
console.log('hello webpack🐸');
// b.js
console.log('no can no bb');
Результат упаковки:
Хэш-значение индексного файла меняется как положено, но суть вендоров по-прежнему заключается в методе идентификации пакета lodash, и это изменение невыносимо.
Причина в том,По умолчанию webpack4 использует автоматически увеличивающийся идентификатор для идентификации модуля в соответствии с порядком разрешения., поэтому вставка b.js приводит к тому, что идентификатор поставщиков будет неверным числом Мы можем видеть, что, сравнив два файла поставщиков, единственная разница между двумя файлами заключается в следующем:
Это тоже упоминается в серой статье, решение очень простое, используйтеHashedModuleIdsPlugin
, который представляет собой встроенный плагин, который генерирует идентификатор модуля на основе пути к модулю, и проблема решена:
(Сначала меня волновало вычисление хеша и нейминг на основе пути к модулю, не будет ли этот метод отличаться из-за разных операционных систем, ведь я уже понес убытки, см.Несогласованный путь пути в Windows/Linux, к счастью webpack официально разобрался с этой проблемой, не стоит об этом беспокоиться)
// webpack.config.js
...
plugins:[
new webpack.HashedModuleIdsPlugin({
// 替换掉base64,减少一丢丢时间
hashDigest: 'hex'
}),
]
...
(настраиватьoptimization.moduleIds:'hash'
можно добиться того же эффекта, ноТребуется webapck@4.16.0 или выше)
Результат упаковки:
// 有b模块时:
index.a169ecea96a59afbb472.js 243 bytes 0 [emitted] index
vendors~index.6b77ad9a953ec4f883b0.js 69.5 KiB 1 [emitted] vendors~index
runtime~index.ec8eb4cb2ebdc83c76ed.js 1.42 KiB 2 [emitted] runtime~index
// 没有b模块时:
index.8296fb0301ada4a021b1.js 185 bytes 0 [emitted] index
vendors~index.6b77ad9a953ec4f883b0.js 69.5 KiB 1 [emitted] vendors~index
runtime~index.ec8eb4cb2ebdc83c76ed.js 1.42 KiB 2 [emitted] runtime~index
4. Добавьте модуль css
Добавьте c.css👇 в файл записи, содержимое c не важно:
// index.js
import './c.css';
import './b';
import './a';
...
настроить егоmini-css-extract-plugin
Извлеките этот модуль css:
// webpack.config.js
...
module: {
rules: [
{
test: /\.css$/,
include: [
path.resolve(__dirname, 'src')
],
use: [
{loader: MiniCssExtractPlugin.loader},
{loader: 'css-loader'}
]
}
]
},
plugins:[
new webpack.HashedModuleIdsPlugin(),
// 增加css抽取
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[name].[contenthash].css'
})
]
...
Затем упакуйте. Измените немного содержимого в c.css и снова запакуйте его.
В этих двух процессах упаковки мыИзменения были внесены только в файл c.css, что ожидается?Конечно, я надеюсь, что изменилось только значение хеш-функции css-файла., однако все не так, как ожидалось:
// 增加了c.css
Asset Size Chunks Chunk Names
index.90d7b62bebabc8f078cd.css 59 bytes 0 [emitted] index
index.e5d6f6e2219665941029.js 276 bytes 0 [emitted] index
vendors~index.6b77ad9a953ec4f883b0.js 69.5 KiB 1 [emitted] vendors~index
runtime~index.de3e5c92fb3035ae4940.js 1.42 KiB 2 [emitted] runtime~index
// 改动c.css中的代码后
Asset Size Chunks Chunk Names
index.22b9c488a93511dc43ba.css 94 bytes 0 [emitted] index
index.704b09118c28427d4e8f.js 276 bytes 0 [emitted] index
vendors~index.6b77ad9a953ec4f883b0.js 69.5 KiB 1 [emitted] vendors~index
runtime~index.de3e5c92fb3035ae4940.js 1.42 KiB 2 [emitted] runtime~index
Обратите внимание на хеш-значение index.js 📌 После упаковки значение хеш-функции входного файла также изменилось, что очень хлопотно.
5. Contenthash лечит все?
contenthash не решает проблему самовозрастания moduleId
В чем разница между поведением указанных выше файлов поставщиков с использованием contenthash и chunkhash? Можно ли решить проблему, связанную с изменением модуля?
Ответ - нет😅.
Ведь содержимое файла содержит измененные вещи, это все еще должно бытьHashedModuleIdsPlugin
плагин.
Сила хэша контента
Что contenthash может решить, так это проблему изменения хеш-значения js после изменения модуля css.
Измените файл конфигурации👇:
...
output: {
path: path.resolve(__dirname, './dist'),
// 改成contenthash
filename: '[name].[contenthash].js'
},
...
Просто посмотрите на сравнение:
// 增加了c.css
Asset Size Chunks Chunk Names
index.22b9c488a93511dc43ba.css 94 bytes 0 [emitted] index
index.41e5e160a222e08ed18d.js 276 bytes 0 [emitted] index
vendors~index.ec19a3033220507df6ac.js 69.5 KiB 1 [emitted] vendors~index
runtime~index.d25723c2af2e039a9728.js 1.42 KiB 2 [emitted] runtime~index
// 改动c.css中的代码后
Asset Size Chunks Chunk Names
index.a4afb491e06f1bb91750.css 60 bytes 0 [emitted] index
index.41e5e160a222e08ed18d.js 276 bytes 0 [emitted] index
vendors~index.ec19a3033220507df6ac.js 69.5 KiB 1 [emitted] vendors~index
runtime~index.d25723c2af2e039a9728.js 1.42 KiB 2 [emitted] runtime~index
Как видите, значение хеш-функции чанка index.js точно такое же до и после изменения💯.
6. Добавьте асинхронный модуль
Чтобы оптимизировать производительность первого экрана или когда бизнес станет более раздутым, мы неизбежно будем извлекать и загружать некоторые асинхронные модули, что очень удобно через динамический импорт.
Однако, как новый фрагмент, каково значение хеш-функции асинхронного модуля?
Давайте попробуем добавить асинхронный модуль.
// webpack.config.js
...
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].[contenthash].js',
// 增加chunkFilename
chunkFilename: '[name].[contenthash].js'
},
...
// async-module.js
export default {
content: 'async-module'
};
// index.js
import './c.css';
import './b';
import './a';
// 增加这个模块
import('./async-module').then(a => console.log(a));
console.log('hello webpack🐸');
Содержимое асинхронного модуля тоже неважно, главное, что значение хеша до и после добавления этого модуля сильно изменилось! Без асинхронного модуля:
Добавьте асинхронный модуль:
Добавьте второй асинхронный модуль:
Вышеупомянутый контраст просто возвращается к освобождению в одночасье. . . В дополнение к хеш-значению файла CSS онлайн произошли и другие изменения.
Причина в том, что хотя мы стабилизировали moduleId, мы ничего не можем сделать с chunkId.И асинхронные модули, потому что нет chunk.name, что приводит к использованию числового автоинкремента для именования.
К счастью, у нас еще естьNamedChunksPlugin
chunkId можно стабилизировать👇:
// webapck.config.js
...
plugin:{
new webpack.NamedChunksPlugin(
chunk => chunk.name || Array.from(chunk.modulesIterable, m => m.id).join("_")
),
...
}
...
Кроме этого, есть и другие способы стабилизации chunkId, но из-за более-менее недостатков я не буду их здесь повторять, а посмотрим на текущие результаты упаковки:
Видно, что асинхронный модуль имеет значение имени, а значение хэша поставщиков вернулось.
7. Добавьте второй файл записи
В процессе бизнес-итерации часто добавляются или удаляются какие-то страницы, так как же меняется значение хеш-функции в таком сценарии?
// webpack.config.js
...
entry: {
index: './src/index.js',
index2: './src/index2.js'
},
...
Мы добавляем файл записи index2, содержимое которого представляет собой предложениеconsole.log('i am index2~')
, чтобы увидеть результат упаковки:
Причина в том, что мы стабилизировали ChunkId, и каждый фрагмент больше не будет увеличиваться в соответствии с порядком разрешения.
В реальной производственной среде, когда вновь представленный фрагмент зависит от других общедоступных модулей, хэш-значение некоторых файлов все равно будет меняться, но это можно решить с помощью стратегии распаковки, поэтому я не буду здесь вдаваться в подробности.
Суммировать
На некоторых примерах эта статья обобщает принцип долгосрочного кэширования с помощью webpack4 и практику наступания на ямы, которые использовались в нашем реальном бизнесе.Для часто повторяющихся предприятий наблюдается значительное улучшение производительности.
По сравнению с предыдущей версией, долгосрочный кеш webpack4 значительно продвинулся вперед и имеет некоторые недостатки, но я верю, что они будут решены в webapck5 🙆♀️~