Как использовать splitChunks для точного управления разделением кода

внешний интерфейс Webpack
Как использовать splitChunks для точного управления разделением кода

задний план

Все друзья фронтенда знают, что для уменьшения размера пакета зависимые интерфейсные модули часто упаковываются независимо друг от друга, напримерvue,vue-routerпопасть в отдельный пакетvendorсередина. Кроме того, каждая страница сложной страницы с несколькими маршрутами часто упаковывается отдельно, и только при доступе к странице загружается js-пакет страницы для ускорения рендеринга домашней страницы.

Будь тоreactвсе ещеvueВсе они предоставляют полные инструменты, помогающие нам избежать утомительной работы по настройке. Когда мы создаем код, разделение кода выполняется автоматически.

Поэтому многие друзья не знают, что происходит за кадром. Что касается того, почему он так разделен и как управлять разделением кода, это еще более запутанно.

Вопрос викторины

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

// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: { app: "./src/index.js" },
  output: {
    filename: "[name].js",
    path: path.resolve(__dirname, "dist")
  },
  optimization: {
    splitChunks: {
      chunks: "all"
    }
  },
  plugins: [
    new HtmlWebpackPlugin()
  ]
};
// index.js

import "vue"
import(/*webpackChunkName: 'a' */ "./a");
import(/*webpackChunkName: 'b' */ "./b");
// a.js

import "vue-router";
import "./someModule"; // 模块大小大于30kb
// b.js

import "vuex";
import "./someModule"; // 模块大小大于30kb
// someModule.js
// 该模块大小超过30kb

// ...

Три способа разделения кода

В webpack есть три распространенных метода разделения кода:

  • точка входа: использоватьentryКонфигурация вручную разделяет код.
  • Динамический импорт: разделение кода с помощью встроенных вызовов функций модулей.
  • Чтобы предотвратить повторение: используйтеsplitChunksДедупликация и разделение чанков. Первый способ очень простой, нужно толькоentryВы можете настроить несколько записей в:
entry: { app: "./index.js", app1: "./index1.js" }

Второй способ заключается в автоматическом использованииimport()Загруженные модули разделены на отдельные пакеты:

//...

import("./a");

//...

Третий способ заключается в использованииsplitChunksплагин, настроить правила разделения, затемwebpackавтоматически удовлетворяет правиламchunkразделение. Все делается автоматически.

Первые два метода разделения просты для понимания. В этой статье в основном обсуждается третий метод.

Разделение кода splitChunks

splitChunksраспределение по умолчанию

splitChunks: {
    // 表示选择哪些 chunks 进行分割,可选值有:async,initial和all
    chunks: "async",
    // 表示新分离出的chunk必须大于等于minSize,默认为30000,约30kb。
    minSize: 30000,
    // 表示一个模块至少应被minChunks个chunk所包含才能分割。默认为1。
    minChunks: 1,
    // 表示按需加载文件时,并行请求的最大数目。默认为5。
    maxAsyncRequests: 5,
    // 表示加载入口文件时,并行请求的最大数目。默认为3。
    maxInitialRequests: 3,
    // 表示拆分出的chunk的名称连接符。默认为~。如chunk~vendors.js
    automaticNameDelimiter: '~',
    // 设置chunk的文件名。默认为true。当为true时,splitChunks基于chunk和cacheGroups的key自动命名。
    name: true,
    // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块,就分配到该组。模块可以被多个组引用,但最终会根据priority来决定打包到哪个组中。默认将所有来自 node_modules目录的模块打包至vendors组,将两个以上的chunk所共享的模块打包至default组。
    cacheGroups: {
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
        // 
    default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
        }
    }
}

Приведенная выше конфигурация резюмируется следующими четырьмя условиями:

  1. Модули повторно используются в коде или берутся изnode_modulesпапка
  2. Объем модуля больше или равен 30кб (до сжатия)
  3. При загрузке чанков по запросу максимальное количество параллельных запросов не может превышать 5
  4. При начальной загрузке страницы максимальное количество параллельных запросов не может превышать 3.
// index.js

import("./a");

// ...
// a.js

import "vue";

// ...

В приведенном выше коде результат сборки в конфигурации по умолчанию выглядит следующим образом:

图1

Анализ причин:

  • index.jsВ качестве входного файла он относится к случаю ручной настройки кода разделения в точке входа, поэтому он будет упакован независимо. (приложение.js)
  • a.jsпройти черезimport()Загрузка — это случай динамического импорта, поэтому пакет будет напечатан независимо. (1.js)
  • vueотnode_modulesдиректории и имеет размер более 30 КБ; переместите его изa.jsПосле демонтажа сa.jsПараллельная загрузка, количество запросов на параллельную загрузку равно 2, что не превышает 5 по умолчанию;vueПосле разделения количество загружаемых параллельно входных файлов не увеличивается и не превышает установленного по умолчанию 3.vueтакже встретитьсяsplitChunksУсловие разделения , отдельный пакет (2.js)

понимать куски

chunksрассказатьsplitChunksОбъект действия , необязательные значения которогоasync,initialиall. Значение по умолчаниюasync, то есть по умолчанию для разделения кода выбираются только асинхронно загруженные чанки. Это мы проверили на примере в начале. Здесь мы используем два примера, чтобы увидеть, когда значение кусковinitialиall, каков результат упаковки. Сначала измените значение кусков наinitial:

chunks: "initial"

Результат сборки следующий:

图2

Анализ причин:

когдаchunksзначениеinitialчас,splitChunksОбласть действия становится начальным куском, который не загружается асинхронно, например, нашindex.jsЭто кусок, который существует, когда он инициализирован. Модуль vue — это чанк, загружаемый асинхронно.a.jsВведен в, так что не будет изолирован.

chunksвсе еще используетсяinitial, Мыindex.jsиa.jsНебольшая модификация:

// index.js
import 'vue'
import('./a')
// a.js
console.log('a')

Результат сборки следующий:

图3

Анализ причин:

vueсуществуетindex.jsимпортируется напрямую, в то время какindex.jsЭто начальный кусок, поэтому он отделяется и попадаетvendors~app.jsсередина.

ты можешь позволитьsplitChunksКак насчет обработки как начальных фрагментов, так и асинхронных фрагментов? Ответ - да, вам просто нужноchunksизменить наall:

chunks: "all"

правильноindex.jsиa.jsНебольшая модификация:

// index.js
import 'vue-router'
import('./a')
// a.js
import 'vue'
console.log('a')

Результат сборки следующий:

图4

Анализ причин:

chunksзначениеallчас,splitChunksОбласть обработки включает в себя сценарии как начального, так и асинхронного фрагмента, поэтомуindex.jsсерединаvue-routerбыл разделен наvendors~app.js, а чанки загружаются асинхронноa.jsсерединаvueбыл разделен на3.jsсередина. Рекомендуется в разработкеchunksУстановить какall.

Понимание maxInitialRequests

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

правильноsplitChunksВнесите следующие изменения, другие используют конфигурацию по умолчанию:

chunks: 'initial',
maxInitialRequests: 1

Небольшая модификация index.js:

// index.js
import 'vue'

Результат сборки следующий:

图5

Анализ причин:

так какmaxInitialRequestsравно 1, еслиvueотindex.jsЕсли он удален из середины, вновь созданный фрагмент используется в качестве начального фрагмента.index.jsПредварительная зависимость страницы должна быть запрошена первой при инициализации страницы. Тогда количество запросов при инициализации становится равным 2, поэтому условие разделения не выполняется, поэтомуsplitChunksбез правindex.jsрасколоть.

Понимание maxAsyncRequests

иmaxInitialRequestsотносительно,maxAsyncRequestsвыражатьsplitChunksПосле разделения чанков количество асинхронных чанков, загружаемых параллельно, не превышает указанного значения.

правильноsplitChunksВнесите следующие изменения, другие используют конфигурацию по умолчанию:

maxAsyncRequests: 1

правильноindex.jsНебольшая модификация:

// index.js
import('./a')
// a.js
import 'vue'
console.log('a')

Результат сборки следующий:

图6

Анализ причин:так какmaxAsyncRequestsравно 1, так какa.jsчерезimport()Загружается асинхронно, количество параллельных асинхронных запросов в это время равно 1. еслиvueотa.jsЕсли он распакован, распакованный пакет также станет фрагментом асинхронного запроса. В этом случае, когда асинхронный запросa.jsКогда количество параллельных запросов равно 2. Следовательно, условие расщепления не выполняется, поэтомуsplitChunksбез правa.jsрасколоть.

Понимание minChunks

minChunksУказывает, что модуль должен быть разделен по крайней мере указанным числом фрагментов, прежде чем его можно будет разделить. По умолчанию 1.

правильноsplitChunksВнесите следующие изменения, другие используют конфигурацию по умолчанию:

chunks: 'all',
minChunks: 2

правильноindex.jsНебольшая модификация:

// index.js
import 'vue'

Результат сборки следующий:

图7

Анализ причин:

так какminChunksравно 2, поэтому только тогда, когдаvueОн будет разделен только тогда, когда он будет разделен как минимум на 2 фрагмента.

мыслительные вопросы

Каков результат сборки следующего кода?

chunks: 'all',
minChunks: 2
// index.js
import 'vue'
import './a'
// a.js
import 'vue'
console.log('a')

понимать группы кеша

cacheGroupsнаследоватьsplitChunksЗначения всех свойств в , таких какchunks,minSize,minChunks,maxAsyncRequests,maxInitialRequests,automaticNameDelimiter,name, мы также можемcacheGroupsпереназначить, перезаписатьsplitChunksзначение . Кроме того, есть некоторые свойства, которые можно использовать только вcacheGroupsиспользуется в:test,priority,reuseExistingChunk.

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

cacheGroupsЕсть две группы по умолчанию, однаvendors, преобразует все изnode_modulesМодуль каталога;default, который содержит модули, используемые более чем двумя чанками.

В предыдущем примере вы, возможно, заметили, насколько странными являются имена некоторых разделенных чанков, напримерvendors~app(по умолчаниюcacheGroupsКлюч группы + имя исходного чанка). Давайте посмотрим, как настроить имя разделенного чанка.

Сначала найдите группу, к которой принадлежит чанк, этот примерvendorsГруппа, внесите следующие изменения, другие используют конфигурацию по умолчанию:

chunks:'all',
cacheGroups: {
    vendors: {
      test: /[\\/]node_modules[\\/]/,
      name: "customName",
      priority: -10
    }
}

Небольшая модификация index.js:

// index.js
import 'vue'

Результат сборки следующий:

图8

Анализ причин:

смотреть изnode_modulesкаталог, назначенный по умолчаниюvendorsгруппа, если не указаноnameЕсли да, то будет использоваться имя чанка по умолчанию, здесь мы указалиname, поэтому последний фрагмент называетсяcustomName.

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

Добавьте новую группу:

chunks:'all',
cacheGroups: {
    vendors: {
      test: /[\\/]node_modules[\\/]/,
      name: "customName",
      priority: -10
    },
    customGroup: {
      test: /[\\/]node_modules[\\/]/,
      name: "customName1",
      priority: 0
    }
}

Результат сборки:

图9

Анализ причин:

Несмотря на то чтоvendorsиcustomGroupУсловия для обеих групп соблюдены, но поскольку последняя имеет более высокий приоритет, в конечном итоге она будетvueв упаковкеcustomName1.jsсередина.

Суммировать

Объяснение здесь, по-видимому, вы правыwebpackИметь четкое представление о том, как выполнять разделение кода. Не могли бы вы дать свой ответ на вопрос в начале статьи?

Подписывайтесь на нас

公众号@前端论道