Механизм code splitting в Rollup тесно связан с выбранным форматом генерации выходных файлов. Возможность разделения кода определяется не только настройками сборщика, но и тем, способен ли целевой формат представлять несколько взаимосвязанных модулей.
При использовании одного входного файла Rollup обычно создаёт один выходной файл. Однако при наличии нескольких точек входа или динамических импортов сборщик может разбивать проект на набор отдельных чанков. Такие чанки должны сохранять связи между собой, а это возможно не во всех форматах модулей.
Основные форматы вывода Rollup:
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'es'
}
};
Поддержка code splitting зависит от значения свойства
format.
Формат es является наиболее естественной средой для code
splitting.
Каждый сгенерированный чанк остаётся полноценным ES-модулем, а связи
между файлами оформляются через стандартные инструкции
import и export.
Исходный код:
// main.js
import('./dashboard.js');
После сборки:
dist/
├── main.js
└── dashboard-8f7c1a.js
Содержимое основного файла:
import('./dashboard-8f7c1a.js');
Содержимое чанка:
export function init() {
console.log('Dashboard');
}
Преимущества формата:
По этой причине большинство современных проектов используют именно
format: 'es'.
Формат CommonJS также поддерживает разделение кода.
При генерации нескольких чанков Rollup создаёт набор файлов, которые
взаимодействуют через функцию require.
Конфигурация:
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'cjs'
}
};
Результат:
dist/
├── main.js
├── dashboard.js
└── vendor.js
Внутри файлов используются конструкции:
const vendor = require('./vendor.js');
или
Promise.resolve().then(() => require('./dashboard.js'));
Особенности:
Формат AMD исторически создавался для загрузки модулей по требованию, поэтому способен эффективно работать с code splitting.
Конфигурация:
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'amd'
}
};
Генерируемые файлы используют конструкцию:
define(['./chunk.js'], function(chunk) {
// код
});
Для динамических импортов применяется механизм загрузчика AMD:
require(['./dashboard.js'], function(module) {
module.init();
});
Такой подход хорошо работает совместно с загрузчиками вроде RequireJS, однако сегодня используется значительно реже, чем ES Modules.
Формат system предназначен для работы через загрузчик
SystemJS и полностью поддерживает разбиение проекта на чанки.
Конфигурация:
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'system'
}
};
Сгенерированные файлы имеют вид:
System.register([], function(exports) {
return {
execute() {
// код
}
};
});
При динамическом импорте создаются отдельные файлы, которые загружаются через SystemJS.
Преимущества:
Недостатком является необходимость подключения дополнительной библиотеки SystemJS.
Формат iife предназначен для генерации одного
самодостаточного файла.
Пример:
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'iife'
}
};
Результат представляет собой немедленно вызываемую функцию:
(function () {
'use strict';
console.log('Application');
})();
Такой файл не содержит механизма импорта других чанков. После загрузки он должен полностью работать самостоятельно.
Попытка использовать code splitting приводит к ошибке:
Invalid value for option "output.format" -
UMD and IIFE output formats are not supported
for code-splitting builds.
Причина проста: формат не предусматривает существование связанных файлов.
UMD разрабатывался как универсальная оболочка для различных систем модулей.
Пример:
(function (global, factory) {
typeof exports === 'object'
? factory(exports)
: typeof define === 'function' && define.amd
? define(['exports'], factory)
: factory(global.library = {});
})(this, function (exports) {
// код
});
Каждый UMD-файл считается самостоятельной библиотекой.
Если бы проект был разделён на несколько чанков, возникли бы проблемы:
Поэтому Rollup запрещает code splitting для umd.
Одним из признаков использования code splitting является
необходимость применения свойства output.dir.
Для одного файла:
output: {
file: 'dist/bundle.js',
format: 'es'
}
Для нескольких чанков:
output: {
dir: 'dist',
format: 'es'
}
Причина очевидна: сборщик не знает заранее количество генерируемых файлов.
При разбиении проекта могут появляться:
Поэтому необходимо указывать каталог, а не конкретное имя файла.
Code splitting часто возникает при использовании нескольких входных модулей.
Конфигурация:
export default {
input: {
admin: 'src/admin.js',
client: 'src/client.js'
},
output: {
dir: 'dist',
format: 'es'
}
};
Rollup анализирует зависимости:
admin.js
└── utils.js
client.js
└── utils.js
Общий модуль автоматически выносится:
dist/
├── admin.js
├── client.js
└── utils-3f91ab.js
Такой механизм работает только для форматов, поддерживающих связи между несколькими файлами.
Для iife и umd подобная конфигурация
невозможна.
Особенно важна связь между динамическими импортами и форматом сборки.
Исходный код:
button.addEventListener('click', async () => {
const module = await import('./analytics.js');
module.start();
});
При формате es создаётся отдельный файл:
main.js
analytics.js
При формате cjs:
main.js
analytics.js
с использованием require.
При формате amd:
main.js
analytics.js
с использованием механизма define и
require.
При формате iife Rollup не сможет корректно представить
такой импорт и завершит сборку ошибкой.
Поддерживающие code splitting форматы позволяют Rollup выделять общие зависимости в отдельные файлы.
Например:
import lodash from 'lodash';
используется в нескольких точках входа.
Вместо дублирования кода Rollup создаёт:
dist/
├── app.js
├── admin.js
└── vendor.js
Оба входных файла получают ссылки на общий чанк:
import './vendor.js';
или
const vendor = require('./vendor.js');
в зависимости от формата.
Подобная оптимизация невозможна для форматов, предполагающих существование только одного итогового файла.
На практике сложились следующие рекомендации:
| Формат | Поддержка code splitting |
|---|---|
| es | Полная |
| cjs | Полная |
| amd | Полная |
| system | Полная |
| iife | Нет |
| umd | Нет |
Для браузерных приложений предпочтителен формат es.
Для серверных приложений Node.js чаще используется cjs
либо современные ES-модули.
Для проектов с устаревшими загрузчиками могут применяться
amd и system.
Если требуется использовать динамические импорты, множественные точки
входа или автоматическое выделение общих зависимостей, выбор необходимо
делать среди форматов es, cjs,
amd и system, поскольку только они обладают
встроенной моделью взаимодействия между несколькими генерируемыми
чанками.