Изоляция модулей: isolatedModules

Параметр isolatedModules в TypeScript предназначен для проверки совместимости исходного кода с инструментами, выполняющими независимую компиляцию каждого файла отдельно. Особенно важен этот режим в экосистеме Vite, поскольку Vite использует сверхбыструю транспиляцию через esbuild, а не полноценный компилятор TypeScript во время разработки.

В tsconfig.json параметр выглядит так:

{
  "compilerOptions": {
    "isolatedModules": true
  }
}

При включении режима TypeScript начинает запрещать конструкции, которые невозможно корректно обработать без анализа всей программы целиком.


Почему isolatedModules важен для Vite

Vite ориентирован на скорость запуска и мгновенную горячую перезагрузку модулей. Для этого он:

  • не запускает полный TypeScript type-check при каждом обновлении;
  • использует esbuild для быстрой транспиляции;
  • компилирует каждый файл изолированно.

Это означает, что Vite не анализирует взаимосвязи между всеми .ts-файлами проекта перед преобразованием кода.

Компилятор TypeScript умеет выполнять глобальный анализ программы:

  • вычислять типы между файлами;
  • удалять типовые конструкции;
  • понимать, какие импорты являются типами, а какие — значениями;
  • корректно обрабатывать namespace и enum.

esbuild работает иначе:

  • каждый файл компилируется отдельно;
  • информация о соседних модулях отсутствует;
  • сложные трансформации невозможны.

Именно поэтому isolatedModules становится механизмом раннего обнаружения несовместимого кода.


Как работает изолированная компиляция

Без isolatedModules TypeScript может использовать глобальный контекст проекта.

Пример:

// types.ts
export interface User {
  name: string
}
// app.ts
import { User } from './types'

const user: User = {
  name: 'Alex'
}

Во время полноценной сборки TypeScript понимает:

  • User — это тип;
  • импорт не нужен в runtime;
  • его можно удалить из итогового JS.

Но изолированный транспилятор не способен надежно определить это во всех случаях.


Ошибка при re-export типов

Одна из наиболее распространённых проблем — повторный экспорт типов.

Проблемный код

// types.ts
export interface User {
  name: string
}
// index.ts
export { User } from './types'

При isolatedModules: true TypeScript выдаст ошибку.

Причина:

  • User существует только на уровне типов;
  • в runtime такого значения нет;
  • изолированный транспилятор не может корректно определить поведение.

Правильный вариант

export type { User } from './types'

Ключевое слово type явно сообщает:

  • экспорт относится исключительно к системе типов;
  • в итоговый JavaScript ничего попадать не должно.

Обязательное разделение type и value

В режиме isolatedModules особенно важна строгая граница между:

  • значениями runtime;
  • типами compile-time.

Неправильный импорт

import { User } from './types'

Правильный импорт

import type { User } from './types'

Такой синтаксис:

  • улучшает совместимость с Vite;
  • помогает esbuild;
  • делает код предсказуемым;
  • уменьшает вероятность ошибок транспиляции.

Поведение import type

Конструкция:

import type { User } from './types'

полностью удаляется из итогового JavaScript.

Например:

Исходный TypeScript

import type { User } from './types'

const user: User = {
  name: 'Alex'
}

Итоговый JavaScript

const user = {
  name: 'Alex'
}

Это особенно важно для:

  • tree-shaking;
  • уменьшения размера bundle;
  • устранения лишних импортов;
  • предотвращения runtime-ошибок.

Запрет namespace

isolatedModules ограничивает использование namespace.

Пример

namespace Utils {
  export const version = '1.0'
}

Причина ограничения:

  • namespace требует сложной трансформации;
  • необходим анализ нескольких частей программы;
  • изолированная компиляция плохо совместима с такой моделью.

Современный TypeScript в Vite-проектах ориентирован на ES-модули.

Рекомендуемый подход

// utils.ts
export const version = '1.0'

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

Пример

const enum Status {
  Active,
  Disabled
}

const enum требует inline-подстановки значений:

Status.Active

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

0

Для этого нужен полноценный анализ TypeScript.

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

Поэтому использование const enum в проектах Vite может вызывать проблемы.

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

enum Status {
  Active,
  Disabled
}

или:

export const Status = {
  Active: 0,
  Disabled: 1
} as const

Ограничение export =

Старый синтаксис CommonJS:

export = something

плохо совместим с современной модульной системой ES Modules.

Vite ориентирован на:

  • ESM;
  • import/export;
  • нативные браузерные модули.

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

export default something

или:

export const something = ...

Типичные ошибки при isolatedModules

Ошибка экспорта типа

export { User }

Исправление:

export type { User }

Ошибка импорта типа

import { Config } from './config'

Исправление:

import type { Config } from './config'

Ошибка namespace

namespace App {}

Исправление:

export const App = {}

Проблемы с const enum

const enum Role {
  Admin
}

Исправление:

enum Role {
  Admin
}

или:

const Role = {
  Admin: 'admin'
} as const

Связь с esbuild

esbuild — основной транспилятор Vite во время разработки.

Он:

  • не выполняет полноценную проверку типов;
  • не строит глобальный граф типов;
  • не использует сложные трансформации TypeScript.

По сути esbuild:

  • удаляет типы;
  • преобразует синтаксис;
  • быстро генерирует JavaScript.

Именно поэтому Vite рекомендует:

{
  "compilerOptions": {
    "isolatedModules": true
  }
}

Этот параметр гарантирует, что код совместим с моделью работы esbuild.


Связь с tsc --noEmit

Типичная архитектура Vite-проектов:

Проверка типов

tsc --noEmit

Транспиляция

Выполняется самим Vite через esbuild.

Это разделение обязанностей:

Инструмент Назначение
TypeScript (tsc) Проверка типов
esbuild Быстрая компиляция
Vite Dev server и HMR

isolatedModules помогает гарантировать совместимость между этими этапами.


Почему Vite-шаблоны включают isolatedModules

Стандартные шаблоны Vite для TypeScript обычно содержат:

{
  "compilerOptions": {
    "isolatedModules": true
  }
}

Это связано с несколькими причинами:

Предсказуемость сборки

Код одинаково работает:

  • в dev-режиме;
  • в build-режиме;
  • в CI;
  • в IDE.

Совместимость с быстрыми транспиляторами

Подходит для:

  • esbuild;
  • SWC;
  • Babel;
  • Bun;
  • tsx.

Устранение неоднозначности

Компилятору не приходится угадывать:

  • тип это или значение;
  • нужно ли сохранять импорт;
  • существует ли сущность в runtime.

Использование вместе с verbatimModuleSyntax

Современные проекты часто комбинируют:

{
  "compilerOptions": {
    "isolatedModules": true,
    "verbatimModuleSyntax": true
  }
}

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

TypeScript перестаёт автоматически модифицировать импорты.

Разработчик явно указывает:

import type { User } from './types'

или:

import { createApp } from './app'

Это уменьшает магию компилятора и повышает предсказуемость сборки.


Влияние на архитектуру проекта

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

Явные типовые импорты

import type { User } from './types'

Использование ES-модулей

export function init() {}

Отказ от namespace

export const utils = {}

Минимизация специальных конструкций TypeScript

  • меньше const enum;
  • меньше старых CommonJS-паттернов;
  • меньше магических трансформаций.

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

Более стабильная сборка

Код одинаково работает в:

  • Vite;
  • Vitest;
  • Bun;
  • Node ESM;
  • SWC;
  • Babel.

Упрощение миграций

Проект легче переносить между:

  • bundler;
  • runtime;
  • тестовыми системами.

Улучшение tree-shaking

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


Более понятный runtime

Разработчик точно понимает:

  • какие импорты реально существуют;
  • что попадёт в JavaScript;
  • какие сущности существуют только на этапе типов.

Пример современного tsconfig для Vite

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true,
    "noEmit": true
  }
}

Когда isolatedModules особенно необходим

Параметр критически важен при использовании:

  • Vite;
  • esbuild;
  • SWC;
  • Babel TypeScript preset;
  • Bun;
  • tsx;
  • Vitest;
  • Astro;
  • SvelteKit;
  • Nuxt с TypeScript;
  • современных ESM-сборщиков.

Во всех этих случаях код часто обрабатывается без полноценной компиляции TypeScript.


Когда проблемы проявляются чаще всего

Наиболее частые сценарии:

Barrel-файлы

export { User } from './types'

Смешивание type/value импортов

import { User, createUser } from './user'

Старый TypeScript-код

  • namespace;
  • export =;
  • const enum;
  • internal modules.

Миграция legacy-проектов

Старые проекты, написанные под tsc, часто несовместимы с изолированной транспиляцией без рефакторинга.


Безопасный современный стиль TypeScript для Vite

Импорты типов

import type { User } from './types'

Экспорты типов

export type { User }

ES Modules

export function createApp() {}

Объекты вместо const enum

export const Role = {
  Admin: 'admin',
  User: 'user'
} as const

Отказ от namespace

export const api = {}

Такой стиль максимально совместим с современной экосистемой JavaScript и архитектурой Vite.