В языке TypeScript перечисления (enum) могут существовать в
двух основных формах:
enum Status {
Pending,
Success,
Error
}
и
const enum Status {
Pending,
Success,
Error
}
Ключевое отличие заключается в том, что обычный enum
сохраняется в результирующем JavaScript-коде в виде объекта, тогда как
const enum предназначен для полного удаления во время
компиляции с подстановкой значений непосредственно в места
использования.
Трансформация const enum в SWC реализует именно этот
механизм: обращения к элементам перечисления заменяются соответствующими
литеральными значениями, а само перечисление может быть удалено из
выходного кода.
Такой подход уменьшает размер бандла и устраняет дополнительные обращения к объектам перечислений во время выполнения.
Рассмотрим следующий код:
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 Direction {
Up,
Down,
Left,
Right
}
const dir = Direction.Left;
После трансформации SWC способен заменить обращение:
Direction.Left
на литерал:
const dir = 2;
Само перечисление больше не требуется в рантайме.
Итоговый код становится значительно проще:
const dir = 2;
Никаких объектов, функций или вспомогательных конструкций не создается.
Обычный 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";
}
Все ссылки заменяются на литералы.
Пример:
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 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);
Все ссылки на перечисление были заменены константами.
Типичная конфигурация включает парсер TypeScript:
{
"jsc": {
"parser": {
"syntax": "typescript"
}
}
}
При обработке TypeScript-кода SWC анализирует объявления const
enum и выполняет соответствующую подстановку значений.
Пример запуска через CLI:
swc src -d dist
Во время компиляции обращения к элементам перечисления заменяются литеральными значениями в результирующем JavaScript.
| Характеристика | 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 обеспечивает наиболее компактный и эффективный результат.