Архитектура компиляции TypeScript и runtime
Дальше: TypeScript Server · Форматы и подключение · JS-курс: компилятор TypeScript
TypeScript не выполняется в браузере или Node.js. Компилятор проверяет типы и генерирует JavaScript. Память в runtime управляет движок JS (V8 и др.) через сборщик мусора. TS помогает до запуска — он не заменяет GC и не ловит утечки из замыканий.
Перед этой статьей — Форматы и подключение. После — TypeScript Server. Теория — компиляция и интерпретация, сборка мусора.
Цепочка от исходника до выполнения
Play ITЗагрузка интерактивного демо…
index.ts → tsc (или bundler) → index.js → V8 / браузер
↑
проверка типов
(типы не попадают в .js)
| Этап | Что происходит |
|---|---|
| Редактирование | tsserver в IDE показывает ошибки в реальном времени |
tsc --noEmit | Только проверка типов (CI) |
tsc / Vite build | Генерация .js, опционально .d.ts, .map |
node dist/... | Выполнение обычного JavaScript |
Этапы работы компилятора
Play ITЗагрузка интерактивного демо…
Play ITЗагрузка интерактивного демо…
| # | Этап | Результат |
|---|---|---|
| 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 — Форматы и подключение TypeScript.
Роли 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 7.0 — нативный компилятор на Go
В июне 2026 Microsoft выпустила TypeScript 7.0: основной пакет typescript на npm теперь содержит нативный компилятор, портированный с исходной реализации на TypeScript/JavaScript в Go (проект typescript-go, ранее — preview tsgo / @typescript/native-preview). Логика type-checking структурно совпадает с TypeScript 6.0; меняются скорость, параллелизм и потребление памяти.
npm install -D typescript # 7.x — обычный tsc
npx tsc --version
Производительность
На крупных open-source репозиториях Microsoft фиксирует ускорение полной сборки примерно в 8–12 раз относительно TypeScript 6.0:
| Кодовая база | TS 6 | TS 7 | Ускорение |
|---|---|---|---|
| vscode | 125.7 с | 10.6 с | 11.9× |
| sentry | 139.8 с | 15.7 с | 8.9× |
| bluesky | 24.3 с | 2.8 с | 8.7× |
| playwright | 12.8 с | 1.47 с | 8.7× |
Память на тех же проектах обычно ниже на 6–26%. В VS Code первая диагностика ошибки в большом файле сократилась с ~17.5 с до <1.3 с. Для monorepo это заметно и в CI (например, у Slack type-check в пайплайне — с ~7.5 мин до ~1.25 мин).
Параллелизация
TypeScript 7 распараллеливает парсинг, type-checking, emit и сборку project references. Новые экспериментальные флаги:
| Флаг | Назначение |
|---|---|
--checkers N | Число воркеров type-checking (по умолчанию 4). Больше ядер — быстрее, но выше RAM |
--builders N | Параллельная сборка project references при tsc -b |
--singleThreaded | Отключить многопоточность (отладка, слабые CI-раннеры) |
--checkers и --builders умножаются (--checkers 4 --builders 4 — до 16 воркеров). На CI с малым объёмом памяти имеет смысл уменьшить оба значения.
--watch и редактор
--watch переписан поверх file-watcher из экосистемы Parcel (порт на Go). Language server в 7.0 тоже на новом фундаменте: LSP, многопоточность, меньше падений по сравнению с TS 6. В VS Code — отдельное расширение TypeScript 7; в Visual Studio 7 включается по workspace автоматически.
Совместимость с TypeScript 6.0
Код, который чисто компилируется на TypeScript 6.0 (с stableTypeOrdering и без ignoreDeprecations), на 7.0 должен давать тот же результат. TypeScript 7 наследует новые дефолты 6.0 (strict: true, module: esnext, явный types: [] и др.) и превращает устаревшие флаги в ошибки (moduleResolution: node10, target: es5, baseUrl и т.д.). Перед апгрейдом на 7.0 разумно сначала перейти на 6.0 — release notes 6.0.
Изменение семантики типов: template literal types теперь режут строки по Unicode code points, а не по UTF-16 суррогатным парам ("😀abc" → ["😀", "abc"], не ["\ud83d", "\ude00abc"]).
Programmatic API и side-by-side
В 7.0 нет стабильного programmatic API (import * as ts from "typescript" для линтеров, трансформеров, Volar). Его обещают в 7.1. Пока инструменты, зависящие от Compiler API, держат TypeScript 6 через пакет @typescript/typescript6 (бинарник tsc6, re-export API 6.0):
{
"devDependencies": {
"@typescript/native": "npm:typescript@^7.0.2",
"typescript": "npm:@typescript/typescript6@^6.0.2"
}
}
npx tsc — проверка на 7.0; typescript-eslint и подобные пакеты продолжают импортировать API 6.0 из typescript.
Плагины, встраивающие TypeScript в свой компилятор (Volar и аналоги), пока работают на 6.0. Для Angular возможна схема: tsc 7 в CI + TS 6 в редакторе. В VS Code — команда Disable TypeScript 7 Language Server, чтобы вернуться к 6.0.
Ночные сборки снова публикуются как typescript@next. Подробнее — история TypeScript, TypeScript Server.
Файлы декларации (.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— кто только типы, кто бандл.