Обработка namespace и enum

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

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


Namespace в TypeScript

Namespace представляет собой механизм логической группировки сущностей внутри одного пространства имён.

Пример объявления:

namespace Geometry {
    export const PI = 3.14159;

    export function area(radius: number): number {
        return PI * radius * radius;
    }
}

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

const result = Geometry.area(10);

Namespace появился ещё до широкого распространения ES-модулей и использовался для организации кода внутри глобальной области видимости.


Принцип трансформации namespace в SWC

Поскольку JavaScript не содержит встроенной конструкции namespace, SWC преобразует её в объект и самовызывающуюся функцию.

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

namespace Utils {
    export const version = "1.0";

    export function print() {
        console.log(version);
    }
}

После компиляции получается код, близкий к следующему:

var Utils;

(function(Utils) {
    Utils.version = "1.0";

    function print() {
        console.log(Utils.version);
    }

    Utils.print = print;
})(Utils || (Utils = {}));

В процессе трансформации:

  1. Создаётся объект пространства имён.
  2. Экспортируемые сущности записываются как свойства объекта.
  3. Функции связываются с объектом namespace.
  4. Сохраняется доступ через точечную нотацию.

Экспорт внутри namespace

Члены пространства имён становятся доступными только после использования ключевого слова export.

Пример:

namespace UserSystem {
    const secret = "token";

    export const name = "Admin";

    export function getName() {
        return name;
    }
}

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

var UserSystem;

(function(UserSystem) {
    const secret = "token";

    UserSystem.name = "Admin";

    function getName() {
        return UserSystem.name;
    }

    UserSystem.getName = getName;
})(UserSystem || (UserSystem = {}));

Неэкспортируемая переменная secret остаётся внутренней и недоступна извне.


Вложенные namespace

TypeScript позволяет создавать вложенные пространства имён.

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

namespace Application {
    export namespace Services {
        export function load() {
            console.log("Loading...");
        }
    }
}

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

Application.Services.load();

SWC разворачивает такую структуру в несколько уровней объектов:

var Application;

(function(Application) {
    var Services;

    (function(Services) {
        function load() {
            console.log("Loading...");
        }

        Services.load = load;
    })(Services = Application.Services || (Application.Services = {}));
})(Application || (Application = {}));

Каждый вложенный namespace становится вложенным объектом JavaScript.


Слияние namespace

Одной из особенностей TypeScript является возможность объединения нескольких объявлений namespace.

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

namespace Config {
    export const host = "localhost";
}

namespace Config {
    export const port = 3000;
}

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

console.log(Config.host);
console.log(Config.port);

SWC сохраняет механизм слияния через повторное использование одного объекта:

var Config;

(function(Config) {
    Config.host = "localhost";
})(Config || (Config = {}));

(function(Config) {
    Config.port = 3000;
})(Config || (Config = {}));

В результате объект содержит оба свойства.


Namespace и классы

Внутри пространства имён могут находиться классы.

Пример:

namespace Models {
    export class User {
        constructor(public name: string) {}
    }
}

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

var Models;

(function(Models) {
    class User {
        constructor(name) {
            this.name = name;
        }
    }

    Models.User = User;
})(Models || (Models = {}));

Класс становится свойством объекта namespace.


Namespace и интерфейсы

Интерфейсы существуют только на этапе проверки типов.

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

namespace API {
    export interface Response {
        status: number;
    }

    export function send(): Response {
        return { status: 200 };
    }
}

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

var API;

(function(API) {
    function send() {
        return {
            status: 200
        };
    }

    API.send = send;
})(API || (API = {}));

Интерфейс полностью удаляется из итогового JavaScript-кода.


Особенности namespace в современных проектах

Хотя SWC полностью поддерживает пространства имён TypeScript, в современных приложениях предпочтение обычно отдаётся ES-модулям.

Вместо:

namespace MathUtils {
    export function sum(a: number, b: number) {
        return a + b;
    }
}

Чаще используется:

export function sum(a: number, b: number) {
    return a + b;
}

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


Enum в TypeScript

Enum представляет собой набор именованных констант.

Пример:

enum Status {
    Pending,
    Approved,
    Rejected
}

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

const current = Status.Approved;

JavaScript не содержит встроенной реализации enum, поэтому SWC создаёт соответствующую структуру объектов.


Трансформация числовых enum

Для числовых перечислений SWC генерирует двустороннее отображение.

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

enum Status {
    Pending,
    Approved,
    Rejected
}

Приблизительный результат:

var Status;

(function(Status) {
    Status[Status["Pending"] = 0] = "Pending";
    Status[Status["Approved"] = 1] = "Approved";
    Status[Status["Rejected"] = 2] = "Rejected";
})(Status || (Status = {}));

Появляются две формы доступа:

Status.Pending;

Результат:

0

И обратное отображение:

Status[0];

Результат:

"Pending"

Механизм обратного отображения

После компиляции объект имеет структуру:

{
    0: "Pending",
    1: "Approved",
    2: "Rejected",
    Pending: 0,
    Approved: 1,
    Rejected: 2
}

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

console.log(Status.Pending);
console.log(Status[0]);

Вывод:

0
Pending

Enum с явными значениями

Значения могут задаваться вручную.

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

enum HttpCode {
    Ok = 200,
    NotFound = 404,
    InternalError = 500
}

Трансформация:

var HttpCode;

(function(HttpCode) {
    HttpCode[HttpCode["Ok"] = 200] = "Ok";
    HttpCode[HttpCode["NotFound"] = 404] = "NotFound";
    HttpCode[HttpCode["InternalError"] = 500] = "InternalError";
})(HttpCode || (HttpCode = {}));

Доступ:

HttpCode.Ok;

Результат:

200

Строковые enum

Строковые перечисления работают иначе.

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

enum Role {
    Admin = "ADMIN",
    User = "USER",
    Guest = "GUEST"
}

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

var Role;

(function(Role) {
    Role["Admin"] = "ADMIN";
    Role["User"] = "USER";
    Role["Guest"] = "GUEST";
})(Role || (Role = {}));

Обратное отображение не создаётся.

Структура объекта:

{
    Admin: "ADMIN",
    User: "USER",
    Guest: "GUEST"
}

Попытка выполнить:

Role["ADMIN"];

вернёт:

undefined

Гетерогенные enum

TypeScript допускает смешивание строковых и числовых значений.

Пример:

enum Mixed {
    No = 0,
    Yes = "YES"
}

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

var Mixed;

(function(Mixed) {
    Mixed[Mixed["No"] = 0] = "No";
    Mixed["Yes"] = "YES";
})(Mixed || (Mixed = {}));

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


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

Перечисления могут использовать вычисления.

Пример:

enum FileAccess {
    None = 0,
    Read = 1 << 1,
    Write = 1 << 2,
    ReadWrite = Read | Write
}

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

var FileAccess;

(function(FileAccess) {
    FileAccess[FileAccess["None"] = 0] = "None";
    FileAccess[FileAccess["Read"] = 2] = "Read";
    FileAccess[FileAccess["Write"] = 4] = "Write";
    FileAccess[FileAccess["ReadWrite"] = 6] = "ReadWrite";
})(FileAccess || (FileAccess = {}));

После компиляции используются уже вычисленные константы.


Const enum

Для повышения производительности TypeScript предоставляет конструкцию const enum.

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

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

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

const value = Direction.Left;

При инлайнинге значение может быть подставлено напрямую:

const value = 2;

В таком случае объект перечисления вообще не создаётся.


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

Обычный enum:

Status.Pending

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

Status.Pending

Требует обращения к объекту.

const enum:

Direction.Left

После подстановки:

2

Преимущества:

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

Enum внутри namespace

Обе конструкции могут использоваться совместно.

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

namespace System {
    export enum State {
        Start,
        Stop
    }
}

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

var System;

(function(System) {
    let State;

    (function(State) {
        State[State["Start"] = 0] = "Start";
        State[State["Stop"] = 1] = "Stop";
    })(State = System.State || (System.State = {}));
})(System || (System = {}));

Использование остаётся прежним:

System.State.Start;

Удаление типовой информации

При обработке namespace и enum SWC одновременно удаляет все элементы системы типов:

namespace Users {
    export interface User {
        id: number;
    }

    export type UserId = number;

    export enum Role {
        Admin,
        User
    }
}

После компиляции остаётся только исполняемый код перечисления:

var Users;

(function(Users) {
    let Role;

    (function(Role) {
        Role[Role["Admin"] = 0] = "Admin";
        Role[Role["User"] = 1] = "User";
    })(Role = Users.Role || (Users.Role = {}));
})(Users || (Users = {}));

Интерфейсы и псевдонимы типов полностью исчезают.


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

Namespace целесообразен, когда требуется поддержка старого TypeScript-кода или совместимость с существующими архитектурными решениями.

ES-модули предпочтительнее, поскольку они лучше оптимизируются современными сборщиками и упрощают анализ зависимостей.

Числовые enum подходят в случаях, когда требуется обратное отображение значения в имя.

Строковые enum удобнее для сериализации данных, взаимодействия с API и хранения значений в базах данных.

Const enum позволяет минимизировать объём итогового кода и используется в производительных участках приложения.

SWC корректно преобразует все основные разновидности namespace и enum, сохраняя их семантику и обеспечивая высокую скорость компиляции TypeScript-проектов.