company_banner

Поиск правильного способа разделения материалов сайтов с помощью Webpack

Автор оригинала: David Gilbertson
  • Перевод
Поиск наилучшего способа организации материалов веб-проектов может оказаться непростой задачей. Существует множество различных сценариев работы пользователей с проектами, множество технологий и других факторов, которые нужно принимать во внимание.

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

image

Общие сведения


В соответствии с глоссарием Webpack, существуют две стратегии разделения файлов. Это — разделение бандла (bundle splitting) и разделение кода (code splitting). Эти термины могут показаться взаимозаменяемыми, но таковыми они не являются.

  • Разделение бандла — это методика разбиения больших бандлов на несколько частей, представляющих собой файлы меньшего размера. Такие файлы, в любом случае, как и при работе с единственным бандлом, будут загружаться всеми пользователями сайта. Сильная сторона этой методики заключается в улучшении использования браузерных механизмов кэширования.
  • Разделение кода — это подход, который подразумевает динамическую загрузку кода по мере возникновения необходимости в нём. Это приводит к тому, что пользователь загружает только тот код, который необходим ему для работы с некоей частью сайта в определённый момент времени.

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

Однако мне хотелось бы сказать, что для многих сайтов гораздо более ценным оказывается именно первая стратегия — разделение бандлов. И, пожалуй, от её внедрения способны выиграть буквально все веб-проекты.

Поговорим об этом подробнее.

Разделение бандлов


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

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

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

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

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

Вот сценарий, подходящий под общее описание, данное в предыдущем абзаце:

  • Алиса посещает наш сайт раз в неделю в течение 10 недель.
  • Мы обновляем сайт один раз в неделю.
  • Мы, каждую неделю, обновляем страницу со списком товаров (product list).
  • Кроме того, у нас есть страница с подробными сведениями о товаре (product details), но мы пока над ней не работаем.
  • На пятой неделе мы добавляем в материалы проекта новый npm-пакет.
  • На восьмой неделе мы обновляем один из уже используемых в проекте npm-пакетов.

Есть такие люди (вроде меня), которые попытаются сделать подобный сценарий настолько реалистичным, насколько это возможно. Но делать так не нужно. Реальный сценарий тут особого значения не имеет. Почему это так — мы скоро выясним.

▍Исходные условия


Предположим, общий размер нашего JavaScript-пакета составляет немалых 400 Кб и мы, в текущих условиях, передаём всё это пользователю в виде одного файла main.js. У нас имеется конфигурация Webpack, которая, в общих чертах, подобна нижеприведённой (то, что к нашему разговору не относится, я оттуда убрал):

const path = require('path');

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
  },
};

Webpack называет результирующий файл main.js в том случае, когда в конфигурации имеется единственная запись entry.

Если вы не очень хорошо себе представляете работу с кэшем, учитывайте, что каждый раз, когда я пишу тут main.js, я на самом деле имею в виду нечто вроде main.xMePWxHo.js. Безумная последовательность символов — это хэш содержимого файла, то, что в конфигурации называется contenthash. Использование такого подхода приводит к тому, что, при изменении кода, меняются и имена файлов, что принуждает браузер к загрузке новых файлов.

В соответствии с вышеописанным сценарием, когда мы, каждую неделю, вносим в код сайта какие-то изменения, строка contenthash пакета меняется. В результате, посещая еженедельно наш сайт, Алиса вынуждена загружать новый файл размером 400 Кб.

Если сделать симпатичную табличку (с бесполезной пока строкой итогов), содержащую данные о еженедельном объёме загрузки данных, приходящихся на этот файл, то у нас получится следующее.


Объём данных, загруженных пользователем

В результате оказывается, что пользователь, за 10 недель, загрузил 4.12 Мб кода. Этот показатель можно улучшить.

▍Отделение пакетов сторонних разработчиков от основного кода


Разделим большой пакет на две части. Наш собственный код будет в файле main.js, а код сторонних разработчиков в файле vendor.js. Сделать это несложно, в этом нам поможет следующая конфигурация Webpack:

const path = require('path');

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

Webpack 4 старается максимально облегчить жизнь разработчику, поэтому он делает всё, что может, и при этом не требует, чтобы ему сообщали о том, как именно нужно разбивать бандлы на части.

Такое вот автоматическое поведение программы приводит к немногочисленным восторгам, вроде: «Ну что за прелесть этот Webpack», и к множеству вопросов в духе: «А что это тут делается с моими бандлами?».

В любом случае, добавление в конфигурацию конструкции optimization.splitChunks.chunks = 'all' сообщает Webpack о том, что нам надо, чтобы он взял всё из node_modules и поместил бы это в файл vendors~main.js.

После того, как мы провели такое вот базовое разделение бандла, Алиса, регулярно посещающая наш сайт еженедельно, будет загружать при каждом визите файл main.js размером 200 Кб. А вот файл vendor.js она загрузит лишь три раза. Произойдёт это во время визитов в первую, пятую и восьмую недели. Вот соответствующая таблица, в которой, волею судьбы, размеры файлов main.js и vendor.js в первые четыре недели совпадают и равняются 200 Кб.


Объём данных, загруженных пользователем

В результате получается, что объём загруженных пользователем за 10 недель данных составил 2.64 Мб. То есть, в сравнении с тем, что было до разделения бандла, объём уменьшился на 36%. Не такой уж и плохой результат, достигнутый добавлением нескольких строк в конфигурационный файл. Кстати, прежде чем читать дальше — сделайте то же самое в своём проекте. А если вам надо обновиться с Webpack 3 на 4 — делайте это и не беспокойтесь, так как процесс это довольно простой и всё ещё бесплатный.

Мне кажется, что рассматриваемое тут улучшение выглядит несколько абстрактно, так как оно растянуто на 10 недель. Однако если считать объём данных, отправленных лояльному пользователю, то это честное сокращение этого объёма на 36%. Это очень хороший результат, но его можно улучшить.

▍Выделение пакетов в отдельные файлы


Файл vendor.js страдает от той же проблемы, что и исходный main.js. Заключается она в том, что изменение любого пакета, входящего в этот файл, приводит к необходимости повторной загрузки постоянным пользователем всего этого файла.

Почему бы нам не сформировать самостоятельные файлы для каждого npm-пакета? Сделать это совсем несложно, поэтому давайте разложим наши react, lodash, redux, moment, и прочее подобное, по отдельным файлам. В этом нам поможет следующая конфигурация Webpack:

const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: path.resolve(__dirname, 'src/index.js'),
  plugins: [
    new webpack.HashedModuleIdsPlugin(), // в результате хэши не будут неожиданно меняться
  ],
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash].js',
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: Infinity,
      minSize: 0,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name(module) {
            // получает имя, то есть node_modules/packageName/not/this/part.js
            // или node_modules/packageName
            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];

            // имена npm-пакетов можно, не опасаясь проблем, использовать 
           // в URL, но некоторые серверы не любят символы наподобие @
            return `npm.${packageName.replace('@', '')}`;
          },
        },
      },
    },
  },
};

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

  • В Webpack есть вполне разумные стандартные установки, которые, на деле, оказываются не такими уж и разумными. Например, максимальное количество выходных файлов установлено в значение 3, минимальный размер файла — в 30 Кб (то есть, файлы меньшего размера будут объединяться). Я это переопределил.
  • cacheGroups — это место, где мы задаём правила того, как Webpack должен сгруппировать данные в выходных файлах. У меня тут есть одна группа, vendor, которая будет использоваться для любого модуля, загруженного из node_modules. Обычно имя (name) для выходного файла задают в виде строки. Но я задал name в виде функции, которая будет вызываться для каждого обработанного файла. Затем я беру имя пакета из пути к модулю. В результате у нас получается один файл для каждого пакета. Например, npm.react-dom.899sadfhj4.js.
  • Имена пакетов, для того, чтобы их можно было опубликовать в npm, должны подходить для использования их в URL, поэтому нам не нужно выполнять операцию encodeURI для имён packageName. Однако я столкнулся с проблемой, которая заключается в том, что .NET-сервер отказывается работать с файлами, в именах которых есть символ @ (такие имена используются для пакетов с заданной областью действия имени, так называемых scoped packages), поэтому я, в соответствующем фрагменте кода, от подобных символов избавляюсь.

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

Алиса, наш постоянный посетитель, всё ещё каждую неделю заново загружает 200-килобайтный main.js, а при первом посещении сайта вынуждена загружать 200 Кб npm-пакетов, однако ей не придётся дважды загружать одни и те же пакеты.

Ниже показана новая версия таблицы со сведениями об объёмах еженедельных загрузок данных. По странному стечению обстоятельств размер каждого файла с npm-пакетами составляет 20 Кб.


Объём данных, загруженных пользователем

Теперь объём загруженных за 10 недель данных составляет 2.24 Мб. Это значит, что мы улучшили базовый показатель на 44%. Результат это уже весьма приличный, но тут возникает вопрос о том, можно ли сделать так, чтобы добиться результата, превышающего 50%. Если подобное получится — это будет просто здорово.

▍Разбиение кода приложения на фрагменты


Вернёмся к файлу main.js, который несчастной Алисе приходится загружать постоянно.

Выше я говорил о том, что на нашем сайте имеется два самостоятельных раздела. Первый — это список товаров, второй — страница с подробными сведениями о товаре. Размер кода, уникального для каждого из них, составляет 25 Кб (а 150 Кб кода применяется и там и там).

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

Кроме того, как оказалось, у нас имеется огромный встроенный SVG-файл, используемый для рендеринга значков, который весит целых 25 Кб и изменяется редко.

С этим надо что-то делать.

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

module.exports = {
  entry: {
    main: path.resolve(__dirname, 'src/index.js'),
    ProductList: path.resolve(__dirname, 'src/ProductList/ProductList.js'),
    ProductPage: path.resolve(__dirname, 'src/ProductPage/ProductPage.js'),
    Icon: path.resolve(__dirname, 'src/Icon/Icon.js'),
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[contenthash:8].js',
  },
  plugins: [
    new webpack.HashedModuleIdsPlugin(), // в результате хэши не будут неожиданно меняться
  ],
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: Infinity,
      minSize: 0,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name(module) {
            // получает имя, то есть node_modules/packageName/not/this/part.js
            // или node_modules/packageName
            const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];

            // имена npm-пакетов можно, не опасаясь проблем, использовать 
           // в URL, но некоторые серверы не любят символы наподобие @
            return `npm.${packageName.replace('@', '')}`;
          },
        },
      },
    },
  },
};

Трудолюбивый Webpack, кроме того, создаст файлы для того, что является общим, например, у ProductList и ProductPage, то есть, дублирующегося кода тут не будет.

То, что мы только что сделали, позволит Алисе экономить почти каждую неделю по 50 Кб трафика. Обратите внимание на то, что файл с описанием значков мы отредактировали на шестой неделе. Вот наша традиционная таблица.


Объём данных, загруженных пользователем

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

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

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

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

▍Вопрос №1. Разве необходимость выполнения множества запросов не вредит скорости загрузки сайта?


На этот вопрос можно дать простой короткий ответ: «Нет, не вредит». Подобная ситуация выливалась в проблему в былые времена, когда в ходу был протокол HTTP/1.1, а при использовании HTTP/2 это уже неактуально.

Хотя, надо отметить, что в этом материале, опубликованном в 2016 году, и в этой статье Khan Academy 2015 года делаются выводы о том, что даже при использовании HTTP/2, использование слишком большого количества файлов замедляет загрузку. Но в обоих этих материалах «слишком большое количество» означает «несколько сотен». Поэтому стоит помнить о том, что если вам приходится работать с сотнями файлов, на скорость их загрузки могут повлиять ограничения на параллельную обработку данных.

Если интересно, поддержка HTTP/2 имеется в IE 11 в Windows 10. Кроме того, я проводил всестороннее исследование среди тех, кто пользуется более старыми системами. Они единодушно заявили, что их скорость загрузки веб-сайтов особенно не заботит.

▍Вопрос №2. В Webpack-бандлах есть вспомогательный код. Создаёт ли он дополнительную нагрузку на систему?


Да, это так.

▍Вопрос №3. При работе с множеством маленьких файлов ухудшается уровень их сжатия, не так ли?


Да, это тоже так. На самом деле, мне хотелось бы сказать вот что:

  • Больше файлов — значит больше вспомогательного Webpack-кода.
  • Больше файлов — это меньший уровень сжатия.

Давайте с этим разберёмся для того, чтобы понять, насколько это плохо.

Только что я провёл испытание, в ходе которого код из файла размером 190 Кб был разбит на 19 частей. Это добавило примерно 2% к объёму данных, отправляемых в браузер.

В итоге получается, что при первом посещении сайта пользователь загрузит на 2% больше данных, а при последующих — на 60% меньше, и продолжаться это будет очень и очень долго.
Так стоит ли об этом беспокоиться? Нет, не стоит.

Когда я проводил сравнение системы, использующей 1 файл, и системы с 19 файлами, я испытал её с использованием различных протоколов, в том числе и HTTP/1.1. Нижеприведённая таблица очень сильно поддерживает идею о том, что больше файлов — значит лучше.


Данные о работе с 2 версиями сайта, размещённого на статическом хостинге Firebase, код которого имеет размеры 190 Кб, но, в первом случае, упакован в 1 файл, а во втором — разбит на 19

При работе в 3G и 4G-сетях на загрузку варианта сайта с 19 файлами ушло на 30% меньше времени, чем на загрузку сайта с одним файлом.

В данных, представленных в таблице, много шума. Например, один сеанс загрузки сайта по 4G (Run 2 в таблице) занял 646 мс, ещё один (Run 4) — 1116 мс, что на 73% дольше. Поэтому возникает ощущение, что говорить о том, что HTTP/2 «на 30% быстрее» — это несколько нечестно.

Я создал эту таблицу для того, чтобы посмотреть, что даёт использование HTTP/2. Но, на самом деле, единственное, что тут можно сказать, заключается в том, что применение HTTP/2, вероятно, не особо заметно влияет на загрузку страниц.

Настоящим сюрпризом стали две последних строчки в этой таблице. Тут представлены результаты для не самой новой версии Windows с IE11 и HTTP/1.1. Я, если бы заранее пытался предсказать результаты испытания, точно сказал бы, что такая конфигурация будет загружать материалы гораздо медленнее других. Правда, тут использовалось очень быстрое сетевое подключение, и мне, для подобных испытаний, вероятно, стоит пользоваться чем-то более медленным.

А теперь расскажу вам одну историю. Я, чтобы исследовать мой сайт на совсем уж древней системе, загрузил виртуальную машину Windows 7 с сайта Microsoft. Там был установлен IE8, который я решил обновить до IE9. Для этого я пошёл на страницу Microsoft, предназначенную для загрузки IE 9. Но сделать этого мне не удалось.


Вот незадача...

Кстати, если говорить об HTTP/2, хочется отметить, что этот протокол интегрирован в Node.js. Если вы хотите поэкспериментировать — можете воспользоваться написанным мной небольшим HTTP/2 сервером с поддержкой кэша ответов, gzip и brotli.

Пожалуй, о методике разделения бандлов я сказал всё, что хотел. Думаю, что единственный минус такого подхода, при использовании которого пользователям приходится загружать очень много файлов, на самом деле, не является таким уж «минусом».

Теперь поговорим о разделении кода.

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


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

Я предпочитаю, когда речь идёт о разделении кода, использовать правило 20/20, которое я только что сформулировал. Если есть какая-то часть сайта, которую посещают лишь 20% пользователей, и её функционал обеспечивают более 20% JavaScript-кода сайта, тогда этот код нужно загружать только по запросу.

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

▍Разделять или нет?


Как найти ответ на вопрос о том, нужно вам разделение кода или нет? Предположим, у вас имеется интернет-магазин, и вы размышляете о том, надо ли отделить от остального кода тот код, который используется для приёма оплаты от покупателей, так как лишь 30% посетителей у вас что-то покупают.

Что тут сказать? Во-первых — вам стоило бы поработать над наполнением магазина и продавать что-то такое, что окажется интересным большему количеству посетителей сайта. Во-вторых — нужно понять то, какой объём кода совершенно уникален для того раздела сайта, где принимается оплата. Так как перед «разделением кода» следует всегда выполнять «разделение бандла», и вы, надеюсь, так и делаете, то вы, вероятно, уже знаете о том, какие размеры имеет интересующий нас код.

Возможно, этот код может оказаться меньше, чем вы думаете, поэтому, прежде чем радоваться новой возможности оптимизации сайта, стоит всё спокойно посчитать. Если у вас, например, имеется React-сайт, тогда хранилище, редьюсеры, система маршрутизации, действия, будут совместно использоваться всеми частями сайта. Уникальный для разных частей сайта код будет, в основном, представлен компонентами и вспомогательными функциями для них.

Итак, вы выяснили, что совершенно уникальный код раздела сайта, используемого для оплаты покупок, занимает 7 Кб. Размер остального кода сайта — 300 Кб. В подобной ситуации я не стал бы заниматься разделением кода по нескольким причинам:

  • Если загрузить эти 7 Кб заранее, сайт это не замедлит. Помните, что файлы загружаются параллельно и попробуйте измерить разницу, необходимую для загрузки 300 Кб и 307 Кб кода.
  • Если вы будете загружать этот код позже, тогда пользователю придётся ждать после нажатия на кнопку «Оплатить». А это ведь тот самый момент, когда вам надо, чтобы всё прошло настолько гладко, насколько это возможно.
  • Разделение кода требует внесения изменений в приложение. В коде, в тех местах, где раньше всё делалось синхронно, появляется асинхронная логика. Конечно, космических сложностей в подобных преобразованиях кода не наблюдается, но это всё равно дополнительный объём работы, который, как мне кажется, должен выполняться ради ощутимого улучшения впечатления пользователя от работы с сайтом.

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

А теперь рассмотрим пару примеров применения этой технологии.

▍Полифиллы


Я начинаю именно с этого примера, так как то, что мы тут рассмотрим, просто реализуется и применимо к большинству сайтов.

Я использую на своём сайте множество полезных штуковин в виде полифиллов. Поэтому у меня имеется файл, в котором всё это подключается. Он состоит из следующих восьми строчек:

require('whatwg-fetch');
require('intl');
require('url-polyfill');
require('core-js/web/dom-collections');
require('core-js/es6/map');
require('core-js/es6/string');
require('core-js/es6/array');
require('core-js/es6/object');

Этот файл импортируется в самом начале кода файла index.js, который является точкой входа в приложение:

import './polyfills';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App/App';
import './index.css';

const render = () => {
  ReactDOM.render(<App />, document.getElementById('root'));
}

render(); // да, смысла во мне пока нет

Благодаря использованию конфигурации Webpack из предыдущего раздела, материалы полифиллов будут автоматически разделены на четыре файла, так как для их реализации используются четыре npm-пакета. Их размер составляет примерно 25 Кб, 90% браузеров они не нужны, поэтому имеет смысл загружать их динамически.

Благодаря применению Webpack 4 и использованию конструкции import() (не путайте её с ключевым словом import), организовать условную загрузку полифиллов очень просто:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App/App';
import './index.css';

const render = () => {
  ReactDOM.render(<App />, document.getElementById('root'));
}

if (
  'fetch' in window &&
  'Intl' in window &&
  'URL' in window &&
  'Map' in window &&
  'forEach' in NodeList.prototype &&
  'startsWith' in String.prototype &&
  'endsWith' in String.prototype &&
  'includes' in String.prototype &&
  'includes' in Array.prototype &&
  'assign' in Object &&
  'entries' in Object &&
  'keys' in Object
) {
  render();
} else {
  import('./polyfills').then(render);
}

Как видите, если всё, что нам надо, поддерживается — мы просто переходим к рендерингу. Если нет — импортируем полифиллы и уже после этого вызываем render(). Когда этот код выполняется в браузере, механизмы Webpack займутся загрузкой наших четырёх npm-пакетов, а когда они окажутся загружены и разобраны, будет осуществлён вызов render() и работа продолжится.

Кстати сказать, для использования import() вам понадобится плагин Babel dynamic-import. Кроме того, как сказано в документации к Webpack, команда import() использует промисы, поэтому полифилл для данной возможности нужно загружать отдельно от других полифиллов.

Как я и говорил, это очень просто. Рассмотрим теперь пример посложнее.

▍Динамическая загрузка материалов в React, основанная на маршрутах


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

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

В настоящий момент настройки маршрутизации сайта таковы, что, когда пользователь просматривает URL /admin, рендерится <AdminPage>. Webpack разделил всё по бандлам, поэтому ему нужно найти конструкцию import AdminPage from './AdminPage.js' и включить это в первоначальную загрузку сайта.

Однако нам это не нужно. Нам надо поместить ссылку на материалы для административной страницы в команду, выполняющую динамический импорт, наподобие import('./AdminPage.js'), в результате Webpack будет знать о том, что загружать эти материалы надо динамически.

Причём, для этого не нужно заниматься конфигурированием.

Поэтому, вместо того, чтобы ссылаться на AdminPage напрямую, я могу создать другой компонент, который будет рендериться при переходе пользователя по URL /admin. Например, выглядеть он может так:

import React from 'react';

class AdminPageLoader extends React.PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      AdminPage: null,
    }
  }

  componentDidMount() {
    import('./AdminPage').then(module => {
      this.setState({ AdminPage: module.default });
    });
  }

  render() {
    const { AdminPage } = this.state;

    return AdminPage
      ? <AdminPage {...this.props} />
      : <div>Loading...</div>;
  }
}

export default AdminPageLoader;

В основе всего этого лежит весьма простая идея. Когда данный компонент монтируется (предполагается, что пользователь перешёл по URL /admin), мы динамически загружаем ./AdminPage.js, после чего сохраняем ссылку на этот компонент в состоянии приложения.

В методе render() мы просто выводим, ожидая загрузки <AdminPage>, надпись <div>Loading...</div>, или выводим <AdminPage> после того, как эта сущность будет загружена и сохранена в состоянии.

Я сделал всё это своими силами ради интереса, но в реальных проектах достаточно воспользоваться react-loadable, как описано в документации React по разделению кода.

Итоги


Полагаю, я рассказал всё, что хотел (хотя, надо отметить, мы не говорили тут о CSS). Подведём краткие итоги:

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

Уважаемые читатели! Используете ли вы разделение бандлов и разделение кода в своих проектах?

RUVDS.com
RUVDS – хостинг VDS/VPS серверов

Комментарии 26

    +1
    Webpack — это круто. Но! С выходом 4-ой версии появились вопросы как раз по чанкам. Может быть кто ответит как сделать так, чтобы было хорошо, вот в такой ситуации?
    У нас есть несколько точек входа: app1.ts, app2.ts, app3.ts и куча модулей. Часть их них из npm, часть свои.
    При сборке с опцией optimization.splitChunks.chunks = 'all' получаем огромное количество чанков.
    Внимание вопрос: как мне узнать какие из чанков надо добавлять на страницу app1.html, какие на страницу app2.html и app3.html соответственно? Интересующая информация есть в output'е самого webpack'а, но она мне нужна в момент формирования тела страницы сервером (.net). А где ее взять? Может есть какой плагин, который сохранит нужный список чанков (js+css) для каждой точки входа?
      0
      так чанки же грузятся автоматически из точек входа
        0

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

          +1

          Все таки не грузятся. Массив deferredModules заполняется и сам никогда не читается. Базовый код webpackBootstrap не содержит в себе кода для подгрузки deferredModules.
          Если не прав — ткните пальцем. Буду только рад.


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


          Следующий код, вставленный в конец страницы, загружает все необходимое (почему это делаю я, а не webpack?)


          <script type="text/javascript">
                  let modules = window["webpackJsonp"] || [];
                  for (let module of modules) {
                      let [, , dependencies] = module;
                      for (let dependency of dependencies) {
                          for (let t = 2; t < dependency.length; t++) {
                              const script = document.createElement("script");
                              script.src = "/public/" + dependency[t] + ".js";
                              document.head.appendChild(script);
                          }
                      }
                  }
          </script>

          Остается не закрытый вопрос с CSS. Если воспользоваться extract-text-webpack-plugin (который все еще в стадии beta для webpack4), то не ясно как его грузить. Тем более что для CSS порядок загрузки важен.

            0
            Вот такой же вопрос возник, когда читал статью. Вот выплюнет он несколько десятков (или даже сотен) js файлов, и как их потом подключать? И которые из них нужно или не нужно подключать? У нас 4 бандла: meta (вроде, точно не помню), vendor, admin и main. Но что из этого подключать мы знаем. А в его варианте?
              0
              Так используйте просто динамический импорт. Вебпак сам все подгрузит.
              0
              Так а есть ли причины css делать через webpack+extract-text-webpack-plugin для последующей загрузки через `link rel=«stylesheet»` (а для чего еще — может я чего не знаю)? Ну кроме «мне так удобно»? Может как-то еще можно через webpack в run time этот css прогонять и какая-то появляется «дополнительная ценность» которой я не вижу…
                0

                Css должны быть загружены до того, как загрузился js. В противном случае возможна ситуация, когда при открытии страницы вы видите некоторое время кашу.

                  0
                  ОК (никакой runtime магии webpackа). Каша это серьезная причина грузить через `link rel=«stylesheet»`, а в чем причина делать сss (да еще и чанками) через вебпак а не скажем клеить как хочешь gulp'ом? Вот зачем пихать css в трубу webpackа а потом вытаскивать плагинами если это ничего не добавляет в дев тайм? Или добавляет?
                    0
                    Потому что нам нужна не только «склейка» css, но и некоторая пост-обработка. К тому же рядом с webpack есть webpack-dev-server, который позволяет делать магию hot-reload.

                    А от gulp мы ушли, т.к. он стал мешать.
                      0
                      это очень хитрая потстобработка которой необходим вебпка? post-css тоже ведь можно из gulp вызвать.
                      webpack-dev-server делает hot reload `link rel=«stylesheet»`?
                        0

                        Да. Очень хитрая. Для webpack'а css это модуль (для него все есть модуль) в котором могут быть ссылки на другие модули (картинки, шрифты). Эти же модули можно использовать в ts/js.


                        Например, можно писать так:


                        div.picture{
                          background-image: src('./images/picture.jpg');
                        }

                        и так:


                        import picture from "./images/picture.jpg";
                        document.createElement("img").src = picture;

                        Webpack поймет что это (picture.jpg) один и тот же модуль. Магия может быть применена и к картинке в том числе. Например, файл может быть заменен base64, если он очень маленький. И это будет прозрачно для js и css.

                          0
                          И вы правда это используете в asp.net mvc core т.е. в MPA? т.е. отказались от `img src` в html/razor коде?
                            0
                            Да, а в чем проблема?
                              0
                              а чем вы заменили img src? (я действительно заинтересован вытянуть опыт, тут нет подколов).

                              проблемам посвещен вся вот эта записись и блог habr.com/post/423693. вы их решили — наверное молодцы. Но вы же их и создали (так что те кто на это всё посмотрят и не пойдут — тоже наверное молодцы).
                                0
                                Расскажите может сюда vitaliy@leschenko.by в чем ваша проблема. Если смогу помогу.
                                  0
                                  спасибо. да я просто тогда в PM раз вы как считаете вопрос чем заменить img src при создании html/razor оффтопом здесь.
            0
            Посмотрите на этот webpack-плагин: github.com/ztoben/assets-webpack-plugin

            Он умеет выдавать json файл с нужными маппингами.
              +1
              Смотрел. Не то. Этот плагин говорит где будет лежать файл имя которого я знаю (по исходникам). Но он ничего не говорит о зависимостях.

              В процессе сборки webpack пишет в консоль:
              много всего
              Entrypoint site-custompage-view = runtime.js?f9cd204313fcc36935c6 runtime.js.map?f9cd204313fcc36935c6 vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js.map?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js.map?e3d572d8ec6a9d2d8786 site-custompage-view.js?2c9e3ea4ee8bc8e96fe6 site-custompage-view.js.map?2c9e3ea4ee8bc8e96fe6
              Entrypoint site-page-view = runtime.js?f9cd204313fcc36935c6 runtime.js.map?f9cd204313fcc36935c6 vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js.map?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js.map?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-page-view~site-post-view.js?751fd19d1df6f81e67a2 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-page-view~site-post-view.js.map?751fd19d1df6f81e67a2 site-page-view.js?be8a2a44c217161efcfc site-page-view.js.map?be8a2a44c217161efcfc
              Entrypoint site-post-view = runtime.js?f9cd204313fcc36935c6 runtime.js.map?f9cd204313fcc36935c6 vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js.map?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js.map?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-page-view~site-post-view.js?751fd19d1df6f81e67a2 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-page-view~site-post-view.js.map?751fd19d1df6f81e67a2 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js?c129b4d28fd029014b5d vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js.map?c129b4d28fd029014b5d vendors~admin-pages~admin-posts~profile-posts~site-common~site-post-view.js?41bdadb1b1aef7fb7442 vendors~admin-pages~admin-posts~profile-posts~site-common~site-post-view.js.map?41bdadb1b1aef7fb7442 vendors~admin-pages~admin-posts~profile-posts~site-post-view.js?57e14dc4e77f560ad7c3 vendors~admin-pages~admin-posts~profile-posts~site-post-view.js.map?57e14dc4e77f560ad7c3 vendors~admin-posts~profile-posts~site-common~site-post-view.js?3f714864320cc42bc538 vendors~admin-posts~profile-posts~site-common~site-post-view.js.map?3f714864320cc42bc538 admin-pages~admin-posts~profile-posts~site-common~site-post-view.js?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.css?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.js.map?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.css.map?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js?2245624565e3fff297f0 admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js.map?2245624565e3fff297f0 admin-posts~site-post-view.js?da420a76849dabea6ce6 admin-posts~site-post-view.css?da420a76849dabea6ce6 admin-posts~site-post-view.js.map?da420a76849dabea6ce6 admin-posts~site-post-view.css.map?da420a76849dabea6ce6 site-post-view.js?77547b7ea939e0f5340c site-post-view.js.map?77547b7ea939e0f5340c
              Entrypoint site-common = runtime.js?f9cd204313fcc36935c6 runtime.js.map?f9cd204313fcc36935c6 vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js.map?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js.map?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-posts~site-common~site-post-view.js?41bdadb1b1aef7fb7442 vendors~admin-pages~admin-posts~profile-posts~site-common~site-post-view.js.map?41bdadb1b1aef7fb7442 vendors~admin-posts~profile-posts~site-common~site-post-view.js?3f714864320cc42bc538 vendors~admin-posts~profile-posts~site-common~site-post-view.js.map?3f714864320cc42bc538 vendors~site-common.js?9faf27b3baae39f343e8 vendors~site-common.js.map?9faf27b3baae39f343e8 admin-pages~admin-posts~profile-posts~site-common~site-post-view.js?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.css?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.js.map?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.css.map?1de9d2f3dd37c79fa4d0 site-common.js?7989aa18b26d9f337922 site-common.css?7989aa18b26d9f337922 site-common.js.map?7989aa18b26d9f337922 site-common.css.map?7989aa18b26d9f337922
              Entrypoint profile-manage-authenticator = runtime.js?f9cd204313fcc36935c6 runtime.js.map?f9cd204313fcc36935c6 vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js.map?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js.map?e3d572d8ec6a9d2d8786 profile-manage-authenticator.js?5681b00d8f196819264e profile-manage-authenticator.js.map?5681b00d8f196819264e
              Entrypoint profile-filestorage = runtime.js?f9cd204313fcc36935c6 runtime.js.map?f9cd204313fcc36935c6 vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js.map?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-page-view~site-post-view.js?751fd19d1df6f81e67a2 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-page-view~site-post-view.js.map?751fd19d1df6f81e67a2 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js?c129b4d28fd029014b5d vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js.map?c129b4d28fd029014b5d admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js?2245624565e3fff297f0 admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js.map?2245624565e3fff297f0 profile-filestorage.js?25c836f2869fc1274fa0 profile-filestorage.css?25c836f2869fc1274fa0 profile-filestorage.js.map?25c836f2869fc1274fa0 profile-filestorage.css.map?25c836f2869fc1274fa0
              Entrypoint profile-posts = runtime.js?f9cd204313fcc36935c6 runtime.js.map?f9cd204313fcc36935c6 vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js.map?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js.map?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-page-view~site-post-view.js?751fd19d1df6f81e67a2 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-page-view~site-post-view.js.map?751fd19d1df6f81e67a2 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js?c129b4d28fd029014b5d vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js.map?c129b4d28fd029014b5d vendors~admin-pages~admin-posts~profile-posts~site-common~site-post-view.js?41bdadb1b1aef7fb7442 vendors~admin-pages~admin-posts~profile-posts~site-common~site-post-view.js.map?41bdadb1b1aef7fb7442 vendors~admin-pages~admin-posts~profile-posts~site-post-view.js?57e14dc4e77f560ad7c3 vendors~admin-pages~admin-posts~profile-posts~site-post-view.js.map?57e14dc4e77f560ad7c3 vendors~admin-posts~profile-posts~site-common~site-post-view.js?3f714864320cc42bc538 vendors~admin-posts~profile-posts~site-common~site-post-view.js.map?3f714864320cc42bc538 admin-pages~admin-posts~profile-posts~site-common~site-post-view.js?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.css?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.js.map?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.css.map?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js?2245624565e3fff297f0 admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js.map?2245624565e3fff297f0 admin-pages~admin-posts~profile-posts.js?8f61e4a5e7732c12614d admin-pages~admin-posts~profile-posts.js.map?8f61e4a5e7732c12614d profile-posts.js?50cea853c3913a6c9592 profile-posts.css?50cea853c3913a6c9592 profile-posts.js.map?50cea853c3913a6c9592 profile-posts.css.map?50cea853c3913a6c9592
              Entrypoint admin-pages = runtime.js?f9cd204313fcc36935c6 runtime.js.map?f9cd204313fcc36935c6 vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js.map?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js.map?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-page-view~site-post-view.js?751fd19d1df6f81e67a2 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-page-view~site-post-view.js.map?751fd19d1df6f81e67a2 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js?c129b4d28fd029014b5d vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js.map?c129b4d28fd029014b5d vendors~admin-pages~admin-posts~profile-posts~site-common~site-post-view.js?41bdadb1b1aef7fb7442 vendors~admin-pages~admin-posts~profile-posts~site-common~site-post-view.js.map?41bdadb1b1aef7fb7442 vendors~admin-pages~admin-posts~profile-posts~site-post-view.js?57e14dc4e77f560ad7c3 vendors~admin-pages~admin-posts~profile-posts~site-post-view.js.map?57e14dc4e77f560ad7c3 admin-pages~admin-posts~profile-posts~site-common~site-post-view.js?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.css?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.js.map?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.css.map?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js?2245624565e3fff297f0 admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js.map?2245624565e3fff297f0 admin-pages~admin-posts~profile-posts.js?8f61e4a5e7732c12614d admin-pages~admin-posts~profile-posts.js.map?8f61e4a5e7732c12614d admin-pages.js?9fed5a72a0b2256c01c9 admin-pages.css?9fed5a72a0b2256c01c9 admin-pages.js.map?9fed5a72a0b2256c01c9 admin-pages.css.map?9fed5a72a0b2256c01c9
              Entrypoint admin-posts = runtime.js?f9cd204313fcc36935c6 runtime.js.map?f9cd204313fcc36935c6 vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-filestorage~profile-manage-authenticator~profile-posts~site-~3a451633.js.map?a94efb6d35554e8d159d vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-manage-authenticator~profile-posts~site-common~site-custompa~cf03b557.js.map?e3d572d8ec6a9d2d8786 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-page-view~site-post-view.js?751fd19d1df6f81e67a2 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-page-view~site-post-view.js.map?751fd19d1df6f81e67a2 vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js?c129b4d28fd029014b5d vendors~admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js.map?c129b4d28fd029014b5d vendors~admin-pages~admin-posts~profile-posts~site-common~site-post-view.js?41bdadb1b1aef7fb7442 vendors~admin-pages~admin-posts~profile-posts~site-common~site-post-view.js.map?41bdadb1b1aef7fb7442 vendors~admin-pages~admin-posts~profile-posts~site-post-view.js?57e14dc4e77f560ad7c3 vendors~admin-pages~admin-posts~profile-posts~site-post-view.js.map?57e14dc4e77f560ad7c3 vendors~admin-posts~profile-posts~site-common~site-post-view.js?3f714864320cc42bc538 vendors~admin-posts~profile-posts~site-common~site-post-view.js.map?3f714864320cc42bc538 admin-pages~admin-posts~profile-posts~site-common~site-post-view.js?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.css?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.js.map?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-posts~site-common~site-post-view.css.map?1de9d2f3dd37c79fa4d0 admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js?2245624565e3fff297f0 admin-pages~admin-posts~profile-filestorage~profile-posts~site-post-view.js.map?2245624565e3fff297f0 admin-pages~admin-posts~profile-posts.js?8f61e4a5e7732c12614d admin-pages~admin-posts~profile-posts.js.map?8f61e4a5e7732c12614d admin-posts~site-post-view.js?da420a76849dabea6ce6 admin-posts~site-post-view.css?da420a76849dabea6ce6 admin-posts~site-post-view.js.map?da420a76849dabea6ce6 admin-posts~site-post-view.css.map?da420a76849dabea6ce6 admin-posts.js?07ddd1170568a8892fe6 admin-posts.css?07ddd1170568a8892fe6 admin-posts.js.map?07ddd1170568a8892fe6 admin-posts.css.map?07ddd1170568a8892fe6


              Здесь для каждой точки входа набор необходимых файлов. Но я понятия не имею как этот выхлоп сохранить куда-то. assets-webpack-plugin может по имени файла сказать как он будет выглядеть с хешем и где он лежит, но мне надо не 1 файл, а список для каждой точки входа.
                0
                Сейчас как раз стою перед такой же проблемой. Если я вас правильно понял, то в репах webpack как раз описана данная ситуация (github), но не описывается как можно вытащить манифест с привязкой к точкам входа. В таком случае догрузки нужных файлов в рантайме действительно не происходит. Я планировал использовать динамические импорты (вот тут есть хороший пример для понимания), или вот это плугин (webpack-assets-manifest) основываясь на данном issue.
            0
            0
            Основная причина разделения, в том что Алиса может ходит по нашему сайту, и всего один файл будет грузиться для второй и последующих страниц каждого вида (в первый — два). Тут мы получаем ускорение загрузки. Плюс мы получаем ускорение за счет прекомпиляции частоиспользуемого кода общего бандла.

            А рассуждения о том как часто Алиса заходит на сайт не учитывают размер кеша в браузере. В firefox он 512 Мб, а в Хроме динамический, но прямо сейчас у меня по умолчанию установлено в 320 Мб.

            И если Алиса заходит на другие сайты в интернете, то получается что в день она весь свой кеш полностью обновит.
              0
              «хотя, надо отметить, мы не говорили тут о CSS»
              в оригинале «Dammit I forgot to mention CSS» — нахальная отмазка халтурщика — хотя и понятно что вопрос сложный.

              Во-первых, так надо или нет css по трубе sass/postcss прогонять через webpack и зачем? Кто что решил? Во-вторых, ну и как его чанками (вендор, не вендор) создавать?
              0
              Stackoverflow с открытым bounty на +500 на схожую тему.
              stackoverflow.com/questions/52350958/webpack-4-migrating-from-commonschunkplugin-with-splitchunksplugin
              Задача деления на чанки по немного другим принципам.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое