В экосистеме Babel ключевым элементом расширяемости выступают плагины, реализующие трансформацию кода на уровне абстрактного синтаксического дерева (AST). Babel не является монолитным компилятором: его поведение полностью определяется набором подключённых плагинов и пресетов.
Основной принцип: Babel преобразует исходный JavaScript-код в AST, после чего последовательно применяет плагины, изменяющие дерево, и затем генерирует итоговый код обратно из AST.
Перед тем как код начинает обрабатываться плагинами, он проходит этап парсинга. Результатом является AST — структурированное представление программы.
Ключевые свойства AST в Babel:
Пример кода:
const sum = (a, b) => a + b;
После парсинга формируется структура, в которой:
Плагин Babel — это функция, возвращающая объект с методами обхода AST. Основной механизм основан на паттерне Visitor.
module.exports = function () {
return {
visitor: {
Identifier(path) {
if (path.node.name === "debug") {
path.node.name = "production";
}
}
}
};
};
Ключевой механизм:
Path — центральная абстракция Babel-плагинов. Он содержит:
path.node // текущий AST-узел
path.parent // родительский узел
path.remove() // удаление узла
path.replaceWith() // замена узла
path.traverse() // обход вложенных узлов
BinaryEx * pression(path) {
if (path.node.operator === "+") {
path.node.operator = "-";
}
}
Visitor определяет стратегию обхода AST. Каждый ключ объекта visitor соответствует типу узла.
visitor: {
FunctionDeclaration(path) {},
CallEx * pression(path) {},
StringLiteral(path) {}
}
visitor: {
Identifier: {
enter(path) {},
exit(path) {}
}
}
StringLiteral(path) {
path.replaceWith(
t.stringLiteral("заменённая строка")
);
}
IfStatement(path) {
if (path.node.test.value === false) {
path.remove();
}
}
FunctionDeclaration(path) {
path.insertBefore(
t.expressionStatement(
t.stringLiteral("лог перед функцией")
)
);
}
Для генерации новых элементов AST используется модуль @babel/types.
Примеры:
t.identifier("x")
t.numericLiteral(42)
t.binaryEx * pression("+", left, right)
Каждый узел должен строго соответствовать спецификации AST.
Плагины объединяются в пресеты.
Пример задач:
presets: [
"@babel/preset-env",
"@babel/preset-react"
]
Babel выполняет плагины в строго определённом порядке:
Внутри каждого плагина порядок visitor-методов также детерминирован.
Синтаксические плагины добавляют поддержку новых возможностей языка без трансформации.
Примеры:
Они лишь расширяют парсер, но не изменяют код.
Основной тип плагинов — трансформационные.
module.exports = function () {
return {
visitor: {
CallEx * pression(path) {
if (
path.node.callee.object?.name === "console" &&
path.node.callee.property?.name === "log"
) {
path.remove();
}
}
}
};
};
Плагин может хранить состояние через state.
module.exports = function () {
return {
visitor: {
Program: {
enter(path, state) {
state.count = 0;
}
},
Identifier(path, state) {
state.count++;
}
}
};
};
State используется для:
Babel предоставляет API для анализа области видимости:
path.scope
Возможности:
Identifier(path) {
if (path.scope.hasBinding("x")) {
// переменная x уже объявлена
}
}
Scope содержит информацию о hoisting:
Плагины используют это для безопасных трансформаций.
После всех трансформаций Babel выполняет генерацию:
Генерация осуществляется модулем babel-generator.
Пресеты позволяют скрыть сложность конфигурации плагинов.
Пример:
presets: [
["@babel/preset-env", {
targets: "> 0.25%, not dead"
}]
]
Типичный pipeline:
Каждый этап строго детерминирован.
Плагины Babel не должны:
Корректная работа требует строго следовать модели AST.
Основные факторы влияния:
Оптимизация достигается за счёт: