webpack стабилизирует moduleid и chunkid для расчесывания постоянного кеша

внешний интерфейс JavaScript CSS Webpack

предисловие

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

текст

1. Два способа посчитать хэш webpack

Если вы хотите использовать постоянное кэширование, вы должны полагаться наwebpackдваhash:hashа такжеchunkhash.

Давайте посмотрим на конкретное значение и разницу между этими двумя значениями:

hash: webpackПри каждой сборкеcompilationобъект, этоhashстоимость основана наcompilationЗначение рассчитывается из всего, что внутри.

chunkhash: Это значение основано на каждомchunkРасчетное значение содержимого.

Так что, просто основываясь на приведенном выше описании,chunkhashОн наиболее эффективен для постоянного кэширования.

2, тест на хэш и чанкхеш

entry входной файл входная зависимость
pageA a.js a.less->a.css, common.js->common.css
pageB b.js b.less->b.css, common.js->common.css
  • Использовать вычисление хэша
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
  entry: {
    pageA: './src/a.js',
    pageB: './src/b.js'
  },
  output: {
    filename: '[name]-[hash].js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: ['css-loader?minimize']
        })
      }
    ]
  },
  plugins: [new ExtractTextPlugin('[name]-[hash].css')]
}

построить результат

Hash: 80c922b349f516e79fb5
Version: webpack 3.8.1
Time: 1014ms
                         Asset      Size  Chunks             Chunk Names
pageB-80c922b349f516e79fb5.js   2.86 kB       0  [emitted]  pageB
pageA-80c922b349f516e79fb5.js   2.84 kB       1  [emitted]  pageA
pageA-80c922b349f516e79fb5.css  21 bytes       1  [emitted]  pageA
pageB-80c922b349f516e79fb5.css  21 bytes       0  [emitted]  pageB

В заключение

Все файлы можно найтиhashЭто все равно, но вы строите его несколько разhashвсе разные. Причина в том, что мы используемExtractTextPlugin,ExtractTextPluginсам по себе включает асинхронный процесс извлечения, поэтому при созданииassetsСуществует неопределенность (последовательность) ресурсов, иupdateHashОн чувствителен к этому, поэтому возникает ситуация с изменением хэша, о которой говорилось выше. Кроме того, всеassetsРесурсыhashЗначение остается постоянным, что не имеет особого смысла для постоянного кэширования всех ресурсов.

  • Вычислить с помощью chunkhash
    ```js
    const path = require('path')
    const ExtractTextPlugin = require('extract-text-webpack-plugin')
    module.exports = {
    entry: {
    pageA: './src/a.js',
    pageB: './src/b.js'
    },
    output: {
    filename: '[name]-[chunkhash].js',
    path: path.resolve(__dirname, 'dist')
    },
    module: {
    rules: [
    {
      test: /\.css$/,
      use: ExtractTextPlugin.extract({
        fallback: 'style-loader',
        use: ['css-loader?minimize']
      })
    }
    ]
    },
    plugins: [new ExtractTextPlugin('[name]-[chunkhash].css')]
    }
**构建结果**
```js
Hash: 810904f973cc0cf41992
Version: webpack 3.8.1
Time: 1038ms
                         Asset      Size  Chunks             Chunk Names
pageB-e9ed5150262ba39827d4.js   2.86 kB       0  [emitted]  pageB
pageA-3a2e5ef3d4506fce8d93.js   2.84 kB       1  [emitted]  pageA
pageA-3a2e5ef3d4506fce8d93.css  21 bytes       1  [emitted]  pageA
pageB-e9ed5150262ba39827d4.css  21 bytes       0  [emitted]  pageB

В заключение

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

3, чтобы изучить взаимосвязь между входом и хэшем файла CSS

В приведенных выше результатах построения мы обнаружили, что хеш-значение CSS совпадает с хеш-значением входного файла, Здесь мы склонны к сомнениям, есть ли связь между этими двумя файлами? Не сомневайтесь и измените содержимое файла b.css, чтобы получить результат сборки:

Hash: 3d95035f096f3ca08761
Version: webpack 3.8.1
Time: 1028ms
                         Asset      Size  Chunks             Chunk Names
pageB-e9ed5150262ba39827d4.js   2.86 kB       0  [emitted]  pageB
pageA-3a2e5ef3d4506fce8d93.js   2.84 kB       1  [emitted]  pageA
pageA-3a2e5ef3d4506fce8d93.css  21 bytes       1  [emitted]  pageA
pageB-e9ed5150262ba39827d4.css  41 bytes       0  [emitted]  pageB

Нани? ? ? Измените содержимое файла css, почему хэш файла css не изменился? Ненаучно, хэш файла входа не изменился. Тщательно обдумав это, webpack рассматривает все как часть файла js. В процессе построения используется ExtractTextPlugin для извлечения стиля из фрагмента записи, но сам фрагмент входа в это время не изменился, а была извлечена часть css. Чанкунхэш рассчитывается на основе чанка, поэтому не изменять его должно быть нормальным. Но это не соответствует требованиям к постоянному кешу, который мы хотим сделать, потому что, если вы его измените, вы должны изменить и хэш.

К счастью, плагин ExtractTextPlugin предоставляет намcontenthashизменить:

  plugins: [new ExtractTextPlugin('[name]-[contenthash].css')]

Измените результаты двух сборок до и после b.css:

Hash: 3d95035f096f3ca08761
Version: webpack 3.8.1
Time: 1091ms
                     Asset      Size  Chunks             Chunk Names
pageB-e9ed5150262ba39827d4.js   2.86 kB       0  [emitted]  pageB
pageA-3a2e5ef3d4506fce8d93.js   2.84 kB       1  [emitted]  pageA
pageA-9783744431577cdcfea658734b7db20f.css  21 bytes       1  [emitted]  pageA
pageB-2d03aa12ae45c64dedd7f66bb88dd3db.css  41 bytes       0  [emitted]  pageB
Hash: 7a96bcf1ef668a49c9d8
Version: webpack 3.8.1
Time: 1193ms
                     Asset      Size  Chunks             Chunk Names
pageB-e9ed5150262ba39827d4.js   2.86 kB       0  [emitted]  pageB
pageA-3a2e5ef3d4506fce8d93.js   2.84 kB       1  [emitted]  pageA
pageA-9783744431577cdcfea658734b7db20f.css  21 bytes       1  [emitted]  pageA
pageB-7e05e00e24f795b674df5701f6a38bd9.css  42 bytes       0  [emitted]  pageB

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

4. Неуправляемость и исправление id модуля

После вышеуказанного теста мы считаем само собой разумеющимся, что я завершил стабилизацию хеширования постоянного кеша. Затем мы случайно удалили файл a.less в a.js, а затем дважды собрали его:

Hash: 88ab71080c53db9d9f70
Version: webpack 3.8.1
Time: 1279ms
                                     Asset       Size  Chunks             Chunk Names
             pageB-a2d1e1d73336f17e2dc4.js    3.82 kB       0  [emitted]  pageB
             pageA-96c9f5afea30e7e09628.js     3.8 kB       1  [emitted]  pageA
pageA-d7ac82de795ddf50c9df43291d77b4c8.css   92 bytes       1  [emitted]  pageA
pageB-56185455ea60f01155a65497e9bf6c85.css  108 bytes       0  [emitted]  pageB
Hash: 172153ea2b39c2046a92
Version: webpack 3.8.1
Time: 1260ms
                                     Asset       Size  Chunks             Chunk Names
             pageB-884da67fe2322246ab28.js    3.81 kB       0  [emitted]  pageB
             pageA-4c0dfb634722c556ffa0.js    3.68 kB       1  [emitted]  pageA
pageA-35be2c21107ce4016c324daaa1dd5e28.css   49 bytes       1  [emitted]  pageA
pageB-56185455ea60f01155a65497e9bf6c85.css  108 bytes       0  [emitted]  pageB

Произошли странные вещи, я удалил файл a.less и обнаружил, что хэш файла входа pageB изменился. Я могу понять, изменился ли только хэш файлов, связанных со страницей А. но? ? ? ? Почему все изменилось? ? ? Нет, я должен посмотреть, почему он изменился.

image
image

В приведенном выше diff обнаружено, что общий идентификатор изменился после того, как мы удалили a.less. Тогда мы можем предположить, что идентификатор этого места представляет конкретный модуль, на который ссылаются.

Затем мы смотрим на информацию о двух строительных блоках до и после:

[3] ./src/a.js 284 bytes {1} [built]
[4] ./src/a.less 41 bytes {1} [built]
[5] ./src/b.js 284 bytes {0} [built]
[6] ./src/b.less 41 bytes {0} [built]
[3] ./src/a.js 264 bytes {1} [built]
[4] ./src/b.js 284 bytes {0} [built]
[5] ./src/b.less 41 bytes {0} [built]

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

5. Изменения в идентификаторе модуля

Исключить несвязанные идентификаторы модулей или содержимое

У людей, которые будут использовать webpack, вероятно, есть одна особенность:Code Splitting, что, по сути, представляет собой процесс разделения и повторного объединения фрагментов. Конкретно как это сделать?

The answer is CommonsChunkPlugin, добавьте в плагин:

plugins: [
    new ExtractTextPlugin('[name]-[contenthash].css'),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime'
    })
]

Далее давайте посмотрим на изменения до и после удаления a.less на странице A:

Hash: 697b36118920d991364a
Version: webpack 3.8.1
Time: 1488ms
                                       Asset       Size  Chunks             Chunk Names
               pageB-9b2eb6768499c911a728.js  491 bytes       0  [emitted]  pageB
               pageA-c342383ca09604e8e7b8.js  495 bytes       1  [emitted]  pageA
             runtime-b6ec3c0d350aef6cbf3e.js     6.8 kB       2  [emitted]  runtime
  pageA-b812cf5b72744af29181f642fe4dbf38.css   43 bytes       1  [emitted]  pageA
  pageB-af8f1e92fd031bd1d1d8db5390b5d0d5.css   59 bytes       0  [emitted]  pageB
runtime-35be2c21107ce4016c324daaa1dd5e28.css   49 bytes       2  [emitted]  runtime
Hash: 7ddaf109d5aa67c43ce2
Version: webpack 3.8.1
Time: 1793ms
                                       Asset       Size  Chunks             Chunk Names
               pageB-613cc5a6a90adfb635f4.js  491 bytes       0  [emitted]  pageB
               pageA-0b72f85fda69a9442076.js  375 bytes       1  [emitted]  pageA
             runtime-a41b8b8bfe7ec70fd058.js    6.79 kB       2  [emitted]  runtime
  pageB-af8f1e92fd031bd1d1d8db5390b5d0d5.css   59 bytes       0  [emitted]  pageB
runtime-35be2c21107ce4016c324daaa1dd5e28.css   49 bytes       2  [emitted]  runtime

Затем посмотрите на сравнение pageB в двух сборках:

image
image

После сравнения мы обнаружили, что на страницу B включен только собственный контент. Так что использование CommonsChunkPlugin оправдало наши ожидания. Извлеченный код является кодом среды выполнения webpack. Код среды выполнения также хранит информацию о модулях и чанках в webpack. Также мы обнаружили, что размер файла страницы A и страницы B также изменился. Причина этого изменения в том, что CommonsChunkPlugin будет извлекать модули, содержащиеся во входном чанке, в обычный чанк, который мы назвали runtime по умолчанию.

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

plugins: [
    new ExtractTextPlugin('[name]-[contenthash].css'),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      minChunks: Infinity
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'runtime'
})

Бревна построены дважды до и после второй противоположной стороны:

Hash: a703a57c828ec32b24e1
Version: webpack 3.8.1
Time: 1493ms
                                     Asset       Size  Chunks                    Chunk Names
            vendor-f11f58b8150930590a10.js     541 kB       0  [emitted]  [big]  vendor
             pageB-7d065cd319176f44c605.js  938 bytes       1  [emitted]         pageB
             pageA-2b7e3707314e7ec4d770.js  910 bytes       2  [emitted]         pageA
           runtime-e68dec8bcad8a5870f0c.js    5.88 kB       3  [emitted]         runtime
pageA-d7ac82de795ddf50c9df43291d77b4c8.css   92 bytes       2  [emitted]         pageA
pageB-56185455ea60f01155a65497e9bf6c85.css  108 bytes       1  [emitted]         pageB
Hash: 26fc9ad18554b28cd8e1
Version: webpack 3.8.1
Time: 1806ms
                                     Asset       Size  Chunks                    Chunk Names
            vendor-d9bad56677b04b803651.js     541 kB       0  [emitted]  [big]  vendor
             pageB-a55dadfbf25a45856d6a.js  929 bytes       1  [emitted]         pageB
             pageA-7cbd77a502262ddcdd19.js  790 bytes       2  [emitted]         pageA
           runtime-fa8eba6e81ed41f50d6f.js    5.88 kB       3  [emitted]         runtime
pageA-35be2c21107ce4016c324daaa1dd5e28.css   49 bytes       2  [emitted]         pageA
pageB-56185455ea60f01155a65497e9bf6c85.css  108 bytes       1  [emitted]         pageB

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

Стабилизируйте идентификатор модуля и оставьте идентификатор модуля неизменным, насколько это возможно.

Идентификатор модуля — это уникальный идентификатор модуля, и этот идентификатор появится в коде после создания соответствующего фрагмента записи. Взгляните на пример кода страницы B после сборки:

__webpack_require__(7)
const sum = __webpack_require__(0)
const _ = __webpack_require__(3)

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

Таким образом, приведенное выше утверждение можно перевести в два шага к действию.

  • Найдите способ заменить идентификатор модуля
  • Найдите возможность заменить id модуля

6. Операции, связанные со стабильным идентификатором модуля

Найдите способ заменить идентификатор модуля
В нашей повседневной разработке мы часто обращаемся к модулям, на которые все ссылаются по адресам. Отсюда мы можем черпать вдохновение, можем ли мы заменить все идентификаторы модулей на путь? Еще одна вещь, которую мы узнали, это то, что мы определенно можем получить путь к ресурсу на этапе модуля разрешения веб-пакета. В начале мы беспокоились о разнице путей платформы. К счастью, исходный код webpack находится вContextModule#74а такжеContextModule#35В веб-пакете путь модуля исправлен по-разному. То есть мы можем безопасно получить путь к модулю через libIdent модуля.

Есть три хука, связанные с идентификатором модуля во время выполнения всего веб-пакета:

before-module-ids -> optimize-module-ids -> after-optimize-module-ids

Так что мы простоbefore-module-idsПросто внесите в него изменения.

Напишите плагин:

'use strict'

class moduleIDsByFilePath {
    constructor(options) {}

    apply(compiler) {
        compiler.plugin('compilation', compilation => {
            compilation.plugin("before-module-ids", (modules) => {
                modules.forEach((module) => {
                    if(module.id === null && module.libIdent) {
                        module.id = module.libIdent({
                            context: this.options.context || compiler.options.context
                        })
                    }
                })
            })
        })
    }
}

module.exports = moduleIDsByFilePath

Вышеупомянутое фактически было извлечено в плагин веб-пакетом:

NamedModulesPlugin

Так что просто добавьте в раздел плагинов

new webpack.NamedModulesPlugin()

Далее сравните изменения в файлах до и после следующих двух сборок:

Hash: e5bc78237ca9a3ad31f8
Version: webpack 3.8.1
Time: 1508ms
                                     Asset       Size  Chunks                    Chunk Names
            vendor-ebd9bfc583f45a344630.js     541 kB       0  [emitted]  [big]  vendor
             pageB-432105effc229524c683.js    1.09 kB       1  [emitted]         pageB
             pageA-158bf2a923c98ab49be2.js    1.09 kB       2  [emitted]         pageA
           runtime-9ca4cebe90e444e723b9.js    5.88 kB       3  [emitted]         runtime
pageA-d7ac82de795ddf50c9df43291d77b4c8.css   92 bytes       2  [emitted]         pageA
pageB-56185455ea60f01155a65497e9bf6c85.css  108 bytes       1  [emitted]         pageB
Hash: 7dce5d9dc88f619522fe
Version: webpack 3.8.1
Time: 1422ms
                                     Asset       Size  Chunks                    Chunk Names
            vendor-ebd9bfc583f45a344630.js     541 kB       0  [emitted]  [big]  vendor
             pageB-432105effc229524c683.js    1.09 kB       1  [emitted]         pageB
             pageA-dae883ddaeff861761da.js  940 bytes       2  [emitted]         pageA
           runtime-c874a0c304fa03493296.js    5.88 kB       3  [emitted]         runtime
pageA-35be2c21107ce4016c324daaa1dd5e28.css   49 bytes       2  [emitted]         pageA
pageB-56185455ea60f01155a65497e9bf6c85.css  108 bytes       1  [emitted]         pageB

Ничего себе, мы сравнили и обнаружили, что изменились только связанные файлы и код среды выполнения, а ни поставщик, ни pageB не изменились. Вкусно~~

Теперь, когда мы достигли нашей цели, мы можем пойти и посмотреть наш построенный код:

__webpack_require__("./src/b.less")
const sum = __webpack_require__("./src/common.js")
const _ = __webpack_require__("./node_modules/lodash/lodash.js")

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

new webpack.HashedModuleIdsPlugin()

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

Я думал, что это закончится здесь. Но внимательный человек обнаружит, что исполняемый файл изменяется каждый раз при компиляции. Что вызвало это? Проверьте это:

image
image

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

  • Найдите способ стабилизировать идентификатор чанка
  • Найдите время, чтобы изменить идентификатор чанка

7. Операции, связанные со стабильным чанком

Как найти id стабильного чанка

Поскольку мы знаем, что запись уникальна при упаковке webpack, можем ли мы просто использовать имя, соответствующее записи? Так что здесь все проще: мы просто заменяем id чанка на имя нашей записи.

Найдите время, чтобы изменить идентификатор чанка

По опыту в модуле есть вышеописанный процесс, так что думаю там тоже чанки.

before-chunk-ids -> optimize-chunk-ids -> after-optimize-chunk-ids

Итак, напишите плагин:

'use strict'

class chunkIDsByFilePath {
    constructor(options) {}

    apply(compiler) {
        compiler.plugin('compilation', compilation => {
            compilation.plugin('before-chunk-ids', chunks => {
                chunks.forEach(chunk => {
                    chunk.id = chunk.name
                })
            })
        })
    }
}

module.exports = chunkIDsByFilePath

К сожалению, у официалов тоже есть этот плагин, так что писать его не нужно.

NamedChunksPlugin

В построенном коде мы видим:

/******/         script.src = __webpack_require__.p + "" + chunkId + "-" + {"vendor":"ed00d7222262ac99e510","pageA":"b5b4e2893bce99fd5c57","pageB":"34be879b3374ac9b2072"}[chunkId] + ".js";

Исходный идентификатор фрагмента теперь стал именем записи, и риск изменения немного меньше. Вкусно~~

После того, как мы изменим имя, проблема будет такой же, как указанный выше идентификатор модуля изменится на имя, и файл станет больше. В настоящее время я все еще думаю об использовании хэша, чтобы справиться с этим так же, как описано выше. В это время действительно необходимо написать плагин. Волна Amway, которую мы написали сами
webpack-hashed-chunk-id-plugin.

До сих пор основные проблемы, возникающие при постоянном кэшировании, были решены.

наконец

Если вы хотите быстро построить проект, добро пожаловать на эту сторону архитектуры проекта, о.
webpack-project-seedУже есть онлайн-проекты, использующие это для запуска. Кстати, звезда.

благодарный:@pigcan