Перейти к основному содержимому

Архитектура компиляции TypeScript и runtime

Разработчику Архитектору

Дальше: TypeScript Server · Форматы и подключение · JS-курс: компилятор TypeScript


TypeScript не выполняется в браузере или Node.js: компилятор проверяет типы и генерирует JavaScript. Память в runtime управляет движок JS (V8 и др.) через сборщик мусора. TS помогает до запуска — не заменяет GC и не ловит утечки из замыканий.

Маршрут: Форматы и подключениекомпиляцияTypeScript Server.

Теория: компиляция и интерпретация, сборка мусора.


Цепочка: исходник → выполнение

index.ts → tsc (или bundler) → index.js → V8 / браузер

проверка типов
(типы не попадают в .js)
ЭтапЧто происходит
Редактированиеtsserver в IDE — ошибки в реальном времени
tsc --noEmitТолько проверка типов (CI)
tsc / Vite buildГенерация .js, опционально .d.ts, .map
node dist/...Выполнение обычного JavaScript

Этапы работы компилятора

#ЭтапРезультат
1Лексический анализТокены (function, interface, идентификаторы)
2ПарсингAST (дерево синтаксиса)
3Проверка типовДиагностики, связь типов с узлами AST
4Эмиссия (emit).js, .d.ts, source maps

На этапе эмиссии исчезают:

  • аннотации : string, : User;
  • interface, type (только типы);
  • большая часть generic-параметров.

Сравните вручную src/index.ts и dist/index.js — это лучшее упражнение для понимания границы TS/JS.

Связанный обзор в JS-курсе — компилятор TypeScript.


Транспиляция и проверка типов

В разговоре про TypeScript смешивают два процесса — их важно разделять:

ПроцессЧто делаетРезультат
Проверка типов (typecheck)Сравнивает код с контрактами, строит диагностикиОшибки в IDE / exit code tsc
Транспиляция (emit)Переписывает синтаксис TS/современного JS в целевой targetФайлы .js, .d.ts, source maps
npx tsc --noEmit # только проверка, без emit
npx tsc # проверка + emit в outDir

Bundler (Vite, esbuild) часто транспилирует быстро, но полную проверку графа типов оставляют tsc — типичный скрипт "build": "tsc -b && vite build". Проверка типов всегда на стороне TS; JavaScript в runtime о типах не знает.


Из .ts в .js: что исчезает

src/greet.ts:

interface User {
id: string;
name: string;
}

export function greet(user: User): string {
return `Hello, ${user.name}`;
}

После tsc в dist/greet.js (упрощённо):

export function greet(user) {
return `Hello, ${user.name}`;
}

Разбор:

  • interface User не попадает в JS — это только проверка на этапе компиляции.
  • Параметр user в .js без : User, но IDE и tsc в исходниках знают контракт.
  • Опечатка user.nmae — ошибка до node dist/greet.js, не в runtime.

isolatedModules и verbatimModuleSyntax

Vite, esbuild и Babel транспилируют файлы по отдельности, без полного анализа графа типов как у tsc.

ОпцияНазначение
isolatedModules: trueЗапрещает конструкции, которые нельзя transpile’ить по одному файлу (например, const enum без inline)
verbatimModuleSyntax: trueЯвные import type / export type — типы не смешиваются с value-импортами
// Плохо при isolatedModules + bundler: type-only import как value
import { User } from "./types.js";

// Лучше
import type { User } from "./types.js";

Разбор:

  • В шаблонах Vite react-ts эти флаги часто уже включены — при ошибке cannot be compiled under isolatedModules смотрите синтаксис — import type.

Ключевые опции компилятора

ОпцияНазначение
targetВ какую версию ECMAScript транспилировать синтаксис
moduleФормат import/export на выходе
libКакие глобальные API считать известными (DOM, ES2022)
outDir / rootDirСтруктура выходных файлов
declarationГенерация .d.ts для библиотек
sourceMapКарты для отладки по .ts
incrementalУскорение повторных сборок
compositeProject references в monorepo

Пример для Node:

{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"sourceMap": true,
"declaration": true,
"strict": true
}
}

Разбор:

  • targetсинтаксис (стрелки, class fields); не путать с module.
  • module: NodeNext — согласование с ESM/CJS в Node — 9.md.

tsc и bundler: разделение ролей

ИнструментТипичная роль
tscПроверка типов; emit JS для Node-библиотек
Vite / esbuild / swcБыстрая сборка frontend, tree-shaking
tsc --noEmitCI: только типы, без файлов
tsc -bСборка monorepo с references

Frontend-проект на Vite часто:

"build": "tsc -b && vite build"

Сначала падает на ошибках типов, затем собирается бандл.

Нативный компилятор на Go (ветка 7.x)

Параллельно классическому компилятору на TypeScript развивается нативная реализация (tsgo / TypeScript Native): цель — ускорить typecheck и emit на очень больших monorepo. Семантика языка остаётся той же; меняется скорость и внутренняя архитектура. Следите за release notes, репозиторием typescript-go и историю.


Файлы декларации (.d.ts)

При "declaration": true компилятор пишет описание публичного API без реализации:

// dist/user.d.ts (сгенерировано)
export interface User {
id: string;
name: string;
}
export declare function getUser(id: string): User;

Назначение:

  • потребители npm-пакета на TS видят типы без исходников;
  • project references в monorepo ссылаются на .d.ts зависимостей.

Source maps

{
"compilerOptions": {
"sourceMap": true,
"declarationMap": true
}
}
  • sourceMap — отладка .ts в DevTools / Node.
  • declarationMap — переход к исходнику из .d.ts в IDE.

Без карт стек ошибок указывает на dist/*.js — читать сложнее.


Память в runtime (JavaScript)

TypeScript не управляет памятью:

  • В браузере и Node — GC (сборщик мусора).
  • TS не предотвращает утечки: глобальные кэши, забытые подписки, замыкания на большие объекты.
  • TS предотвращает обращение к несуществующему полю, неверные аргументы, часть null-ошибок — до запуска.
ПроблемаЛовит TS?
user.nmae опечаткаДа (если тип User)
Утечка памяти в MapНет
null без проверкиДа при strictNullChecks
Неверный тип в JSON с сервераТолько при явной валидации (Zod и т.п.)

Сравнение GC языков — шпаргалка GC.


noEmit и CI

npx tsc --noEmit
  • Проверяет весь проект по tsconfig.json.
  • Не создаёт dist/ — удобно, когда emit делает Vite.

Отдельный job в CI обычно быстрее полной production-сборки и в логах явно виден как проверка типов.


Project references

Для monorepo:

{
"compilerOptions": {
"composite": true,
"declaration": true,
"incremental": true
},
"references": [{ "path": "../shared" }]
}
tsc -b

Компилятор строит граф пакетов и пересобирает только изменённые части. Подробнее — форматы и подключение — monorepo, справочник — project references.


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

СимптомПричина
В браузере старый кодНе пересобрали / кэш
Breakpoint в .js, не в .tsВыключен sourceMap
Типы есть, runtime падаетTS не валидирует сеть без parse
tsc и Vite дают разный JSРазные target / разный transpiler
Огромный distЛишние файлы в include

Практика

  1. Соберите src/index.tsdist/index.js и выпишите, что исчезло из TS.
  2. Включите sourceMap, поставьте breakpoint в .ts при отладке.
  3. Добавьте в CI tsc --noEmit отдельным шагом.
  4. Сгенерируйте .d.ts (declaration: true) и откройте в IDE Go to definition из другого пакета.
  5. Сравните время tsc и vite build — кто только типы, кто бандл.

Смежные статьи