Внутреннее устройство SWC основано на работе с абстрактным синтаксическим деревом (AST), где каждый узел представляет собой конструкцию языка: выражение, оператор, объявление, модуль. Для обхода и модификации этого дерева используется паттерн Visitor, позволяющий отделить структуру данных от операций над ней и масштабировать трансформации без изменения базовых типов AST.
Visitor в SWC — это центральный механизм, через который реализуются плагины, транспиляция TypeScript, JSX-преобразования, оптимизации и код-модификации. Его роль заключается в том, чтобы последовательно посещать узлы дерева и применять к ним пользовательские или системные правила трансформации.
AST в SWC представляет собой строго типизированную иерархию:
Каждый узел содержит вложенные подузлы, формируя рекурсивную структуру. Например, выражение вызова функции включает:
Без универсального механизма обхода пришлось бы вручную рекурсивно разбирать каждый тип узла, что делает систему нерасширяемой. Visitor решает эту проблему за счёт единообразного интерфейса обхода.
Visitor в SWC реализует принцип:
Базовая модель:
Таким образом можно:
В пользовательском API SWC visitor реализуется через объект с методами, соответствующими типам узлов.
Пример базовой структуры:
import { Visitor } from "@swc/core/Visitor";
const visitor: Visitor = {
VariableDeclarator(n) {
return n;
},
FunctionDeclaration(n) {
return n;
}
};
Каждый метод получает узел AST и возвращает:
Ключевой особенностью SWC Visitor является автоматическая рекурсия. Разработчику не требуется вручную вызывать обход вложенных узлов.
Например:
const visitor: Visitor = {
Identifier(n) {
if (n.value === "debug") {
return {
...n,
value: "production"
};
}
return n;
}
};
Даже если Identifier находится глубоко внутри ExpressionStatement → CallExpression → MemberExpression, visitor будет вызван автоматически.
AST в SWC рассматривается как неизменяемая структура. Любое изменение создаёт новый узел, а не модифицирует существующий.
Это приводит к важным следствиям:
Пример замены узла:
CallEx * pression(n) {
return {
...n,
callee: {
type: "Identifier",
value: "patchedFunction",
span: n.callee.span
}
};
}
Visitor может работать в двух режимах:
Обработка узла до обработки детей:
Обработка после посещения всех потомков:
В SWC чаще используется комбинация, управляемая внутренним walker’ом.
Помимо Visitor существует более низкоуровневый API visitMut, позволяющий изменять узлы “на месте” в рамках управляемой мутации.
Пример:
import { visitMut } from "@swc/core";
visitMut(ast, {
Ex * pression(n) {
if (n.type === "UnaryExpression") {
return {
...n,
operator: "!"
};
}
return n;
}
});
Особенность:
Одним из ключевых преимуществ паттерна является возможность композиции:
const visitorA: Visitor = {
Identifier(n) {
return { ...n, value: n.value.toUpperCase() };
}
};
const visitorB: Visitor = {
Identifier(n) {
return { ...n, value: `_${n.value}` };
}
};
Комбинирование:
import { chain } from "@swc/core";
const combined = chain(visitorA, visitorB);
Порядок применения влияет на итоговый AST, что важно при построении пайплайнов трансформации.
Внутри SWC каждый узел имеет типизированную структуру. Например:
Visitor получает строго типизированный объект:
CallEx * pression(n) {
const args = n.arguments;
return {
...n,
arguments: args.map(arg => ({
...arg,
expression: arg.expression
}))
};
}
Такая модель позволяет безопасно манипулировать деревом без ручной проверки структуры на каждом уровне.
Visitor часто используется для условной модификации AST:
VariableDeclaration(n) {
if (n.kind === "var") {
return {
...n,
kind: "let"
};
}
return n;
}
Этот подход используется в:
Хотя Visitor концептуально статeless, на практике можно хранить состояние:
let counter = 0;
const visitor: Visitor = {
Identifier(n) {
counter++;
return n;
}
};
Однако более безопасный подход — передача контекста через замыкание:
function createVisitor() {
let renamed = 0;
return {
Identifier(n) {
renamed++;
return n;
}
};
}
Это позволяет:
Удаление узла в Visitor модели зависит от типа родительского контейнера.
Пример:
BlockStatement(n) {
return {
...n,
body: n.body.filter(stmt => {
return stmt.type !== "EmptyStatement";
})
};
}
Удаление обычно реализуется через:
Несмотря на универсальность, подход имеет особенности:
Visitor используется в ключевых слоях SWC:
Он выступает как фундаментальный слой, поверх которого строятся более высокоуровневые абстракции.
В архитектуре SWC Visitor часто комбинируется с:
Visitor при этом остаётся центральной точкой интеграции всех операций над AST.
export default function () {
return {
Program(program) {
return program;
},
FunctionDeclaration(fn) {
if (!fn.identifier) return fn;
return {
...fn,
identifier: {
...fn.identifier,
value: fn.identifier.value + "_patched"
}
};
},
CallEx * pression(expr) {
return expr;
}
};
}
Такая структура демонстрирует стандартный подход к расширению компилятора через декларативные AST-правила.
Внутренне SWC реализует Visitor через:
Это позволяет добиться высокой производительности при трансформации даже больших кодовых баз.