ДокументацияTypeScriptМодули и namespaces в TypeScript
Средний 10 мин чтения

Модули и namespaces в TypeScript

Организация кода в TypeScript — ES-модули (import/export), namespaces, barrel-экспорты, паттерны реэкспорта и структура проекта.

typescriptmodulesnamespacesimportexportbarrel

ES-модули в TypeScript

TypeScript использует тот же синтаксис модулей, что и JavaScript (ES2015+). Любой файл с import или export считается модулем.

Named exports

// utils.ts
export function formatDate(date: Date): string {
  return date.toLocaleDateString('ru-RU')
}

export function capitalize(str: string): string {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

export const PI = 3.14
// main.ts
import { formatDate, capitalize } from './utils'

formatDate(new Date())
capitalize('hello')

Можно импортировать с псевдонимом:

import { formatDate as fmtDate } from './utils'

Default export

// config.ts
export default {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
}
import config from './config'
config.apiUrl

Re-export

Перенаправление экспортов из другого модуля:

export { formatDate, capitalize } from './utils'
export { default as config } from './config'

Re-export с переименованием:

export { formatDate as formatDateString } from './utils'

Barrel-экспорты

Barrel-файл (обычно index.ts) реэкспортирует всё из модулей директории:

components/
  Button.ts
  Input.ts
  Modal.ts
  index.ts    ← barrel
// components/index.ts
export { Button } from './Button'
export { Input } from './Input'
export { Modal } from './Modal'
// Можно импортировать из директории
import { Button, Input } from './components'

Экспорт всего через *:

export * from './Button'
export * from './Input'
export * from './Modal'

Осторожно: export * может создать конфликты имён, если несколько модулей экспортируют одно и то же.

Типы в модулях

TypeScript позволяет экспортировать и импортировать типы:

// types.ts
export interface User {
  id: number
  name: string
  email: string
}

export type UserRole = 'admin' | 'editor' | 'viewer'
import type { User, UserRole } from './types'

import type гарантирует, что импорт будет удалён при компиляции и не попадёт в JavaScript-бандл:

import { type User, formatDate } from './utils'
//     ^^^^^^^^^^^ только тип, удалится
//                  ^^^^^^^^^^^ останется в бандле

Namespaces

Namespaces — способ организации кода, который появился в TypeScript до того, как ES-модули стали стандартом. Сейчас они используются реже, но встречаются в старых проектах и при написании определений типов (.d.ts).

// validation.ts
namespace Validation {
  export interface StringValidator {
    isValid(s: string): boolean
  }

  export class EmailValidator implements StringValidator {
    isValid(s: string): boolean {
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s)
    }
  }

  export class PhoneValidator implements StringValidator {
    isValid(s: string): boolean {
      return /^\+?\d{10,15}$/.test(s)
    }
  }
}

const emailValidator = new Validation.EmailValidator()
emailValidator.isValid('test@mail.ru')

Вложенные namespaces

namespace App {
  export namespace Models {
    export interface User {
      id: number
      name: string
    }
  }

  export namespace Services {
    export function getUser(id: number): Models.User {
      return { id, name: 'Анна' }
    }
  }
}

const user = App.Services.getUser(1)

Когда использовать namespaces

СитуацияРекомендация
Новый проектES-модули
Определения типов (.d.ts)Namespaces или модули
Глобальная утилитаNamespace с declare global
Организация бизнес-логикиES-модули

Файловые модули vs глобальные

Файл без import/export — глобальный скрипт. Переменные и типы, объявленные в нём, видны всем:

// global.d.ts
declare global {
  interface Window {
    analytics: {
      track(event: string, data?: Record<string, unknown>): void
    }
  }
}

export {}

export {} в конце превращает файл в модуль, но позволяет объявлять глобальные типы через declare global.

Разделение типов и реализации

Популярный паттерн — вынести все интерфейсы и типы в отдельный файл:

features/
  users/
    types.ts        ← только типы
    api.ts          ← функции для работы с API
    hooks.ts        ← composables / hooks
    index.ts        ← barrel
// features/users/types.ts
export interface User {
  id: number
  name: string
  email: string
  role: UserRole
}

export type UserRole = 'admin' | 'editor' | 'viewer'

export interface CreateUserPayload {
  name: string
  email: string
  password: string
}

export type UpdateUserPayload = Partial<Omit<User, 'id'>>
// features/users/api.ts
import type { User, CreateUserPayload, UpdateUserPayload } from './types'

export async function fetchUsers(): Promise<User[]> {
  const res = await fetch('/api/users')
  return res.json()
}

export async function createUser(data: CreateUserPayload): Promise<User> {
  const res = await fetch('/api/users', {
    method: 'POST',
    body: JSON.stringify(data),
  })
  return res.json()
}

Path aliases

В tsconfig.json можно настроить сокращённые пути:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"]
    }
  }
}
import { Button } from '@components/Button'
import { formatDate } from '@utils/format'

В Vite-проектах нужно продублировать алиасы в vite.config.ts:

import { defineConfig } from 'vite'
import path from 'path'

export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
})

В Nuxt алиас ~/ и @/ работают из коробки.

Итог

В новых проектах используйте ES-модули — это стандарт JavaScript. Namespaces нужны в основном для .d.ts-файлов и старого кода. Barrel-экспорты через index.ts помогают упростить импорты. import type гарантирует, что типы не попадут в бандл. Path aliases сокращают пути, но требуют настройки и в tsconfig.json, и в бандлере.