Трансформация const enum

В языке TypeScript перечисления (enum) могут существовать в двух основных формах:

enum Status {
    Pending,
    Success,
    Error
}

и

const enum Status {
    Pending,
    Success,
    Error
}

Ключевое отличие заключается в том, что обычный enum сохраняется в результирующем JavaScript-коде в виде объекта, тогда как const enum предназначен для полного удаления во время компиляции с подстановкой значений непосредственно в места использования.

Трансформация const enum в SWC реализует именно этот механизм: обращения к элементам перечисления заменяются соответствующими литеральными значениями, а само перечисление может быть удалено из выходного кода.

Такой подход уменьшает размер бандла и устраняет дополнительные обращения к объектам перечислений во время выполнения.


Как работает обычный enum

Рассмотрим следующий код:

enum Direction {
    Up,
    Down,
    Left,
    Right
}

const dir = Direction.Left;

После компиляции TypeScript обычно генерирует объект:

var Direction;

(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));

const dir = Direction.Left;

Создается объект, который существует во время выполнения программы.

Это позволяет выполнять такие операции:

console.log(Direction.Left);
console.log(Direction[2]);

Результат:

2
Left

Поддерживается двустороннее отображение значений и имен.


Как работает const enum

Версия с const enum выглядит следующим образом:

const enum Direction {
    Up,
    Down,
    Left,
    Right
}

const dir = Direction.Left;

После трансформации SWC способен заменить обращение:

Direction.Left

на литерал:

const dir = 2;

Само перечисление больше не требуется в рантайме.

Итоговый код становится значительно проще:

const dir = 2;

Никаких объектов, функций или вспомогательных конструкций не создается.


Причины использования const enum

Сокращение размера выходного кода

Обычный enum:

enum Color {
    Red,
    Green,
    Blue
}

может породить десятки строк JavaScript.

Версия с const enum:

const enum Color {
    Red,
    Green,
    Blue
}

const color = Color.Green;

превращается в:

const color = 1;

Экономия становится особенно заметной при большом количестве перечислений.

Отсутствие объектов во время выполнения

Обычный enum:

const value = Status.Success;

требует доступа к объекту:

Status.Success

После трансформации:

1

Никаких обращений к памяти через объект не происходит.

Улучшение возможностей оптимизации

После подстановки литералов бандлеры и минификаторы получают более простой код:

if (status === 1) {
    ...
}

Вместо:

if (status === Status.Success) {
    ...
}

Это облегчает дальнейшие оптимизации.


Подстановка числовых значений

Наиболее распространенный случай:

const enum HttpCode {
    Ok = 200,
    NotFound = 404,
    ServerError = 500
}

const code = HttpCode.NotFound;

Результат:

const code = 404;

SWC заменяет ссылку на конкретное значение перечисления.


Подстановка строковых значений

Поддерживаются и строковые литералы.

Исходный код:

const enum Role {
    Admin = "admin",
    User = "user",
    Guest = "guest"
}

const role = Role.Admin;

После трансформации:

const role = "admin";

В результирующем коде отсутствует объект Role.


Использование в выражениях

Подстановка выполняется не только при присваивании.

Пример:

const enum LogLevel {
    Error = 0,
    Warning = 1,
    Info = 2
}

if (level === LogLevel.Warning) {
    handleWarning();
}

После преобразования:

if (level === 1) {
    handleWarning();
}

Все обращения заменяются литералами.


Вычисляемые значения

Перечисления могут содержать вычисляемые элементы.

const enum Flags {
    Read = 1,
    Write = 2,
    Execute = 4,
    ReadWrite = Read | Write
}

SWC способен вычислить:

Flags.ReadWrite

как:

3

Пример:

const permissions = Flags.ReadWrite;

Результат:

const permissions = 3;

Побитовые флаги

Одно из наиболее популярных применений const enum — работа с битовыми масками.

const enum Permission {
    Read = 1 << 0,
    Write = 1 << 1,
    Delete = 1 << 2
}

Использование:

const userPermissions =
    Permission.Read |
    Permission.Write;

После трансформации:

const userPermissions = 1 | 2;

А затем минификатор может дополнительно упростить выражение:

const userPermissions = 3;

Трансформация внутри функций

Исходный код:

const enum State {
    Idle,
    Loading,
    Success,
    Error
}

function process(state: State) {
    if (state === State.Loading) {
        return "loading";
    }

    return "done";
}

После обработки:

function process(state) {
    if (state === 1) {
        return "loading";
    }

    return "done";
}

Все ссылки заменяются на литералы.


Трансформация в switch

Пример:

const enum Action {
    Create,
    Update,
    Delete
}

switch (action) {
    case Action.Create:
        create();
        break;

    case Action.Update:
        update();
        break;
}

После преобразования:

switch (action) {
    case 0:
        create();
        break;

    case 1:
        update();
        break;
}

Это делает конструкцию максимально компактной.


Вложенные вычисления

const enum MathConstants {
    A = 10,
    B = 20,
    Sum = A + B
}

Использование:

const value = MathConstants.Sum;

После трансформации:

const value = 30;

SWC вычисляет константное выражение заранее.


Ограничения const enum

Несмотря на преимущества, существуют ограничения.

Невозможность получить объект перечисления

Код:

const enum Status {
    Active,
    Disabled
}

console.log(Status);

Невозможно корректно реализовать после удаления перечисления.

Поэтому подобные сценарии несовместимы с идеологией const enum.


Невозможность перебора значений

Для обычного enum допустимо:

for (const key in Status) {
    console.log(key);
}

Поскольку объект существует.

Для const enum объекта нет:

Status

не существует в рантайме.


Отсутствие обратного отображения

Обычный enum:

Status[0];

дает:

Active

После инлайнинга значения подобная операция невозможна.


Пример полной трансформации

Исходный TypeScript:

const enum UserRole {
    Guest = 0,
    User = 1,
    Moderator = 2,
    Admin = 3
}

function canDelete(role: UserRole) {
    return role >= UserRole.Moderator;
}

const result = canDelete(UserRole.Admin);

После работы SWC:

function canDelete(role) {
    return role >= 2;
}

const result = canDelete(3);

Все ссылки на перечисление были заменены константами.


Настройка SWC для обработки const enum

Типичная конфигурация включает парсер TypeScript:

{
  "jsc": {
    "parser": {
      "syntax": "typescript"
    }
  }
}

При обработке TypeScript-кода SWC анализирует объявления const enum и выполняет соответствующую подстановку значений.

Пример запуска через CLI:

swc src -d dist

Во время компиляции обращения к элементам перечисления заменяются литеральными значениями в результирующем JavaScript.


Сравнение enum и const enum

Характеристика enum const enum
Создается объект в JS Да Нет
Доступен во время выполнения Да Нет
Подстановка литералов Нет Да
Обратное отображение значений Да Нет
Минимальный размер кода Нет Да
Подходит для битовых масок Да Да
Производительность доступа Ниже Выше

Влияние на производительность

Рассмотрим код:

enum Level {
    Debug,
    Info,
    Error
}

if (level === Level.Error) {
    log();
}

После компиляции:

if (level === Level.Error) {
    log();
}

Каждое обращение требует чтения свойства объекта.

Версия с const enum:

const enum Level {
    Debug,
    Info,
    Error
}

Преобразуется в:

if (level === 2) {
    log();
}

Интерпретатор работает напрямую с литералом.

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


Практические сценарии использования

Коды ошибок

const enum ErrorCode {
    Validation = 100,
    Authentication = 200,
    Database = 300
}

Роли пользователей

const enum Role {
    Guest,
    User,
    Admin
}

Состояния приложения

const enum AppState {
    Initial,
    Loading,
    Ready,
    Failed
}

Типы событий

const enum EventType {
    Click,
    Focus,
    Blur
}

Побитовые разрешения

const enum Permission {
    Read = 1,
    Write = 2,
    Execute = 4
}

Во всех перечисленных случаях объект перечисления обычно не требуется во время выполнения, поэтому инлайнинг значений через SWC обеспечивает наиболее компактный и эффективный результат.