Декораторы: версия 2022-03 и legacy
Декораторы в SWC реализуются как трансформационный слой, который переписывает синтаксис функций-декораторов в эквивалентный JavaScript-код. SWC поддерживает две несовместимые модели: legacy-декораторы (историческая реализация TypeScript и ранних транспайлеров) и стандартную спецификацию ECMAScript Decorators 2022-03, которая существенно меняет семантику, порядок выполнения и форму API.
## Архитектура обработки декораторов в SWC
Внутри SWC декораторы обрабатываются на этапе AST-трансформации. Парсер формирует дерево, в котором декораторы представлены как отдельные узлы, привязанные к классам, методам, полям и параметрам. Далее трансформер выбирает стратегию преобразования в зависимости от конфигурации:
* `legacy: true` — используется модель TypeScript legacy decorators
* `legacy: false` — используется спецификация TC39 (2022-03 и новее)
* `decoratorMetadata: true` — добавление метаданных типов (совместимость с reflect-metadata)
Конфигурация SWC обычно задаётся через `.swcrc`:
```json
{
"jsc": {
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
}
}
}
```
или в более новых версиях:
```json
{
"jsc": {
"transform": {
"legacyDecorator": false
}
}
}
```
Разница между режимами не ограничивается синтаксисом — меняется сама модель исполнения.
---
## Legacy-декораторы
Legacy-модель основана на поведении TypeScript до стандартизации ECMAScript. Декораторы представляют собой функции, которые вызываются с заранее определёнными аргументами в момент определения класса.
### Форма декоратора
Для метода:
```ts
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Call: ${key}`);
return original.apply(this, args);
};
}
```
Использование:
```ts
class UserService {
@log
getUser(id: number) {
return { id };
}
}
```
### Семантика выполнения
Legacy-декораторы:
* применяются сверху вниз
* получают `target`, `key`, `descriptor`
* могут мутировать descriptor напрямую
* работают через `Object.defineProperty`
* поддерживают `emitDecoratorMetadata`
Ключевая особенность — изменение существующего дескриптора свойства. Это делает legacy-декораторы тесно связанными с `Object.defineProperty` и прототипной моделью JavaScript.
### Метаданные
При включении `emitDecoratorMetadata` SWC добавляет вызовы `Reflect.metadata`:
```ts
import "reflect-metadata";
class Example {
constructor(private service: Service) {}
}
```
После трансформации появляется:
```js
__metadata("design:paramtypes", [Service])
```
Это используется DI-контейнерами и фреймворками, но не является частью стандарта ECMAScript.
---
## Декораторы 2022-03 (ECMAScript Proposal)
Новая модель декораторов, зафиксированная в спецификации 2022-03, полностью пересматривает механизм. Основное изменение — переход от мутации descriptor к декларативному описанию поведения через context-объект.
### Форма декоратора
```ts
function log(value, context) {
if (context.kind === "method") {
return function (...args) {
console.log(`Call: ${context.name}`);
return value.apply(this, args);
};
}
}
```
Использование:
```ts
class UserService {
@log
getUser(id) {
return { id };
}
}
```
### Контекст декоратора
Вместо `target/key/descriptor` используется объект `context`, который содержит:
* `kind` — тип элемента (`class`, `method`, `field`, `getter`, `setter`, `accessor`)
* `name` — имя свойства
* `access` — доступ к оригинальному значению (в некоторых режимах)
* `private` — флаг приватности
* `addInitializer` — регистрация инициализаторов
* `metadata` — пространство для метаданных
---
## Ключевые отличия моделей
### 1. Момент и способ модификации
Legacy:
* модификация через `PropertyDescriptor`
* прямое изменение поведения метода
* патчинг объекта
2022-03:
* возврат новой функции или descriptor-like структуры
* явное управление через return
* возможность не мутировать исходный объект
---
### 2. Порядок применения
Legacy:
* снизу вверх при объявлении
* сверху вниз при вызове обёрток
2022-03:
* строгий порядок применения: сначала поля, затем методы, затем классы
* разделение инициализации и определения
---
### 3. Поля класса
Legacy:
* нестабильная поддержка
* часто реализуется через `Object.defineProperty` в конструкторе
2022-03:
* полноценная поддержка class fields
* возможность использовать `addInitializer`
Пример:
```ts
function initLogger(value, context) {
if (context.kind === "field") {
return function (initialValue) {
console.log(`Init field ${context.name}`);
return initialValue;
};
}
}
```
---
### 4. Инициализация (initializers)
Одно из ключевых различий SWC при переключении режимов.
2022-03 вводит механизм:
```ts
context.addInitializer(() => {
// выполняется при создании экземпляра
});
```
Это позволяет:
* регистрировать side effects без изменения конструктора
* избегать ручного патчинга prototype
* работать в композиции декораторов
Legacy не имеет эквивалента — любые побочные эффекты реализуются вручную через обёртки конструктора.
---
## Поведение SWC при трансформации legacy-декораторов
SWC в legacy-режиме генерирует вспомогательные функции:
* `__decorate`
* `__metadata`
* `__param`
Пример трансформации:
```ts
class A {
@log
method() {}
}
```
становится:
```js
class A {
method() {}
}
__decorate([
log
], A.prototype, "method", null);
```
Это отражает внешний, «пост-компиляционный» характер применения декораторов.
---
## Поведение SWC при трансформации 2022-03
В новом режиме SWC избегает глобальных helper-функций в стиле TypeScript legacy. Вместо этого генерируется код, который:
* создаёт функции-декораторы с контекстом
* применяет их во время определения класса
* использует локальные вспомогательные обвязки
Принципиально меняется модель: декораторы становятся частью декларативной инициализации класса, а не пост-обработкой.
---
## Совместимость и конфликт режимов
Legacy и 2022-03 несовместимы на уровне API.
### Конфликтные зоны:
* сигнатура декоратора
* наличие `context`
* return-значения
* работа с полями класса
* порядок выполнения
Попытка смешивания приводит к ошибкам трансформации или некорректному runtime-поведению.
---
## SWC и TypeScript interoperability
SWC часто используется как drop-in замена TypeScript компилятора, поэтому поддержка двух моделей декораторов критична.
### Типичный сценарий TS legacy:
```json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
```
### Эквивалент SWC:
```json
{
"jsc": {
"parser": {
"syntax": "typescript",
"decorators": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
}
}
}
```
---
## Практическое различие поведения в runtime
### Legacy:
* декоратор выполняется сразу при определении класса
* влияет на prototype
* легко перехватывает методы
### 2022-03:
* разделение definition-time и initialization-time
* возможность откладывать эффекты
* более строгая изоляция контекста
---
## Причины перехода на 2022-03 модель
Основные проблемы legacy:
* отсутствие стандартизации
* сложная композиция декораторов
* неочевидный порядок выполнения
* сильная зависимость от `Object.defineProperty`
Новая модель решает это через:
* унифицированный context API
* детерминированный порядок
* поддержку полей и классов на уровне спецификации
* возможность безопасной композиции
---
## Особенности реализации SWC
SWC оптимизирует обе модели:
* минимизирует runtime helper-ов
* инлайнит простые декораторы
* избегает лишних обёрток при отсутствии side effects
* различает pure и impure декораторы при трансформации
Особенно важно, что в 2022-03 режиме SWC может сохранять больше исходной структуры AST, снижая объем генерируемого кода.
---
## Типичные ошибки при миграции
* использование `target, key, descriptor` в новом режиме
* ожидание автоматического `reflect-metadata` поведения
* попытка изменять descriptor в 2022-03
* смешивание `legacyDecorator: true` и нового API
Такие ошибки часто проявляются не на этапе компиляции, а только в runtime, что усложняет диагностику.