Сборка и тулчейн
Проверка типов через tsc против транспиляции esbuild или SWC, Go-нативный компилятор TypeScript 7, project references и инкрементальная сборка в монорепозитории, typescript-eslint и диагностика медленной проверки типов.
18 вопросов
JuniorТеорияОчень частоЧто делает tsc, чего транспиляторы esbuild и SWC не делают?
Что делает tsc, чего транспиляторы esbuild и SWC не делают?
tsc проверяет типы всей программы, а потом эмитит. esbuild и SWC только срезают типы и эмитят — проверку типов они пропускают полностью. Они видят по одному файлу за раз, без обзора соседних, поэтому и нужен isolatedModules.
Типичные ошибки
- ✗Считать, что зелёная сборка
esbuildозначает корректные типы - ✗Принимать
isolatedModulesза флаг скорости, а не за ограничение однофайлового эмита - ✗Думать, что
tsc --noEmitв CI лишний, раз в пайплайне уже есть быстрый транспилятор
Уточняющие вопросы
- →Какие конструкции TypeScript запрещает
isolatedModulesи почему именно их? - →Куда в пайплайне поставить
tsc --noEmitотносительно бандлераesbuild?
MiddleТеорияЧастоЧто ловит типо-зависимый линтер typescript-eslint, чего tsc не поймает никогда?
Что ловит типо-зависимый линтер typescript-eslint, чего tsc не поймает никогда?
tsc проверяет только корректность типов — код бывает типобезопасным и всё равно скверным. typescript-eslint кладёт поверх той же информации о типах политику: забытые promise, небезопасный доступ через any, правила именования и идиом. Это выбор, а не ошибки типов.
Типичные ошибки
- ✗Считать, что чистый прогон линтера означает корректные типы, или наоборот
- ✗Верить, что линтер работает только по синтаксису и не умеет пользоваться типами
- ✗Включать типо-зависимые правила без настройки
project, которая молча их отключает
Уточняющие вопросы
- →Почему типо-зависимым правилам нужен
tsconfigи почему они куда дороже синтаксических? - →Какое правило ловит promise, чей отказ никто не обрабатывает?
MiddleДизайнЧастоВ вашем монорепозитории двенадцать пакетов: несколько библиотек, два Node-сервиса и одно React-приложение. Сейчас единственный корневой tsconfig.json перечисляет все папки с исходниками в одной программе, и команда спорит, оставить это так или дать каждому пакету собственный tsconfig.json, который extends общую базу. Ограничения: библиотеки должны поставляться как собранные пакеты с объявлениями; React-приложению нужны jsx и DOM-типы, которых Node-сервисы видеть не должны; CI обязан падать на ошибке типов в любом пакете; изменение одной библиотеки не должно перепроверять весь репозиторий. Сравните обе раскладки, скажите, какую выберете и почему, и что положите в общую базу, а что — в конфиг каждого пакета.
В вашем монорепозитории двенадцать пакетов: несколько библиотек, два Node-сервиса и одно React-приложение. Сейчас единственный корневой tsconfig.json перечисляет все папки с исходниками в одной программе, и команда спорит, оставить это так или дать каждому пакету собственный tsconfig.json, который extends общую базу. Ограничения: библиотеки должны поставляться как собранные пакеты с объявлениями; React-приложению нужны jsx и DOM-типы, которых Node-сервисы видеть не должны; CI обязан падать на ошибке типов в любом пакете; изменение одной библиотеки не должно перепроверять весь репозиторий. Сравните обе раскладки, скажите, какую выберете и почему, и что положите в общую базу, а что — в конфиг каждого пакета.
Общая база плюс tsconfig в каждом пакете, который её extends, связанные через project references. В базе — общая политика: strict, target, module. Пакет владеет тем, что различается: lib, jsx, outDir. Одна программа не даст Node и DOM разные lib.
Типичные ошибки
- ✗Пускать DOM-типы в Node-сервисы через единый общий
libна весь репозиторий - ✗Дублировать
strictи target в каждом пакете вместо наследования общей базы - ✗Ждать от одной корневой программы пересборки по пакетам без графа ссылок
Уточняющие вопросы
- →Что положить в базовый конфиг, а что обязано остаться в пакете?
- →Как не дать Node-сервису импортировать пакет, предназначенный только для браузера?
MiddleТеорияЧастоЧто дают project references в tsconfig в монорепозитории?
Что дают project references в tsconfig в монорепозитории?
Они делят репозиторий на отдельно собираемые проекты с объявленным графом зависимостей. Каждый проект проверяется один раз и эмитит .d.ts, которые потребляют зависящие вместо исходников, поэтому tsc --build пересобирает лишь устаревшее.
Типичные ошибки
- ✗Путать
referencesсpaths— первое строит граф, второе лишь подменяет спецификаторы - ✗Забывать, что указанный проект обязан выставить
composite: trueи эмитить объявления - ✗Запускать голый
tsc, игнорирующий граф ссылок, вместоtsc --build
Уточняющие вопросы
- →Что
composite: trueтребует от указанного проекта? - →Как
tsc --buildрешает, что указанный проект устарел?
MiddleДебаггингИногдаПочему AppError не присваивается к AppError после добавления одной зависимости?
Почему AppError не присваивается к AppError после добавления одной зависимости?
На диске две копии @acme/errors, поэтому компилятор строит два несвязанных типа AppError. Структурное сопоставление их не спасёт: член private делает класс номинативным, а приватные члены из разных объявлений не совпадают никогда. Дедуплицируйте пакет.
Типичные ошибки
- ✗Считать, что структурная типизация делает два одинаковых класса взаимозаменяемыми, забыв про
private - ✗Винить расхождение версий, когда обе копии на деле одной версии
- ✗Глушить ошибку через
as anyвместо дедупликации зависимости
Уточняющие вопросы
- →Какая команда покажет, что пакет отрезолвился в дереве зависимостей дважды?
- →Почему поле
privateзаставляет структурный класс вести себя номинативно?
MiddleТеорияИногдаЧто incremental кеширует в .tsbuildinfo и когда этот кеш становится несвежим?
Что incremental кеширует в .tsbuildinfo и когда этот кеш становится несвежим?
Он записывает хеш версии каждого входа плюс граф зависимостей и сигнатур объявлений. tsc перепроверяет только изменённые файлы и затронутых зависящих. Смена опций или версии компилятора сбрасывает кеш; удаление эмита делает его несвежим.
Типичные ошибки
- ✗Думать, что
.tsbuildinfoкеширует эмитированный JavaScript, а не состояние проверки - ✗Ждать, что смена опции или версии компилятора всё равно переиспользуется инкрементально
- ✗Удалить вывод сборки, но оставить кеш — и
tscпропустит нужный эмит
Уточняющие вопросы
- →Почему
tsc --buildдополнительно сравнивает время вывода со временем входов? - →Стоит ли коммитить
.tsbuildinfoв систему контроля версий или игнорировать?
MiddleПроизводительностьИногдаКак найти, какой проект в графе монорепозитория медленно проверяется по типам?
Как найти, какой проект в графе монорепозитория медленно проверяется по типам?
Измеряйте, а не гадайте. tsc --build --diagnostics показывает время проверки и число файлов по каждому проекту; --extendedDiagnostics добавляет счётчики инстанцирований и памяти. Для худшего проекта --generateTrace покажет, какие типы и файлы доминируют.
Типичные ошибки
- ✗Гадать по числу файлов вместо замера времени проверки по каждому проекту
- ✗Считать, что самый большой пакет автоматически и проверяется дольше всех
- ✗Игнорировать счётчик инстанцирований, где один дженерик может съесть весь прогон
Уточняющие вопросы
- →На что обычно указывает огромное число инстанцирований в
--extendedDiagnostics? - →Как сузить медленную проверку с целого проекта до одного файла?
MiddleТеорияИногдаЧто на самом деле решает компилятор TypeScript 7.0 и что он точно не меняет?
Что на самом деле решает компилятор TypeScript 7.0 и что он точно не меняет?
Он бьёт по задержке: время холодной проверки типов на большом репозитории и отзывчивость редактора улучшаются в разы. Систему типов он не трогает — те же программы проходят и падают с теми же диагностиками. Это починка скорости, а не семантики.
Типичные ошибки
- ✗Ждать новых возможностей системы типов вместе с портом на Go
- ✗Считать, что ошибки программы поменяются под новым компилятором
- ✗Думать, что ускорение помогает эмиту или бандлингу, а не проверке
Уточняющие вопросы
- →Как измерить выигрыш холодной проверки именно на вашем репозитории?
- →Если диагностика различается между TypeScript 6.0 и 7.0 — это фича или баг?
SeniorДизайнИногдаВы сопровождаете десятилетнюю кодовую базу на TypeScript. В корневом tsconfig.json до сих пор стоят target: es5, module: umd, moduleResolution: node10, baseUrl для импортов, outFile для старого бандла и esModuleInterop: false; второй конфиг использует module: amd ради старого плагин-хоста. Команда обновилась до TypeScript 6.0, увидела одни предупреждения об устаревании и теперь считает, что переход на TypeScript 7.0 — рутинный подъём версии. Опишите аудит перед этим подъёмом: что именно перестанет компилироваться, почему прогон на TypeScript 6.0 не дал команде полезного сигнала, в каком порядке вы снимете зависимость от этих опций и как докажете готовность репозитория.
Вы сопровождаете десятилетнюю кодовую базу на TypeScript. В корневом tsconfig.json до сих пор стоят target: es5, module: umd, moduleResolution: node10, baseUrl для импортов, outFile для старого бандла и esModuleInterop: false; второй конфиг использует module: amd ради старого плагин-хоста. Команда обновилась до TypeScript 6.0, увидела одни предупреждения об устаревании и теперь считает, что переход на TypeScript 7.0 — рутинный подъём версии. Опишите аудит перед этим подъёмом: что именно перестанет компилироваться, почему прогон на TypeScript 6.0 не дал команде полезного сигнала, в каком порядке вы снимете зависимость от этих опций и как докажете готовность репозитория.
TypeScript 6.0 их лишь депрецировал; 7.0 делает их жёсткими ошибками: es5, AMD, UMD, systemjs, none, moduleResolution: node10/classic, baseUrl, outFile, downlevelIteration, esModuleInterop: false. Чистая сборка 6.0 не доказывает ничего; проверяйте каждый конфиг.
Типичные ошибки
- ✗Принимать сборку TypeScript 6.0 без предупреждений за доказательство, что переход на 7.0 пройдёт
- ✗Проверять только корневой
tsconfig, упуская конфиги, которые его наследуют или переопределяют - ✗Считать, что опции резолвинга вроде
baseUrlуцелели, раз удалили только опции эмита
Уточняющие вопросы
- →Что заменяет
baseUrl, когда он становится жёсткой ошибкой? - →Как продолжать выпускать сборку под ES5, когда компилятор отказывается её эмитить?
SeniorДебаггингИногдаРедактор показывает ошибки в пакете лишь через 30с. О чём говорят эти цифры?
Редактор показывает ошибки в пакете лишь через 30с. О чём говорят эти цифры?
Парсинг и биндинг — две секунды, проверка — девяносто шесть, а 48 миллионов инстанцирований на 1842 файла чудовищно непропорциональны. Это взрыв на уровне типов, а не число файлов и не диск. Снимите --generateTrace и найдите дженерик.
Типичные ошибки
- ✗Винить число файлов или диск, когда время парсинга и биндинга уже почти нулевое
- ✗Читать большую цифру памяти как причину, а не как следствие инстанцирований
- ✗Гадать, какой дженерик виноват, вместо того чтобы снять трассу и посмотреть
Уточняющие вопросы
- →О чём говорит размер кеша присваиваемости рядом с числом инстанцирований?
- →Какие конструкции типов чаще всего дают такой взрыв инстанцирований?
SeniorДизайнИногдаМонорепозиторий из двадцати пакетов уже использует project references, но правка одной строки в общем пакете @acme/core перепроверяет почти всё, что от него зависит, — девять минут CI ради правки комментария. От core зависят все, сам core через файл-бочку реэкспортирует типы трёх других пакетов, а пакеты эмитят объявления с declaration: true, но composite выставлен вразнобой. Объясните, что на самом деле заставляет tsc --build считать зависящий проект устаревшим, и перестройте граф ссылок и сами пакеты так, чтобы обычная правка внутри core больше не тянула пересборку всего, что ниже по графу.
Монорепозиторий из двадцати пакетов уже использует project references, но правка одной строки в общем пакете @acme/core перепроверяет почти всё, что от него зависит, — девять минут CI ради правки комментария. От core зависят все, сам core через файл-бочку реэкспортирует типы трёх других пакетов, а пакеты эмитят объявления с declaration: true, но composite выставлен вразнобой. Объясните, что на самом деле заставляет tsc --build считать зависящий проект устаревшим, и перестройте граф ссылок и сами пакеты так, чтобы обычная правка внутри core больше не тянула пересборку всего, что ниже по графу.
tsc --build перепроверяет зависящего, только когда меняется сигнатура эмитированного .d.ts зависимости, поэтому держите правки реализации вне публичных объявлений: выставьте composite везде, разделите core на пакет типов и реализацию, уберите файл-бочку.
Типичные ошибки
- ✗Думать, что зависящий проект обесценивает правка исходника, а не изменившаяся сигнатура объявлений
- ✗Не выставить
composite, отключая тем самым проверку актуальности, на которой держатся ссылки - ✗Держать бочку, реэкспортирующую всё, из-за чего любая правка расходится по всему графу
Уточняющие вопросы
- →Почему выделение пакета только с типами из
coreукорачивает цепочку пересборки? - →Что
composite: trueменяет в том, какtsc --buildрешает вопрос устаревания?
JuniorТеорияРедкоПочему Go-нативный компилятор TypeScript 7.0 быстрее, чем компилятор TypeScript 6.0?
Почему Go-нативный компилятор TypeScript 7.0 быстрее, чем компилятор TypeScript 6.0?
TypeScript 7.0 переносит тот же checker на Go: он работает как нативный код вместо JavaScript и использует настоящий параллелизм по ядрам. Это порт, а не переделка — система типов, синтаксис и диагностики не изменились. Меняется только задержка.
Типичные ошибки
- ✗Думать, что порт меняет систему типов или правила вывода
- ✗Ждать от TypeScript 7.0 других диагностик или другого набора принимаемых программ
- ✗Считать, что скорость взялась из урезанных проверок, а не из нативного кода и параллелизма
Уточняющие вопросы
- →Что выигрывает больше — холодная проверка в CI или инкрементальное обновление в редакторе?
- →Программа, которая не компилируется на TypeScript 6.0, останется ошибочной и на 7.0?
SeniorДизайнРедкоВаша платформенная команда владеет кодогенератором и набором собственных правил линтера — оба делают import * as ts from 'typescript' и обходят AST через compiler API. Руководство прочитало бенчмарки TypeScript 7.0 и хочет перевести на него весь репозиторий за квартал; сейчас CI гоняет один tsc --build по двадцати пакетам за одиннадцать минут. Объясните, что реально возможно сегодня при нынешнем состоянии TypeScript 7.0, а что нет, и спроектируйте план для руководства: что переезжает сразу, что остаётся на TypeScript 6.0 и почему, как две версии компилятора уживаются в одном репозитории и какой конкретный сигнал скажет, что миграцию можно завершить.
Ваша платформенная команда владеет кодогенератором и набором собственных правил линтера — оба делают import * as ts from 'typescript' и обходят AST через compiler API. Руководство прочитало бенчмарки TypeScript 7.0 и хочет перевести на него весь репозиторий за квартал; сейчас CI гоняет один tsc --build по двадцати пакетам за одиннадцать минут. Объясните, что реально возможно сегодня при нынешнем состоянии TypeScript 7.0, а что нет, и спроектируйте план для руководства: что переезжает сразу, что остаётся на TypeScript 6.0 и почему, как две версии компилятора уживаются в одном репозитории и какой конкретный сигнал скажет, что миграцию можно завершить.
Потребителей API мигрировать нельзя: у TypeScript 7.0 нет программного API — он нацелен на 7.1. Оставьте 6.0 библиотекой, которую импортируют кодогенератор и правила линтера, а 7.0 запускайте рядом как --noEmit в CI. Сигнал к завершению — API в 7.1.
Типичные ошибки
- ✗Считать, что выход релиза означает, будто вместе с ним переехал и compiler API
- ✗Планировать разовый подъём версии по всему репозиторию, когда два компилятора обязаны сосуществовать
- ✗Выбрасывать типо-зависимые инструменты только ради того, чтобы уйти от зависимости от API
Уточняющие вопросы
- →Как держать две версии TypeScript в одном репозитории так, чтобы они не столкнулись?
- →Что измерить, чтобы доказать, что вторая, неблокирующая проверка стоит своего места?
SeniorТеорияРедкоTypeScript 7.0 уже вышел — почему typescript-eslint всё ещё закреплён на TypeScript 6.0?
TypeScript 7.0 уже вышел — почему typescript-eslint всё ещё закреплён на TypeScript 6.0?
Потому что TypeScript 7.0 вышел без программного API компилятора; новый нацелен на 7.1. Всему, что гоняет checker внутри своего процесса — typescript-eslint, Volar, Angular, — просто нечего вызывать. Командная строка пригодна, библиотека — ещё нет.
Типичные ошибки
- ✗Считать закрепление экосистемы вопросом расписания, а не отсутствующего API
- ✗Верить, что семантика TypeScript 7.0 иная и именно это заставляет закрепляться
- ✗Считать вопрос решённым просто потому, что компилятор дошёл до релиза
Уточняющие вопросы
- →Какие ещё инструменты заблокированы ровно тем же отсутствующим API?
- →Что из TypeScript 7.0 всё-таки можно принять сегодня без программного API?
SeniorДизайнРедкоCI вашего репозитория гоняет tsc --build и типо-зависимый проход typescript-eslint; вместе они занимают четырнадцать минут, а линтер закреплён на TypeScript 6.0, потому что грузит компилятор как библиотеку. Скорость TypeScript 7.0 нужна вам уже сейчас, но отказаться от линтера нельзя, и ложное падение CI из-за компилятора, за которым экосистема не поспела, вы себе позволить не можете. Спроектируйте, как ввести TypeScript 7.0 в этот пайплайн: где именно он запускается, что ему разрешено блокировать, как обе версии компилятора уживаются в одном репозитории и какие данные позволят сделать его блокирующей проверкой.
CI вашего репозитория гоняет tsc --build и типо-зависимый проход typescript-eslint; вместе они занимают четырнадцать минут, а линтер закреплён на TypeScript 6.0, потому что грузит компилятор как библиотеку. Скорость TypeScript 7.0 нужна вам уже сейчас, но отказаться от линтера нельзя, и ложное падение CI из-за компилятора, за которым экосистема не поспела, вы себе позволить не можете. Спроектируйте, как ввести TypeScript 7.0 в этот пайплайн: где именно он запускается, что ему разрешено блокировать, как обе версии компилятора уживаются в одном репозитории и какие данные позволят сделать его блокирующей проверкой.
Добавьте TypeScript 7.0 второй, неблокирующей задачей --noEmit рядом с текущей сборкой, а линтер пусть сохраняет свою зависимость от 6.0. Поставьте обе версии под разными алиасами. Делайте 7.0 блокирующим, когда его вердикты совпадут с 6.0 на настоящих PR.
Типичные ошибки
- ✗Направлять типо-зависимый линтер на TypeScript 7.0, который он не может загрузить как библиотеку
- ✗Делать новый checker блокирующим до того, как его вердикты сверили со старым
- ✗Считать, что две версии TypeScript нельзя поставить бок о бок в одном репозитории
Уточняющие вопросы
- →Как поставить две версии TypeScript бок о бок, чтобы они не столкнулись?
- →Какие данные убедят вас сделать проверку на TypeScript 7.0 блокирующей?
SeniorТеорияРедкоГде TypeScript 7.0 реально можно применить сегодня, а где ещё нельзя?
Где TypeScript 7.0 реально можно применить сегодня, а где ещё нельзя?
Проверка — безопасный первый шаг: tsc --noEmit в CI выдаёт лишь вердикт «прошло или нет», и артефакты его никто дальше по конвейеру не потребляет. Всё, что грузит компилятор как библиотеку, держите на 6.0: программного API у 7.0 нет.
Типичные ошибки
- ✗Считать, что релиз означает, будто переехать могут все потребители компилятора разом
- ✗Пытаться подменить компилятор под инструментом, который импортирует его как библиотеку
- ✗Ждать от нового checker других вердиктов и из-за этого держать CI на старом
Уточняющие вопросы
- →Как запустить новый checker в CI, не блокируя существующую сборку?
- →Какие из ваших инструментов импортируют
typescriptкак библиотеку, а не зовутtsc?
SeniorТеорияРедкоПочему компилятор TypeScript 7.0 — это порт на Go, а не переписывание на Rust?
Почему компилятор TypeScript 7.0 — это порт на Go, а не переписывание на Rust?
Потому что это порт, а не переписывание. Существующий checker написан на TypeScript, и идиоматичный Go повторяет его структура в структуру — сборка мусора и циклические изменяемые графы достаются даром, а сверху остаются нативная скорость и контроль над раскладкой памяти.
Типичные ошибки
- ✗Пересказывать сравнение Go против Rust, которого нет ни в одном первоисточнике
- ✗Называть это переписыванием, когда весь смысл — механический порт существующего checker
- ✗Считать, что язык-хост поменял систему типов или алгоритмы проверки
Уточняющие вопросы
- →Что даёт «порт, а не переписывание», когда обе реализации обязаны идти нога в ногу?
- →Какое свойство модели памяти Go важно для checker на циклических изменяемых графах?