Декораторы: поддержка и версии спецификации

Декораторы в JavaScript прошли длительный путь стандартизации в рамках TC39. Изначально они появились как экспериментальная возможность, затем закрепились в экосистеме TypeScript и Babel, а позже стали частью официального предложения ECMAScript, но с существенными изменениями синтаксиса и семантики.

Ключевая особенность развития — существование нескольких несовместимых поколений спецификации, которые продолжают сосуществовать в реальных проектах:

  • legacy-декораторы (историческая реализация TypeScript и раннего Babel)
  • stage 2 / stage 3 предложения TC39 (новая модель декораторов)
  • промежуточные редакции спецификации, изменявшие порядок применения и контекст выполнения

Это привело к тому, что современные компиляторы, включая SWC, вынуждены поддерживать несколько режимов трансформации.


Legacy-декораторы: историческая модель

Legacy-декораторы — это де-факто стандарт, который долгое время использовался в TypeScript и Babel до пересмотра спецификации TC39.

Основные характеристики legacy-модели

  • Декоратор — это функция, вызываемая во время определения класса
  • Порядок применения идёт снизу вверх
  • Возможна модификация дескрипторов свойств
  • Активно используется паттерн Object.defineProperty
  • Поддерживается через reflect-metadata и emitDecoratorMetadata в TypeScript

Пример поведения:

function readonly(target, key, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

Legacy-модель тесно связана с TypeScript и его опцией:

  • experimentalDecorators: true
  • emitDecoratorMetadata: true

Ограничения legacy-подхода

  • отсутствует унифицированная работа с приватными полями
  • не соответствует текущему стандарту TC39
  • не поддерживает некоторые будущие возможности метапрограммирования
  • конфликтует с новым pipeline декораторов

Современная спецификация TC39: stage 3 декораторы

Новая спецификация декораторов (часто называемая “TC39 decorators”) радикально отличается от legacy-модели.

Ключевые изменения архитектуры

Главное отличие — разделение этапов декорирования:

  • создание дескрипторов выполняется отдельно
  • декораторы получают более формализованный контекст
  • вводится объект context, описывающий тип декорируемого элемента
  • поддерживается декорирование классов, полей, методов, аксессоров

Пример новой модели:

function logged(value, context) {
  if (context.kind === "method") {
    return function (...args) {
      console.log("call:", context.name);
      return value.apply(this, args);
    };
  }
}

Контекст декоратора

Объект context содержит:

  • kind — тип сущности (class, method, field, getter, setter)
  • name — имя метода или поля
  • access — объект доступа к значению
  • static — статический ли элемент
  • private — признак приватности

Эта модель делает декораторы более предсказуемыми и безопасными.


Различия legacy и TC39 моделей

Семантика выполнения

Legacy:

  • функция получает target, key, descriptor
  • модификация происходит через дескриптор

TC39:

  • функция получает value и context
  • возвращается новая сущность или undefined
  • управление через формализованный API

Область применения

Legacy:

  • классы
  • методы
  • аксессоры

TC39:

  • классы
  • методы
  • поля (field decorators)
  • статические и приватные элементы

Совместимость

  • legacy-декораторы не совместимы с новой моделью
  • TC39-декораторы требуют другой трансформации AST
  • смешивание режимов приводит к некорректной генерации кода

Поддержка декораторов в SWC

SWC реализует декораторы как часть трансформационного слоя jsc.transform.

Основная особенность SWC — явное управление версией спецификации через конфигурацию.


Конфигурация декораторов в SWC

Основной блок конфигурации:

{
  "jsc": {
    "transform": {
      "decoratorMetadata": true,
      "legacyDecorator": true
    }
  }
}

legacyDecorator

Опция включает поддержку legacy-модели.

  • трансформирует декораторы в стиль TypeScript/Babel
  • использует дескрипторы свойств
  • совместим с experimentalDecorators

decoratorMetadata

Включает генерацию метаданных:

  • используется вместе с reflect-metadata
  • добавляет типовую информацию
  • активно применяется в DI-фреймворках (NestJS-подобные системы)

Современные версии декораторов в SWC

SWC поддерживает выбор версии спецификации через параметр уровня трансформации.

Типичная конфигурация:

{
  "jsc": {
    "transform": {
      "decoratorVersion": "2022-03"
    }
  }
}

Возможные режимы:

legacy

  • классическая модель TypeScript
  • максимальная совместимость
  • используется по умолчанию во многих старых проектах

2022-03 (и близкие редакции)

  • переходная модель TC39
  • поддержка контекста декоратора
  • частичная совместимость с legacy-подходом через адаптеры

2023+ (актуальные редакции)

  • финализированная модель TC39
  • полная поддержка context
  • отказ от descriptor-based логики

Внутренняя трансформация декораторов в SWC

SWC выполняет преобразование через Rust-реализацию AST-проходов.

Этапы обработки

  1. Разбор синтаксиса декораторов в AST

  2. Классификация типа декоратора:

    • класс
    • метод
    • поле
    • аксессор
  3. Выбор стратегии трансформации (legacy или TC39)

  4. Генерация вспомогательных функций

  5. Инъекция runtime-хелперов


Генерация вспомогательных функций

В legacy-режиме SWC генерирует обёртки:

  • __decorate
  • __metadata
  • __param

В TC39-режиме используется более компактная модель без классических helper-цепочек, но с runtime-вызовами декораторов через контекст.


Декораторы классов в SWC

Legacy-поведение

@sealed
class Example {}

Трансформируется в:

  • вызов функции декоратора с target класса
  • возможная замена конструктора

TC39-поведение

@sealed
class Example {}

В новой модели:

  • декоратор получает value класса
  • возвращает новый класс или модифицирует существующий
  • context.kind === “class”

Декораторы методов

Legacy

  • изменение descriptor
  • контроль writable, enumerable, configurable

TC39

  • работа через context.addInitializer
  • возможность внедрения логики в момент инициализации класса

Пример:

function trace(value, context) {
  if (context.kind === "method") {
    return function (...args) {
      console.log(context.name);
      return value.apply(this, args);
    };
  }
}

Декораторы полей

Важное отличие современных декораторов — полноценная поддержка полей класса.

Legacy

  • отсутствует прямой доступ к полям
  • требуется обход через getter/setter

TC39

  • прямое декорирование поля
  • возможность изменения значения при инициализации

Ограничения и несовместимости в SWC

Конфликт версий

Если одновременно включены:

  • legacyDecorator: true
  • decoratorVersion: “2022-03” или выше

поведение становится неопределённым, поскольку модели трансформации несовместимы.


Влияние TypeScript

SWC часто используется как замена TypeScript-компилятора, но:

  • TypeScript по умолчанию использует legacy-модель
  • TC39-декораторы требуют отдельной настройки

Babel-совместимость

SWC старается сохранять совместимость с Babel:

  • legacy-режим близок к @babel/plugin-proposal-decorators
  • TC39-режим ближе к современным preset-ам Babel 8+

Производительность трансформации

Одно из ключевых преимуществ SWC:

  • декораторы обрабатываются на уровне Rust AST
  • минимальное количество промежуточных объектов
  • отсутствие интерпретируемых проходов

Это особенно заметно в проектах с большим количеством классов:

  • ORM-сущности
  • DI-контейнеры
  • UI-фреймворки с декораторами компонентов

Практическая модель выбора версии

В реальных кодовых базах выбор версии декораторов определяется:

  • возрастом проекта
  • используемым фреймворком
  • типом сборщика
  • наличием legacy-зависимостей

SWC выступает как универсальный слой, позволяющий:

  • поддерживать старые проекты без миграции
  • постепенно переходить на TC39-модель
  • смешивать конфигурации на уровне разных пакетов монорепозитория