open atlas
↑ К треку
Система типов TypeScript вглубь TS · 07 · 04

Project references: tsc --build, composite и инкрементальный граф сборки

Project references разбивают монорепо на `tsconfig`-проекты, которые `tsc --build` компилирует по графу зависимостей. `composite` (форсирует `declaration`) включает ссылки и кэш `.tsbuildinfo`. Ловушки: устаревший buildinfo, циклические ссылки, ошибка «must have composite».

TS Middle ◷ 14 min
Уровень
ОсновыJuniorMiddleSenior

Монорепо на 200k строк проверяло типы 90 секунд на каждом сохранении, потому что один большой tsconfig перепроверял все пакеты при любом изменении файла. Кто-то разбил его на проекты со ссылками и добавил composite: true — и однострочная правка в пакете web теперь перепроверяет только web, переиспользуя кэшированный вывод core и ui. Первая сборка записала .tsbuildinfo на каждый проект; последующие сборки читают его и пропускают неизменённые проекты. Но миграция началась со стены ошибок error TS6306: Referenced project must have setting "composite": true.

Форма: проект, указывающий на свои зависимости

У каждого пакета свой tsconfig.json. Пакет, зависящий от другого, перечисляет его в references, и проект, на который ссылаются, должен быть composite:

// packages/web/tsconfig.json
{
  "compilerOptions": { "composite": true, "outDir": "dist" },
  "references": [
    { "path": "../core" },   // web зависит от core
    { "path": "../ui" }      // и от ui
  ]
}
// packages/core/tsconfig.json
{
  "compilerOptions": {
    "composite": true,        // ОБЯЗАТЕЛЬНО, чтобы на него ссылались
    "declaration": true,      // composite это подразумевает; типы — это межпроектный контракт
    "outDir": "dist"
  }
}

composite: true включает три вещи: форсирует declaration: true (чтобы проект генерировал .d.tsинтерфейс, который другие проекты потребляют вместо исходника), форсирует инкрементальное поведение с .tsbuildinfo и требует, чтобы каждый входной файл был покрыт include/files (чтобы сборка надёжно хешировала входы).

Почему это работает

Почему composite требует declaration? Потому что весь смысл проекта-со-ссылкой в том, что нижестоящие проекты не перечитывают его исходник — они читают его сгенерированный .d.ts. .d.ts — это межпроектный контракт типов. Без declaration emit web не на что было бы проверять типы, кроме сырых .ts из core, что разрушает изоляцию (и скорость). Поэтому composite и declaration неразделимы: проект-со-ссылкой обязан публиковать свои типы.

tsc --build обходит граф ссылок

Обычная сборка tsc проекта со ссылками либо упадёт, либо проигнорирует их. Нужно использовать режим сборки: tsc --build (или tsc -b). Режим сборки читает граф ссылок, топологически сортирует его и собирает зависимости перед зависящими — и пропускает любой проект, чьи входы не изменились с момента его .tsbuildinfo:

tsc --build packages/web   # собирает core, потом ui, потом web — в порядке зависимостей
tsc --build --verbose      # печатает, какие проекты актуальны, а какие пересобраны
tsc --build --clean        # удаляет все выводы + .tsbuildinfo (лекарство от устаревшего кэша)
tsc --build --force        # игнорирует .tsbuildinfo, пересобирает всё

Инкрементальность и .tsbuildinfo

Прежде чем смириться с 90-секундной проверкой типов на каждом сохранении, спроси себя: знает ли твой tsconfig, какие файлы принадлежат каждому пакету? Если нет, tsc каждый раз хеширует всё заново.

.tsbuildinfo — это кэш хешей файлов и сигнатур. При пересборке tsc -b сравнивает текущие входы с кэшированным состоянием; если входы проекта (и .d.ts его зависимостей) не изменились, он актуален и пропускается целиком. Важно: проекту нужно пересобирать зависящего, только когда его публичный .d.ts реально изменился — внутренняя правка, не меняющая генерируемые типы, оставляет зависящих в кэше (оптимизация по «сигнатуре»).

Именно поэтому редакторы шустрее: каждый проект-со-ссылкой проверяется изолированно, так что языковой сервер не переанализирует всё монорепо на каждом нажатии клавиши.

Ловушки

error TS6306: Referenced project must have setting "composite": true — ты сослался на проект, который не composite. Добавь composite: true в тот, на который ссылаются (частая путаница: его добавляют ссылающемуся).

Устаревший .tsbuildinfo — если изменить что-то, что кэш не отслеживает (апгрейд компилятора с особенностью, ручная правка dist), сборка может ошибочно счесть проект актуальным. Лекарство: tsc -b --clean, затем пересборка, или --force.

Циклические ссылкиA ссылается на B, а B на A — отвергается (TS6202); DAG сборки должен быть ацикличным. Разорви цикл, вынеся общие типы в третий лист-проект, от которого зависят оба.

Path mapping между проектами — алиасы paths по-прежнему не переписываются при emit (урок 02), поэтому межпроектные импорты должны идти через настоящий entry/exports пакета, а не через алиас paths, который понимает только проверщик типов.

Все четыре ловушки объединяет паттерн: ошибка указывает на проект, на который ссылаются, а не на ссылающийся; кэш — это файл, который всегда можно удалить; циклы требуют нового листа для разрыва; алиасы — фикция проверщика типов, которая не должна просачиваться в emit.

Расставь шаги по порядку

Расставь, что делает `tsc --build packages/web` для графа web → ui → core и ui → core:

  1. 1 Читает references у web, затем транзитивно собирает ui и core в граф сборки
  2. 2 Топологически сортирует: у core нет зависимостей, значит он собирается первым
  3. 3 Для каждого проекта хеширует входы против .tsbuildinfo; пересобирает только при изменении, иначе пропускает
  4. 4 Собирает зависящих после их зависимостей: core → ui → web, генерируя .js + .d.ts
Викторина

Ты запускаешь `tsc -b` и получаешь TS6306: Referenced project 'packages/core' must have setting 'composite': true. Куда добавить composite?

Вспомните перед уходом
  1. 01
    Что делает `composite: true` и почему он форсирует `declaration`?
  2. 02
    Что делает `tsc --build`, чего не делает обычный `tsc`, и как он использует граф ссылок?
  3. 03
    Назови три классических провала project references и их фиксы.
Итог

Теперь ты умеешь структурировать монорепо для быстрых изолированных сборок: references объявляют граф, composite (форсирующий declaration) делает проект потребляемым, tsc -b упорядочивает и кэширует сборку через .tsbuildinfo, и ты знаешь, как читать TS6306, чистить устаревшие кэши и разрывать циклы. Дальше — build tooling: ссылки организуют собственную работу tsc, но большинство проектов отдают emit в esbuild/swc/babel; мы увидим, почему эти транспиляторы не проверяют типы, почему тогда isolatedModules обязателен и как tsc --noEmit становится CI-гейтом типов рядом с бандлером. Теперь, когда видишь монорепо с единственным tsconfig, перепроверяющим 200k строк на каждом сохранении, ты знаешь фикс: разбить на проекты-со-ссылками, добавить composite: true каждому листу и запускать tsc -b вместо tsc.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 7 завершено
Связанные уроки

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources1
expand
  1. 01

Trademarks belong to their respective owners. Editorial reference only.