Импорт и инициализация

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 import трактуется как экспорт с именем default
  • namespace import формирует объект-обёртку над всеми экспортами модуля

Статический анализ импортов

Ключевая особенность 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 выполняет следующие этапы:

  1. Загружает entry-модуль
  2. Парсит его AST (Abstract Syntax Tree)
  3. Извлекает все import-выражения
  4. Рекурсивно обходит зависимости
  5. Строит полный граф модулей

Каждый модуль в графе хранит метаданные:

  • список импортов
  • список экспортов
  • зависимости
  • флаги side effects

Инициализация модулей и порядок выполнения

После построения графа Rollup определяет порядок инициализации модулей. Важно понимать, что импорт сам по себе не означает выполнение кода — выполнение происходит строго по правилам ES Modules.

Пример:

// a.js
console.log('A');

export const a = 1;

// b.js
import { a } from './a.js';

console.log('B', a);

При сборке и запуске поведение будет следующим:

  1. a.js выполняется первым
  2. затем выполняется b.js
  3. зависимости инициализируются до использования

Rollup сохраняет эту семантику, не изменяя порядок исполнения, что критично для корректности ES Modules.

Hoisting импортов

Все 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

Это позволяет:

  • удалять неиспользуемые экспорты
  • переименовывать символы при minification
  • объединять модули без конфликтов

Инициализация модулей и side effects

Особое значение имеет флаг 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';

В момент инициализации:

  • модули создаются как “частично инициализированные”
  • экспортируемые bindings становятся live-ссылками
  • доступ к значениям возможен только после выполнения соответствующих модулей

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().

Инициализация таких чанков происходит лениво, что влияет на:

  • порядок загрузки
  • размер initial bundle
  • поведение runtime

Влияние формата вывода на инициализацию

Поведение импорта зависит от output format:

  • ESM — сохраняет оригинальную модель модулей
  • CJS — эмулирует require-цепочку
  • IIFE — сворачивает всё в единый scope

В ESM:

export { x };
import { x } from './a.js';

В CJS:

const a = require('./a.js');
exports.x = a.x;

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

Роль entry-point в инициализации

Entry-point задаёт стартовую точку всей системы инициализации. Именно с него начинается построение графа.

input: 'src/index.js'

Все зависимости, даже косвенные, становятся частью единой цепочки инициализации. Если модуль не достижим из entry-point, он исключается из графа и не участвует в сборке.

Итоговая модель инициализации импортов

Импорт в Rollup — это не операция выполнения, а декларация зависимости. Вся система инициализации строится вокруг следующих принципов:

  • статический граф модулей
  • раннее разрешение зависимостей
  • live bindings ES Modules
  • отсутствие runtime-резолвинга путей
  • строгая детерминированность порядка выполнения

Эта модель позволяет Rollup формировать предсказуемый и минимальный бандл без потери семантики исходного кода.