Модульный бандлер ориентирован на работу с современным JavaScript и ESM-экосистемой, но при этом сам по себе не является транспилятором языка. Это ключевое ограничение определяет подход к использованию экспериментальных возможностей ECMAScript, включая декораторы, приватные поля, pipeline-операторы и другие stage-фичи.
В основе работы лежит парсинг исходного кода через Acorn и последующая трансформация модулей. Любая конструкция, которая не поддерживается парсером или текущей версией ECMAScript-спеки, требует внешнего преобразования до этапа бандлинга.
Rollup не предназначен для понимания нестандартного синтаксиса без расширений. Его задача ограничивается:
Любая синтаксическая конструкция проходит этап парсинга. Если она не поддерживается, процесс прерывается. Это особенно важно для декораторов, которые исторически проходили несколько стадий стандартизации.
Декораторы в JavaScript прошли несколько несовместимых версий:
Эта несовместимость делает невозможным «нативную» поддержку без выбора конкретного трансформера.
Пример:
@sealed
class Service {
@readonly
config() {}
}
Без трансформации парсер не сможет обработать @sealed и
@readonly, если они не включены в поддерживаемый
синтаксис.
Наиболее распространённый способ работы с декораторами — подключение Babel через плагин:
Конвейер выглядит следующим образом:
import babel from '@rollup/plugin-babel';
export default {
input: 'src/index.js',
output: {
format: 'esm',
file: 'dist/bundle.js'
},
plugins: [
babel({
babelHelpers: 'bundled',
extensions: ['.js', '.ts']
})
]
};
Ключевой момент заключается в том, что декораторы должны быть явно включены в Babel-конфигурации:
{
"plugins": [
["@babel/plugin-proposal-decorators", { "version": "2023-11" }]
]
}
TypeScript часто используется как первичный транспилятор до этапа бандлинга. В этом случае декораторы обрабатываются раньше, чем код попадает в Rollup.
TS → JS → Rollup
TS → Babel → Rollup
Экспериментальные возможности ECMAScript делятся на две категории:
Требуют изменения AST:
Не требуют парсинга, но могут влиять на runtime:
Rollup критичен к первой категории, так как она влияет на этап анализа кода.
Rollup использует Acorn как базовый парсер. Для экспериментальных фич возможны расширения через:
Однако на практике это редко применяется для декораторов, так как:
Декораторы влияют на статический анализ:
Пример проблемы:
function log(target) {
console.log('class defined');
}
@log
class A {}
Для Rollup это означает потенциальный side effect при импорте модуля.
В результате:
При использовании Rollup и декораторов важно учитывать несколько принципов архитектуры сборки:
Порядок критичен:
plugins: [
typescript(),
babel(),
nodeResolve()
]
Неправильный порядок приводит к:
Старый стандарт декораторов используется в:
Особенности:
При использовании в Rollup-конвейере важно не смешивать режимы:
Иначе итоговый бандл может содержать некорректное поведение классов.
Добавление транспиляции влияет на:
Rollup сам по себе оптимизирован для быстрых графов модулей, но Babel/TypeScript становятся узким местом при:
Приводит к:
Пример:
Результат:
Декораторы часто:
Это напрямую влияет на tree-shaking и может приводить к неожиданному сохранению кода в финальном бандле.
Экспериментальные фичи ECMAScript требуют стратегии «плавающей совместимости»:
Rollup остаётся стабильным слоем, если вся нестабильность изолирована на уровне транспиляции.
Несмотря на экспериментальный статус, декораторы часто используются для:
Их интеграция в сборочный процесс требует чёткого разграничения:
При корректной настройке Rollup становится прозрачным слоем между этими уровнями, не вмешиваясь в семантику декораторов напрямую.