Компиляция

Процесс преобразования TypeScript-кода в JavaScript с проверкой типов, генерацией source maps и declaration-файлов.

Зачем нужно

  • Браузеры и Node.js не понимают TypeScript напрямую — нужен JavaScript
  • Компилятор tsc проверяет типы и ловит ошибки до запуска кода
  • Source maps связывают скомпилированный JS с исходным TS для отладки
  • Declaration files (.d.ts) позволяют другим проектам использовать типы вашей библиотеки

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

  • В каждом TypeScript-проекте на этапе сборки
  • CI/CD пайплайны запускают tsc --noEmit для проверки типов
  • Публикация npm-пакетов: генерация .js + .d.ts
  • Разработка: watch mode для автоматической перекомпиляции

Предпосылки

Базовая компиляция с tsc

Компиляция одного файла

# Компилирует app.ts → app.js
npx tsc app.ts

# С указанием целевой версии JS
npx tsc --target ES2020 app.ts

# Несколько файлов
npx tsc app.ts utils.ts helpers.ts

Компиляция проекта (через tsconfig.json)

# Компилирует всё по настройкам из tsconfig.json
npx tsc

# Указать конкретный конфиг
npx tsc --project tsconfig.production.json()
npx tsc -p tsconfig.production.json()

Проверка типов без генерации файлов

# Только проверка — файлы не создаются
npx tsc --noEmit

# Полезно для CI/CD и pre-commit хуков

Watch Mode — автоматическая перекомпиляция

# Следит за изменениями и перекомпилирует
npx tsc --watch
npx tsc -w

# С конкретным конфигом
npx tsc -w -p tsconfig.json

Watch mode отслеживает:

  • Изменения в .ts/.tsx файлах
  • Добавление и удаление файлов
  • Изменения в tsconfig.json
# Пример вывода watch mode
[12:00:00] Starting compilation in watch mode...
[12:00:01] Found 0 errors. Watching for file changes.

# После изменения файла:
[12:00:15] File change detected. Starting incremental compilation...
[12:00:15] Found 0 errors. Watching for file changes.

Оптимизированный watch

// tsconfig.json
{
  "watchOptions": {
    // Стратегия наблюдения за файлами
    "watchFile": "useFsEvents",         // Использовать события FS (быстрее)
    "watchDirectory": "useFsEvents",     // Для директорий тоже

    // Отложенная компиляция для пакетных изменений
    "fallbackPolling": "dynamicPriority",

    // Исключить ненужные директории из наблюдения
    "excludeDirectories": ["node_modules", "dist"]
  }
}

Source Maps

Source maps связывают скомпилированный JavaScript с исходным TypeScript. Это позволяет отлаживать TS-код прямо в браузере или в отладчике Node.js.

Включение source maps

// tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true  // Генерирует .js.map файлы
  }
}
src/
  index.ts
dist/
  index.js         ← скомпилированный JS
  index.js.map     ← source map (связь JS → TS)

Inline source maps

{
  "compilerOptions": {
    // Source map встраивается прямо в JS-файл (base64)
    "inlineSourceMap": true,

    // Исходный TS-код тоже встраивается в map
    "inlineSources": true
  }
}

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

# Node.js автоматически подхватывает .map файлы
node --enable-source-maps dist/index.js

# В браузере source maps подхватываются DevTools автоматически
// src/index.ts
function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error("Division by zero"); // Строка 3 в TS
  }
  return a / b;
}

divide(10, 0);
// Стек ошибки покажет src/index.ts:3, а не dist/index.js:5

Declaration Files (.d.ts)

Declaration files содержат только описания типов без реализации. Они нужны чтобы TypeScript мог проверять типы при использовании вашего кода.

Генерация declaration files

// tsconfig.json
{
  "compilerOptions": {
    "declaration": true,       // Генерировать .d.ts
    "declarationDir": "./types", // Куда складывать (опционально)
    "declarationMap": true     // Связь .d.ts → .ts для Go to Definition
  }
}

Пример генерации

// src/math.ts (исходник)
export function add(a: number, b: number): number {
  return a + b;
}

export interface MathResult {
  value: number;
  operation: string;
}
// dist/math.d.ts (сгенерированный declaration file)
export declare function add(a: number, b: number): number;

export interface MathResult {
  value: number;
  operation: string;
}

Только declaration (без JS)

{
  "compilerOptions": {
    "declaration": true,
    "emitDeclarationOnly": true  // Только .d.ts, без .js
  }
}

Это полезно когда JS генерируется другим инструментом (esbuild, Babel, SWC), а tsc используется только для типов.

Инкрементальная компиляция

Инкрементальная компиляция сохраняет информацию о предыдущей сборке и при следующем запуске перекомпилирует только изменённые файлы.

// tsconfig.json
{
  "compilerOptions": {
    "incremental": true,           // Включить инкрементальную компиляцию
    "tsBuildInfoFile": "./.tsbuildinfo"  // Файл с информацией о сборке
  }
}
# Первая компиляция — полная (создаёт .tsbuildinfo)
npx tsc
# Build time: 5.2s

# Вторая компиляция — инкрементальная (перекомпилирует только изменённое)
npx tsc
# Build time: 0.8s

Composite (для project references)

{
  "compilerOptions": {
    "composite": true  // Включает incremental + declaration + ещё проверки
  }
}
# Собирает проект с учётом зависимостей между подпроектами
npx tsc --build
npx tsc -b

# Очистка артефактов
npx tsc -b --clean

# Force перекомпиляция
npx tsc -b --force

Этапы компиляции TypeScript

1. Parsing        → AST (Abstract Syntax Tree)
2. Binding        → Связывание символов (переменные, функции)
3. Type Checking  → Проверка типов (основная работа!)
4. Emit           → Генерация .js, .d.ts, .map файлов
// Стадия 1: Parsing — создание AST
const x: number = "hello";
// AST: VariableDeclaration -> TypeAnnotation(number) -> StringLiteral("hello")

// Стадия 3: Type Checking — обнаружение ошибки
// Error: Type 'string' is not assignable to type 'number'

// Стадия 4: Emit — (не выполнится из-за ошибки, или выполнится с noEmitOnError: false)

Контроль emit при ошибках

{
  "compilerOptions": {
    // Не генерировать JS если есть ошибки типов (по умолчанию false!)
    "noEmitOnError": true
  }
}

Альтернативные инструменты компиляции

tsc — не единственный способ компилировать TypeScript:

Инструмент Скорость Проверка типов Использование
tsc Средняя Да Стандартный компилятор
esbuild Очень быстрая Нет Бандлинг, dev-сервер
SWC Очень быстрая Нет Next.js, Parcel
Babel Быстрая Нет Webpack, legacy
tsx Быстрая Нет Запуск TS в Node.js
ts-node Средняя Да (опционально) Запуск TS в Node.js

Типичный pipeline

# Проверка типов — tsc
npx tsc --noEmit

# Компиляция/бандлинг — быстрый инструмент
npx esbuild src/index.ts --bundle --outfile=dist/index.js
// package.json
{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "build": "esbuild src/index.ts --bundle --outdir=dist --platform=node",
    "dev": "tsx watch src/index.ts",
    "start": "node dist/index.js"
  }
}

Полезные флаги CLI

# Показать все файлы, которые tsc собирается компилировать
npx tsc --listFiles

# Показать время каждого этапа компиляции
npx tsc --diagnostics

# Расширенная диагностика
npx tsc --extendedDiagnostics

# Показать итоговую конфигурацию (с учётом extends и defaults)
npx tsc --showConfig

# Вывести список поддерживаемых опций
npx tsc --help
npx tsc --all  # все опции

# Сгенерировать trace для анализа производительности
npx tsc --generateTrace ./trace

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

  1. Забыть noEmitOnError: true — tsc генерирует JS даже при ошибках типов
  2. Не использовать incremental — каждая компиляция полная и медленная
  3. Watch mode не видит новые файлы — перезапустите tsc -w или проверьте include
  4. Source maps не работают — убедитесь что "sourceMap": true и файл .map рядом с .js
  5. Медленная компиляция — используйте skipLibCheck: true, incremental: true
# Диагностика медленной компиляции
npx tsc --extendedDiagnostics

# Вывод покажет:
# Files:                      1234
# Lines:                     56789
# Parse time:                1.23s
# Bind time:                 0.45s
# Check time:                3.67s  ← основное время
# Emit time:                 0.89s
# Total time:                6.24s
  1. Разница между tsc и бандлером — бандлеры (esbuild, SWC) не проверяют типы, только удаляют аннотации

Практика

  1. Создайте проект с src/index.ts и tsconfig.json
  2. Скомпилируйте с sourceMap: true — изучите файл .js.map
  3. Запустите tsc -w — поменяйте файл и наблюдайте перекомпиляцию
  4. Добавьте declaration: true — посмотрите на .d.ts файлы
  5. Включите incremental: true — сравните время первой и второй компиляции
  6. Попробуйте tsc --noEmit в CI-скрипте

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

Ресурсы