Rollup строится вокруг нативной модульной системы ES Modules, и именно это определяет его поведение при импорте кода. В отличие от сборщиков, ориентированных на CommonJS, Rollup не пытается эмулировать различные модульные стандарты на уровне рантайма — он анализирует граф импортов статически и формирует оптимизированный результат.
Импорт в Rollup всегда рассматривается как часть графа зависимостей.
Каждый import в исходном коде становится узлом, который
связывается с другими модулями через явные ссылки. Такой подход
позволяет Rollup выполнять tree-shaking и исключать неиспользуемый код
на этапе сборки.
Простейшая форма импорта:
import { sum } from './math.js';
import config from './config.js';
import * as utils from './utils.js';
Каждая из этих конструкций обрабатывается Rollup следующим образом:
defaultКлючевая особенность Rollup — строго статический разбор импортов. Это означает, что такие конструкции недопустимы:
const moduleName = './math.js';
import { sum } from moduleName;
Причина заключается в том, что Rollup должен полностью построить граф зависимостей до выполнения кода. Любая динамика в путях импорта разрушает возможность анализа.
Допустимая динамика ограничивается использованием
import():
import('./math.js').then((module) => {
module.sum(2, 3);
});
Однако даже динамический импорт не становится частью основного графа — он выделяется в отдельный чанок, если это позволяет конфигурация.
Процесс инициализации сборки начинается с определения входных точек:
export default {
input: 'src/main.js',
output: {
file: 'bundle.js',
format: 'esm'
}
};
После запуска Rollup выполняет следующие этапы:
import-выраженияКаждый модуль в графе хранит метаданные:
После построения графа Rollup определяет порядок инициализации модулей. Важно понимать, что импорт сам по себе не означает выполнение кода — выполнение происходит строго по правилам ES Modules.
Пример:
// a.js
console.log('A');
export const a = 1;
// b.js
import { a } from './a.js';
console.log('B', a);
При сборке и запуске поведение будет следующим:
a.js выполняется первымb.jsRollup сохраняет эту семантику, не изменяя порядок исполнения, что критично для корректности ES Modules.
Все import-выражения поднимаются на уровень модуля. Это
означает, что они всегда обрабатываются до выполнения любого кода внутри
файла.
console.log(value);
import { value } from './data.js';
Несмотря на визуальный порядок, фактическая инициализация импорта
происходит раньше выполнения console.log.
Rollup использует это поведение для предварительного построения зависимостей, не требуя анализа выполнения кода.
Rollup строит прямые связи между экспортами и импортами без промежуточных абстракций. Каждый импортированный символ указывает на конкретный экспортный узел.
// math.js
export function sum(a, b) {
return a + b;
}
// main.js
import { sum } from './math.js';
Внутри Rollup это превращается в ссылку:
main.js.sum → math.js.sumЭто позволяет:
Особое значение имеет флаг side effects. Он определяет, должен ли модуль быть включён в бандл, если его экспорты не используются.
// logger.js
console.log('module loaded');
export const log = () => {};
Даже если log не используется, модуль будет включён,
если не указано обратное в package.json:
{
"sideEffects": false
}
Rollup использует это для оптимизации графа и удаления ненужных модулей.
На этапе импорта Rollup активно использует плагины для трансформации модулей. Каждый импорт проходит через цепочку хуков:
resolveId — определение пути модуляload — загрузка содержимогоtransform — преобразование кодаПример поведения:
export default {
plugins: [
{
resolveId(source) {
if (source === 'virtual-module') {
return source;
}
},
load(id) {
if (id === 'virtual-module') {
return 'export const x = 42;';
}
}
}
]
};
Таким образом, импорт может инициировать генерацию кода, даже если физического файла не существует.
Rollup использует кэш модулей для предотвращения повторной загрузки и анализа одного и того же файла. Если модуль уже присутствует в графе, повторный импорт не приводит к повторной обработке.
import './utils.js';
import './utils.js';
Фактически модуль utils.js будет обработан один раз, а
второй импорт лишь создаст дополнительную ссылку в графе.
Rollup корректно обрабатывает циклы:
// a.js
import { b } from './b.js';
export const a = 'A';
// b.js
import { a } from './a.js';
export const b = 'B';
В момент инициализации:
Rollup сохраняет эту модель без преобразований, опираясь на семантику ES Modules.
После построения графа Rollup выполняет финальную стадию оптимизации:
Пример трансформации:
Исходный код:
import { sum } from './math.js';
console.log(sum(2, 3));
После сборки:
function sum(a, b) {
return a + b;
}
console.log(sum(2, 3));
Импорт как отдельная сущность исчезает, так как его содержимое инлайнится в основной модуль.
При использовании динамических импортов Rollup разделяет граф:
import('./admin.js').then(m => m.init());
Это приводит к созданию отдельного чанка, который инициализируется
только при вызове import().
Инициализация таких чанков происходит лениво, что влияет на:
Поведение импорта зависит от output format:
В ESM:
export { x };
import { x } from './a.js';
В CJS:
const a = require('./a.js');
exports.x = a.x;
Инициализация при этом становится синхронной и более линейной, что влияет на порядок выполнения.
Entry-point задаёт стартовую точку всей системы инициализации. Именно с него начинается построение графа.
input: 'src/index.js'
Все зависимости, даже косвенные, становятся частью единой цепочки инициализации. Если модуль не достижим из entry-point, он исключается из графа и не участвует в сборке.
Импорт в Rollup — это не операция выполнения, а декларация зависимости. Вся система инициализации строится вокруг следующих принципов:
Эта модель позволяет Rollup формировать предсказуемый и минимальный бандл без потери семантики исходного кода.