Chunk js что это

от admin

Chunk js что это

The data is a transfer from server to client for a particular request in the form of a stream. The stream contains chunks. A chunk is a fragment of the data that is sent by the client to server all chunks concepts to each other to make a buffer of the stream then the buffer is converted into meaningful data. In this article, we will discuss how to send chunks from the request body to the server. The request object is used for handling the chunks of data.

Syntax:

Parameters: This function accepts the following two parameters:

What are module, chunk and bundle in webpack?

I can’t understand concepts like module, chunk and bundle very well.

In the above configuration, are only-dev-server.js and foo.js both chunks? Are foo and bar both bundles? Are foo.js and bar.js both modules?

3 Answers 3

After spending a day digging into this I wanted to provide what I believe is a slightly more accurate answer. The webpack team has published a useful (and tricky to notice) glossary.

I believe the confusion about chunks and bundles is that the terms can refer to the same thing, but should be used at different points in the webpack process.

webpack glossary definitions

Module: Discrete chunks of functionality that provide a smaller surface area than a full program. Well-written modules provide solid abstractions and encapsulation boundaries which make up a coherent design and clear purpose.

Chunk: This webpack-specific term is used internally to manage the bundling process. Bundles are composed out of chunks, of which there are several types (e.g. entry and child). Typically, chunks directly correspond with the output bundles however, there are some configurations that don’t yield a one-to-one relationship.

Bundle: Produced from a number of distinct modules, bundles contain the final versions of source files that have already undergone the loading and compilation process.

(emphasis added by me)

Interpretation

I summarize the difference as: a chunk is a group of modules within the webpack process, a bundle is an emitted chunk or set of chunks.

The distinction is useful when talking about webpack processes as they are occuring. As modules are chunked together, they can change significantly (especially during plugin processing), so it isn’t accurate to call them bundles at that point, the chunks may not even be emitted as bundles in the final output! Then after webpack is finished it is useful to have a term to refer to all of the emitted, final files (bundles).

Your example

So for your example

  • Modules: «webpack/hot/only-dev-server.js», «./src/foo.js», «./src/bar.js» ( + any other modules that are dependencies of these entry points!)
  • Chunks: foo, bar
  • Bundles: foo, bar

In your example your chunks and bundles have a 1:1 relationship, but this isn’t always the case. For example adding source maps would mean you have a 1:2 relationship between a chunk and a bundle.

Внутреннее устройство и оптимизация бандла webpack

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

Чтобы разобраться в секретах этой магии, мы обратились к эксперту, человеку, который неоднократно залезал внутрь webpack, — Алексею Иванову. Он готов объяснить, как выглядит бандл изнутри, как на него влияют разные настройки, к чему и почему могут привести некоторые из них, а также рассказать, как все это отладить и оптимизировать.

В основе материала — доклад Алексея Иванова на конференции HolyJS 2017, проходившей в Санкт-Петербурге 2-3 июня.

В компании Злые марсиане я занимаюсь сервисом «eBay for business». eBay выдвигает довольно жесткие требования, например по тому, сколько должен весить сайт, первая страница, сколько она должна грузиться. Чтобы не выйти за пределы ограничений, мы регулярно смотрим на содержимое наших бандлов: что туда попадает и что делать, чтобы туда не попадало всякого странного.

Для этого мы используем различные инструменты: webpack bundle analyzer, webpack runtime analyzer и другие. Выглядит это примерно так:

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

  • React в бандле весит больше чем lib/react.js
  • Несколько версий lodash или underscore
  • Moment.js грузит 100+ локалей
  • Непонятные полифилы
  • Не работает tree shaking
  • Изменяется кеш для не изменившихся чанков
  • И так далее

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

После этого мне пришлось провести серию экспериментов: запустить webpack с пустым бандлом, с одним файликом и пустым js, с одним import и т.д. И мне это помогло, все возникшие проблемы я исправил. И тут я подумал, что я, наверное, не один такой, и с таким проблемами сталкивались многие. И поэтому решил поделиться своим опытом.

Содержание

  • CommonJS
  • Резолв путей до файлов
  • Устройство бандла изнутри
  • Глобальные константы и DefinePlugin
  • UglifyJS dead code elimination
  • ES6 modules и tree shaking
  • Выделение чанков и асинхронная подгрузка
  • Анализ результатов сборки

Про CommonJS-модули необходимо помнить следующее: они появились в node, и когда вы пользуетесь webpack, на самом деле используете не просто CommonJS-модули, а штуку, которая позволяет обеспечить совместимость с node. В общем CommonJS необходима:

  • JS-файл с переменными require, exports, module
  • this равно exports
  • exports по умолчанию равно <>
  • Чтобы мы могли использовать npm-модули в браузере, нам нужно эмулировать в браузере это поведение.

Дальше, если вы не написали расширение, то он сначала попытается добавить расширение js. Если его нет, он попытается найти папку, где лежит index.js. Если мы говорим про node, то она ищет по умолчанию не только js, а еще JSON, файлы с расширением node и другое. В webpack все это не используется, поэтому об этом можно пока забыть. Но это и так все знают.

Самое интересное — это то, что происходит, когда мы пытаемся сделать import модуля. Если мы написали module, что делает node и, соответственно, webpack? Она идет в текущую папку node, ищет папку node_modules, заходит в нее и ищет папку с названием модуля. Если она нашла ее, то она ее берет, и дальше все нормально.

Если она ее не нашла, она идет в папку на уровень выше, после этого — еще на уровень выше, и так пока не дойдет до корня файловой системы. И вот тут у нас есть первый нюанс, на который можно хорошо попасться. Он выглядит вот так:

Вы поставили какую-то библиотеку из npm, и в ней прописано, что ей нужна зависимость lodash 1.0.0, а вы в своем проекте используете lodash 5.0.0. И вот, если так произошло, npm создаст свою папку node_modules и поставит свою версию lodash. Если таких библиотек несколько, то в каждой может оказаться версия lodash, никак не связанная со всеми остальными. Если вы используете node, никаких проблем, а вот если webpack, то все версии lodash подгружаются в браузер. Это такие базовые вещи, которые webpack’у необходимо делать, чтобы хорошо работать.

Базовое устройство бандла

Базовое устройство бандла выглядит примерно так:

Вот у нас есть файл, и вроде бы там все хорошо, но есть нюансы. Браузер ни про require, ни про exports, ни про module ничего не знает. Ему надо об этом как-то рассказать. Самый простой способ — взять содержимое файла, обернуть его в функцию, в которой все эти штуки передать в параметрах, а потом в какой-то момент ее выполнить. На самом деле webpack примерно так и делает, но с небольшим изменением.

Первое изменение: мы меняем require на __webpack_require__. Зачем это нужно?

На самом деле, для двух вещей. Во-первых, чтобы, когда вы противоестественным образом подгружаете себе js в обход webpack, например, через JSONP или еще как-то, он не ломал билд. Потому что если создается функция с названием require, то могут быть всякие нехорошие вещи, а так есть некоторая защита от этого.

Во-вторых, из-за способа webpack помечать функции, которые он портирует внутрь бандла. Соответственно, мы можем делать всякие оптимизации.

Покажу еще раз. Вот тут у нас написан path:

Вот тут у нас в скобочках 0:

Почему так? В браузерах файловой системы нет. Поэтому когда webpack собирает модули в один файлик, он на самом деле кладет их в массив. Цифра в скобочках — по умолчанию индекс в этом массиве. Это нам аукнется еще в будущем. Почему массив? Потому что по сравнению со всякими объектами с ключами это самый компактный вариант и он будет меньше всего весить.

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

Первая строчка, которая нам важна, — это installedModules. То есть когда webpack грузит массив, он не инициализирует то, что в нем находится, автоматически — оно продолжает лежать в массиве мертвым кодом.

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

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

Что делает __webpack_require__

  1. Ищет инициированный модуль в кэше
  2. Создает заглушку и добавляет ее в массив
  3. Выполняет код модуля с this равным module.exports
  4. Возвращает module.exports

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

То есть мы вызываем не просто функцию, а именно через call, чтобы exports тоже попал в первый объект. Это нужно для обратной совместимости с node. В результате получается примерно следующее:

С этого момента у нашего модуля, который живет в installedModules, в exports уже не пустой объект, а то, что мы ему назначили и вернули. Почему это интересно и важно? Потому что, так как мы делаем все вот таким способом, у нас в бандле есть один рабочий экземпляр нашего модуля, и мы можем его использовать как замыкание.

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

После того как мы все это инициализировали, создали экземпляр модуля, мы возвращаем то, что находится внутри exports, и на этом успокаиваемся.

На самом деле, если бы мы говорили про CommonJS и самый простой бандл, то на этом можно было бы заканчивать, потому что в самом простом варианте webpack больше ничего не делает. На практике webpack стал популярным не из-за того, что он умеет вот так делать, а из-за того, что он умеет делать более сложные вещи. Например, он умеет делать вот так:

То есть когда вы указываете внутри require не полный путь файла, а какую-то регулярку, webpack сможет это собирать. При этом так как он не занимается анализом кода в живую, то он не может знать, что у вас на самом деле используется, и на всякий случай тащит туда все, что вообще может быть. То есть как это все работает? Webpack в данном случае создаст в массиве новый модуль, в котором запишет логику про resolve путей.

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

Проблема следующая. У нас есть библиотека moment.js, которая позволяет делать различные операции с датами. Когда вы ее используете через node, есть небольшой нюанс. Внутри корневого файла moment.js есть строчка require(‘./locale/’ + name). Соответственно, webpack идет внутрь папки locale, находит там 118 локалей, подгружает их все в bundle и создает карту примерно из 250 ключей. Наверное, это не совсем то, что хотелось бы видеть.

Для webpack есть ContextReplacementPlugin. Он проверяет первую часть по маске. Если маска совпала с тем, что написано в первом аргументе, он, вместо того чтобы возвращать то, что нашел в файловой системе, возвращает то, что вы ему передали вторым параметром.

Глобальные константы и DefinePlugin


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

Допустим, у вас есть dev и production-версии, у которых разный путь, и вы хотите, чтобы webpack при dev разработке работал с одним путем, а при production — с другим. Или есть разные номера версий. Еще один вариант использования, который применяется во многих библиотеках, — задание process.env.NODE_ENV. Данная переменная является эдаким общим шаблоном, который говорит, что эти функции не надо использовать в режиме разработки, а, например, функции для дебага — в режиме production.

Возникает вопрос, как нам передавать эти переменные? Существует DefinePlugin, где можно объявить данные переменные и, следовательно, они попадут внутрь бандла.

DefinePlugin берет строку слева, в нашем случае это VERSION, далее берет строку справа, идет в файлик, и регулярка заменяет старую строку на новую. В итоге результат выглядит примерно так:

JSON.stringify добавляет кавычки. Если бы кавычек не было, у нас была бы просто цифра 1.0.1 и все бы сломалось. Что здесь произошло? Если после замены какой-либо строки или числа webpack может понять, что это условие if, и он понимает, что левая часть сравнивается с правой и обе — константы, то он заменяет их на true либо на false. Так происходит для того, чтобы мог прийти UglifyPlugin и прибраться.

Вторая интересная вещь: как вы могли заметить, require осталась require. Она не заменилась на __webpack_require__. Соответственно, require в бандл не попала и в сборке ее не будет. Если же вы хотите отключить какую-то часть функционала, вот один из способов это сделать. Но, как и везде, тут есть нюансы. Проблема выглядит вот так:

Если вы хотите быть модным и использовать, например, babel, и у него есть деструктуринг, то вы можете написать NODE_ENV. К сожалению, это все поломает. Но почему? Вот так выглядит код после преобразования:

То есть одна переменная ссылается на вторую, которой тоже что-то приходит. И внутри ваше условие будет выглядеть так: NODE_ENV !== «production».

На самом деле если выкатить этот код на production, все будет работать так, как вы бы и хотели. Потому что переменной приходит false, и то, что внутри if, не выполнится. Но так как webpack не знает, что такое переменные, и он не делает полный анализ кода и не выясняет, что в этой переменной будет, то он не может понять, что то, что находится внутри, грузить не нужно. В этой ситуации модуль, который находится внутри и не должен грузиться, на самом деле подгрузится.

Читать:
Как в экселевских файлах одновременно удалить информацию

Поэтому, еще раз, если вы используете DefinePlugin, то обязательно используйте замену строк, т.е. полная строка заменяется на полную строку, никаких сокращений.

Что будет, если заменить process.env?

Webpack пытается эмулировать node, потому что большинство модулей, которые лежат в папке node_modules, могут быть и чисто node. Стандартная переменная в node — process. Поэтому когда вы не указали process.env, а какая-то из библиотек, которая в импорте, использует process.env или просто process, webpack думает, что это node-модуль, и добавляет полифил. В итоге мало того, что код не уменьшился, так еще и полифил добавился. То есть любую переменную, которая по умолчанию есть внутри node, если она используется в вашем файле и не заменена и не объявлена, при сборке webpack заменит полифилом.

Функции отладки в библиотеках

Если вы не добавите process для React, Redux и пр., будет много полифилов.

Сжатие кода

Что же делает Uglify со всем нашим кодом?

UglifyPlugin

  • Удаляет пробелы
  • Переименовывает переменные короткими именами
  • Делает dead code elimination

Что здесь происходит? Мы каким-то образом внутри условия сделали значение, которое стало константой и гарантированно не поменяется. Когда сюда придет Uglify и увидит это, то оставит вот так:

Если в условии живет переменная, которая является не статической, Uglify не сможет понять, что происходит, и ничего не удалит.

Поэтому даже если вы объявили переменную перед условием и указали, что она false, и сразу после нее идет if, то Uglify все равно не будет разбирать данный код. Весь этот код в итоге останется.

ES6 modules


Больше ничего глобального не происходит. Отличие, которое есть у import и export, примерно следующее. Во-первых, ключи в import и export обязательно иммутабельны, т.е. мы не можем собирать их из частей, они всегда должны быть константой. Во-вторых, import и export должны жить в верхнем scope.

В чем радость от использования import и export? Она выглядит примерно вот так:

Tree shaking

В теории webpack может точно определить, что используется у нас в приложении, и помечать их соответственно. На практике все несколько сложнее.

Одна из главных фич, которая есть в webpack 2, — понимание import и умение делать Tree shaking. Было бы замечательно, если бы все работало, но есть проблема.

На самом деле то, что export не используется, еще не означает, что код не выполнится и у него не будет сайд-эффектов. Если у кода есть сайд-эффекты, существует вероятность что-то поломать.

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

Что произойдет после того, как webpack прочтет это? Сначала место, которое он импортирует, превращает в CommonJS-модуль.

Тут уже интереснее. Что происходит в файле, с которого ушел export? Во-первых, убрали export перед словом const. Во-вторых, у той константы, которая экспортируется, мы вручную написали __webpack_exports__ с каким-то ключом. И все вроде бы хорошо. Когда Uglify приходит сюда и видит неиспользованную константу 2, он ее удаляет.

Но существуют нюансы.

Если у одного из экспортов использовалась переменная, которую раньше импортировали из какого-то import, то в этом месте webpack и Uglify уже ничего не удалят. Точнее, const method удалится, а import останется и его содержимое тоже добавится в бандл. Почему? Во-первых, потому что webpack не знает, будет ли он использоваться или нет, есть ли там сайд-эффекты или нет, поэтому он его оставил. Во-вторых, используется модуль, поэтому он его тоже оставил. После этого к нам пришел Uglify, увидел метод и удалил его, а import оставил, потому что это на самом деле вызов из массива, там могут быть сайд-эффекты и на самом деле Uglify про него ничего не знает. Поэтому он эту переменную оставит, и она будет жить внутри массива.

Например, мы решили использовать lodash-es, который написан с import и export. Мы импортируем из него метод и надеемся, что все остальное не попадет, но на самом деле так не сработает.

В строке, где from, мы сделали импорт всех модулей, которые есть в lodash, и теперь все они попадут к вам в бандл. Здесь от этой проблемы не уйти. Необходимо использовать какой-нибудь babel-плагин, который будет заменять lodash на конкретные методы, либо вручную записать вплоть до метода все, что необходимо.

И еще очень важно: по умолчанию, если вы используете babel с дефолтными настройками, то он на самом деле транспилирует все ваши красивые импорты и экспорты в обычный require. Поэтому если у вас стоит babel с дефолтными настройками, то во внутрь webpack’a у вас никакие импорты не попадут, а будут лишь только старые require. Соответственно, если вы хотите, чтобы они работали, необходимо в babel заменить транспилинг import и export.

Чанки

Чанк — это кусок кода, который можно загружать синхронно или асинхронно. Для того чтобы загрузить чанк, необходимо немного поправить код, который занимается инициализацией. Он правится примерно так — добавляется функция window[«webpackJsonp»].

  • Синхронные и асинхронные
  • В первый файл добавляется функция window[«webpackJsonp»]
  • В следующих файлах вызывается функция webpackJsonp со списком модулей и id модулей, которые надо запустить
  • Все модули попадают в общий массив и используются оттуда

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

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

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

У нас добавляется функция __webpack_require__.e, которая асинхронно загружает другие файлы. В ней нам важен один момент, который выглядит вот так:

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

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

CoomonsChunkPlugin

Собственно, как создаются чанки? Самый простой способ — при помощи CommonChunkPlugin.

Работает очень просто. Мы добавляем плагин, говорим ему, что «minChunks: 2» — это значит, что если в двух чанках используется какой-то общий плагин, то давайте создадим отдельный чанк, который будет грузиться синхронно, и в нем будет лежать общая часть. Но при этом есть пара нюансов.

Первый нюанс — когда мы вот так написали, и у нас есть чанки, которые создаются через import, то он не будет с ними работать, так как данные чанки считаются детьми, и у них отдельная логика.

Например, вы работаете с React и, чтобы у вас было не пять копий, а одна, вам необходимо добавить children: true, тогда он будет выносить общий модуль и из детей.

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

Вот такой код позволяет нам это сделать. Но с ним есть один нюанс. Он не работает.

Изменяющиеся индексы

Пример с node_modules не работает для кэша:

  1. При добавлении файлов меняются индексы
  2. Код загрузки и инициализации живет в первом файле:
  • меняется стартовый индекс
  • меняются ссылки на чанки

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

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

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

Необходимо сделать две вещи. Во-первых, зафиксировать имена файлов. Для этого в webpack есть два встроенных плагина. Первый позволяет оставлять те имена, которые вы использовали раньше внутри webpack’а, но это не очень удобно для production, так как имена становятся очень длинными. Второй позволяет менять имена на четырехбуквенные хэши.

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

Здесь «minChunks: Infinity» означает, что будет только код загрузки и 0 своих чанков. Соответственно, у вас получится не два файла, а три: первый — с кодом загрузки, второй — с node_modules, третий — с вашим кодом. Конечно, кода станет больше, но зато будет работать кэширование.

Вот так можно подключить два плагина подряд:

Анализ бандла

Для анализа бандла есть два полезных плагина:

webpack-bundle-analyzer

Строит treemap бандлов. Удобно проверять, не попали ли в бандл:

  • Две версии одной библиотеки
  • Копии библиотеки в разных чанках
  • Библиотеки, которые должны были вырезаться по условию
  • Непредвиденные зависимости у библиотек
  • Просто большие файлы

webpack-runtime-analyzer

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

code splitting

For big web apps it’s not efficient to put all code into a single file, especially if some blocks of code are only required under some circumstances. Webpack has a feature to split your codebase into «chunks» which are loaded on demand. Some other bundlers call them «layers», «rollups», or «fragments». This feature is called «code splitting».

It’s an opt-in feature. You can define split points in your code base. Webpack takes care of the dependencies, output files and runtime stuff.

To clarify a common misunderstanding: Code Splitting is not just about extracting common code into a shared chunk. The more notable feature is that Code Splitting can be used to split code into an on demand loaded chunk. This can keep the initial download small and downloads code on demand when requested by the application.

Defining a split point

AMD and CommonJs specify different methods to load code on demand. Both are supported and act as split points:

The require.ensure method ensures that every dependency in dependencies can be synchronously required when calling the callback . An implementation of the require function is sent as a parameter to the callback.

Note: require.ensure only loads the modules, it doesn’t evaluate them.

The AMD spec defines an asynchronous require method with this definition:

When called, all dependencies are loaded and the callback is called with the exports of the loaded dependencies .

Note: AMD require loads and evaluate the modules. In webpack modules are evaluated left to right.

Note: It’s allowed to omit the callback.

tl;dr: Webpack doesn’t support ES6 modules; use require.ensure or require directly depending on which module format your transpiler creates.

Webpack 1.x.x (coming in 2.0.0 !) does not natively support or understand ES6 modules. However, you can get around that by using a transpiler, like Babel, to turn the ES6 import syntax into CommonJs or AMD modules. This approach is effective but has one important caveat for dynamic loading.

The module syntax addition ( import x from ‘foo’ ) is intentionally designed to be statically analyzable, which means that you cannot do dynamic imports.

Luckily, there is a JavaScript API «loader» specification being written to handle the dynamic use case: System.load (or System.import ). This API will be the native equivalent to the above require variations. However, most transpilers do not support converting System.load calls to require.ensure so you have to do that directly if you want to make use of dynamic code splitting.

All dependencies at a split point go into a new chunk. Dependencies are also recursively added.

If you pass a function expression (or bound function expression) as callback to the split point, webpack automatically puts all dependencies required in this function expression into the chunk too.

If two chunks contain the same modules, they are merged into one. This can cause chunks to have multiple parents.

If a module is available in all parents of a chunk, it’s removed from that chunk.

If a chunk contains all modules of another chunk, this is stored. It fulfills multiple chunks.

Depending on the configuration option target a runtime logic for chunk loading is added to the bundle. I. e. for the web target chunks are loaded via jsonp. A chunk is only loaded once and parallel requests are merged into one. The runtime checks for loaded chunks whether they fulfill multiple chunks.

An entry chunk contains the runtime plus a bunch of modules. If the chunk contains the module 0 the runtime executes it. If not, it waits for chunks that contains the module 0 and executes it (every time when there is a chunk with a module 0 ).

A normal chunk contains no runtime. It only contains a bunch of modules. The structure depends on the chunk loading algorithm. I. e. for jsonp the modules are wrapped in a jsonp callback function. The chunk also contains a list of chunk id that it fulfills.

Initial chunk (non-entry)

An initial chunk is a normal chunk. The only difference is that optimization treats it as more important because it counts toward the initial loading time (like entry chunks). That chunk type can occur in combination with the CommonsChunkPlugin .

Split app and vendor code

To split your app into 2 files, say app.js and vendor.js , you can require the vendor files in vendor.js . Then pass this name to the CommonsChunkPlugin as shown below.

This will remove all modules in the vendor chunk from the app chunk. The bundle.js will now contain just your app code, without any of its dependencies. These are in vendor.bundle.js .

In your HTML page load vendor.bundle.js before bundle.js .

Multiple entry chunks

It’s possible to configure multiple entry points that will result in multiple entry chunks. The entry chunk contains the runtime and there must only be one runtime on a page (there are exceptions).

Running multiple entry points

With the CommonsChunkPlugin the runtime is moved to the commons chunk. The entry points are now in initial chunks. While only one initial chunk can be loaded, multiple entry chunks can be loaded. This exposes the possibility to run multiple entry points in a single page.

The CommonsChunkPlugin can move modules that occur in multiple entry chunks to a new entry chunk (the commons chunk). The runtime is moved to the commons chunk too. This means the old entry chunks are initial chunks now. See all options in the list of plugins.

There are optimizing plugins that can merge chunks depending on specific criteria. See list of plugins.

Related Posts