Декораторы и экспериментальные возможности

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

Parcel как сборщик модулей не реализует декораторы самостоятельно, а делегирует их обработку соответствующим трансформерам — в первую очередь Babel или TypeScript. Это делает поддержку гибкой и зависящей от выбранного стека.


Синтаксис и модель работы декораторов

Декоратор — это функция, принимающая целевой элемент программы и изменяющая его поведение или метаданные.

Простейший пример:

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

class User {
  @readonly
  name() {
    return "Alex";
  }
}

Декоратор применяется во время определения класса, до создания его экземпляров.

Основные виды декораторов:

  • декораторы классов
  • декораторы методов
  • декораторы свойств
  • декораторы аксессоров (getter/setter)

Современное состояние стандарта

В экосистеме JavaScript существует две основные реализации:

  • legacy decorators (старый экспериментальный стандарт TypeScript и Babel)
  • stage 3 decorators (новая спецификация ECMAScript)

Они несовместимы по сигнатуре и поведению.

Legacy-версия:

function log(target, key, descriptor) {
  const original = descriptor.value;

  descriptor.value = function (...args) {
    console.log(key, args);
    return original.apply(this, args);
  };

  return descriptor;
}

Новая версия Stage 3:

function log(value, context) {
  return function (...args) {
    console.log(context.name, args);
    return value.apply(this, args);
  };
}

Разница критична для конфигурации сборки, так как требует разных трансформеров.


Роль Parcel в обработке декораторов

Parcel работает как «оркестратор» трансформаций. Он не интерпретирует декораторы, а передает файлы в соответствующие трансформеры.

Основные пути обработки:

  • @parcel/transformer-babel
  • @parcel/transformer-typescript-tsc
  • пользовательские Babel-конфигурации

Parcel автоматически определяет наличие Babel или TypeScript конфигурации и применяет соответствующий pipeline.


Настройка через Babel

Для поддержки legacy-декораторов используется Babel-плагин:

npm install --save-dev @babel/plugin-proposal-decorators

Конфигурация .babelrc или babel.config.json:

{
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }]
  ]
}

Parcel подхватывает эту конфигурацию автоматически при использовании Babel-трансформера.


Настройка TypeScript

TypeScript поддерживает декораторы на уровне компилятора.

В tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Важный момент: TypeScript по умолчанию использует legacy-реализацию. Новые decorators требуют другой стратегии и пока ограниченно поддерживаются через комбинацию TypeScript + Babel.


Интеграция Parcel с TypeScript-декораторами

Parcel использует @parcel/transformer-typescript-tsc, который вызывает tsc-совместимую трансформацию.

Особенности:

  • декораторы компилируются до этапа бандлинга
  • Parcel не интерпретирует AST декораторов
  • результатом является уже преобразованный JavaScript

Конфликты между Babel и TypeScript

Одновременное использование двух транспилеров может привести к двойной трансформации декораторов.

Типичный проблемный сценарий:

  • TypeScript преобразует декораторы в legacy-форму
  • Babel повторно пытается обработать их как stage 3

Это приводит к некорректному рантайм-коду.

Для устранения конфликта используется принцип единственного источника трансформации:

  • либо только TypeScript
  • либо только Babel

Декораторы и Parcel pipeline

Parcel строит граф зависимостей и применяет трансформеры по цепочке:

  1. чтение исходного файла
  2. определение типа ресурса (JS/TS)
  3. применение трансформера
  4. генерация AST
  5. оптимизация и минификация

Декораторы исчезают на этапе трансформации AST, превращаясь в обычные вызовы функций и обертки.


Экспериментальные возможности JavaScript в Parcel

Parcel активно поддерживает новые ECMAScript-фичи через транспиляцию, даже если они не являются частью текущего стандарта движка.

Ключевые категории:

  • stage proposals (декораторы, pipeline operator, records & tuples — частично экспериментально)
  • синтаксические расширения (top-level await, optional chaining)
  • нестандартные плагины Babel/TypeScript

Top-level await

Parcel поддерживает top-level await в ES modules без дополнительной конфигурации в современных режимах.

Пример:

const data = await fetch("/api/data").then(r => r.json());

export default data;

При сборке Parcel:

  • разбивает зависимости на async chunks
  • сохраняет корректный порядок выполнения модулей

Optional chaining и nullish coalescing

Эти фичи поддерживаются через Babel/TypeScript без дополнительных настроек.

const value = user?.profile?.name ?? "guest";

Parcel не изменяет семантику, а лишь обеспечивает корректную транспиляцию для старых сред выполнения.


Dynamic import как часть экспериментальной архитектуры

const module = await import("./feature.js");

Parcel воспринимает dynamic import как сигнал к code splitting:

  • создаёт отдельный бандл
  • связывает через async runtime loader
  • оптимизирует загрузку по требованию

Декораторы в связке с классами и DI-подходами

Часто декораторы используются в архитектурах внедрения зависимостей.

Пример упрощённого контейнера:

const registry = new Map();

function Injectable(target) {
  registry.set(target.name, new target());
}

@Injectable
class Service {}

Parcel не влияет на семантику DI, но обеспечивает корректную трансформацию синтаксиса.


Метаданные и reflect-metadata

При использовании TypeScript-опции:

{
  "emitDecoratorMetadata": true
}

добавляется зависимость от reflect-metadata:

import "reflect-metadata";

function logType(target, key) {
  const type = Reflect.getMetadata("design:type", target, key);
  console.log(type);
}

Parcel не генерирует метаданные, но обеспечивает корректную упаковку runtime-библиотеки.


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

Декораторы увеличивают нагрузку на этап сборки из-за необходимости AST-преобразований.

Факторы влияния:

  • глубина наследования классов
  • количество декораторов на сущность
  • использование metadata reflection
  • сложность Babel pipeline

Parcel компенсирует это за счёт:

  • кеширования трансформированных модулей
  • параллельной обработки файлов
  • инкрементальной сборки

Кеширование и инкрементальная сборка

Parcel сохраняет результаты трансформации декораторов в кэш:

  • AST после Babel/TypeScript
  • финальный JS output
  • зависимости модулей

При изменении исходного кода пересобирается только затронутая часть графа.


Ошибки и диагностика

Типичные ошибки при работе с декораторами в Parcel:

  • конфликт legacy и stage 3
  • отсутствие Babel-плагина
  • несовместимость TypeScript и Babel pipeline
  • неправильный порядок трансформеров

Пример ошибки:

Unexpected token @

Причина — отсутствие декоратор-трансформера в цепочке обработки.


Архитектурная роль декораторов в сборке

С точки зрения Parcel декораторы являются:

  • синтаксическим элементом AST
  • объектом трансформации
  • источником runtime-обёрток

Они не влияют на граф модулей напрямую, но изменяют структуру генерируемого кода, что может косвенно влиять на оптимизацию и tree-shaking.


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

Развитие стандарта декораторов напрямую влияет на работу сборщиков.

Ожидаемые изменения:

  • унификация stage 3 реализации
  • отказ от legacy-режима
  • упрощение Babel-плагинов
  • уменьшение роли TypeScript-специфичных флагов

Parcel адаптируется к этим изменениям через обновление трансформеров и упрощение конфигурационного слоя.