TS модули и пространства имён

Модули в TypeScript — файлы с import/export (стандарт ES modules), разделяющие область видимости; пространства имён (namespace) — устаревший способ группировки кода внутри одного файла, заменённый модулями.

Зачем нужно

  • ES modules — единственный стандартный способ изоляции кода в современном JavaScript/TypeScript
  • Правильная настройка module и moduleResolution в tsconfig критична для работы с bundler и Node.js
  • Понимание re-export, barrel-файлов и path aliases необходимо для организации больших кодовых баз

Где используется

  • Любой TypeScript-проект — вся кодовая база состоит из модулей
  • Библиотеки — публичный API через barrel-файлы (index.ts)
  • Monorepo — cross-package импорты через path aliases
  • Декларационные файлы .d.ts — типы для JS-библиотек

Основной контент

Экспорт и импорт (ES modules)

// math.ts — named exports
export function add(a: number, b: number): number { return a + b; }
export function multiply(a: number, b: number): number { return a * b; }
export const PI = 3.14159;

// Можно экспортировать через отдельный export
function divide(a: number, b: number): number { return a / b; }
export { divide };

// main.ts — named imports
import { add, PI } from "./math";
import { divide as div } from "./math"; // переименование
import * as Math from "./math";        // namespace import

Math.add(1, 2);
Math.PI;

Default export

// user-service.ts
export default class UserService {
  getUser(id: number): Promise<User> { /* ... */ }
}

// Или функция
export default function createStore() { /* ... */ }

// Импорт — имя может быть любым
import UserService from "./user-service";
import createStore from "./store";

// Default + named в одном файле (разрешено, но не рекомендуется)
export default UserService;
export type { UserConfig };

Barrel-файлы (index.ts)

// features/auth/index.ts — публичный API модуля
export { AuthService } from "./auth-service";
export { LoginForm } from "./login-form";
export type { AuthState, AuthConfig } from "./types";

// Теперь импорт короче
import { AuthService, LoginForm } from "@/features/auth";
// вместо
import { AuthService } from "@/features/auth/auth-service";
import { LoginForm } from "@/features/auth/login-form";

Path aliases в tsconfig

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@api/*": ["src/api/*"]
    }
  }
}
// Вместо относительных путей
import { Button } from "../../../components/Button";

// Через алиас
import { Button } from "@components/Button";

Настройка module в tsconfig

{
  "compilerOptions": {
    "module": "ESNext",          // для bundler (Vite, webpack)
    "moduleResolution": "bundler", // для современных bundler
    // ИЛИ
    "module": "NodeNext",        // для Node.js ESM
    "moduleResolution": "NodeNext"
  }
}

namespace (устаревший подход)

// Использовался до ES modules — сейчас не рекомендуется
namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }

  export class EmailValidator implements StringValidator {
    isAcceptable(s: string): boolean {
      return /\S+@\S+\.\S+/.test(s);
    }
  }
}

const validator = new Validation.EmailValidator;

// Современный эквивалент — обычный модуль validation.ts

Типы для сторонних JS-библиотек

// Создание .d.ts для JS-библиотеки без типов
// custom-lib.d.ts
declare module "custom-lib" {
  export function process(data: string): string;
  export interface Options {
    timeout: number;
  }
}

// Расширение существующих типов (declaration merging)
declare module "express" {
  interface Request {
    user?: User; // добавляем кастомное поле
  }
}

Частые ошибки

  • Смешивать CommonJS (require) и ESM (import) — TypeScript поддерживает оба, но нельзя мешать в одном файле без esModuleInterop
  • Circular imports — файл A импортирует из B, B импортирует из A; обычно решается выносом общего кода в C
  • Неверный moduleResolution"node" не понимает exports field в package.json; для современных проектов нужен "bundler" или "NodeNext"
  • Barrel-файлы с re-export всегоexport * from "./module" в каждом файле нарушает tree-shaking
  • Использовать namespace в новом коде — предпочтительны ES modules; namespace оставить только для declaration merging

Связанные темы

Ресурсы