Архитектура компиляции 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 | Ускорение повторных сборок |
composite | Project 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 --noEmit | CI: только типы, без файлов |
tsc -b | Сборка monorepo с references |
Frontend-проект на Vite часто:
"build": "tsc -b && vite build"
Сначала падает на ошибках типов, затем собирается бандл.
Параллельно классическому компилятору на 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 |
Практика
- Соберите
src/index.ts→dist/index.jsи выпишите, что исчезло из TS. - Включите
sourceMap, поставьте breakpoint в.tsпри отладке. - Добавьте в CI
tsc --noEmitотдельным шагом. - Сгенерируйте
.d.ts(declaration: true) и откройте в IDE Go to definition из другого пакета. - Сравните время
tscиvite build— кто только типы, кто бандл.