Редактор показывает ошибки в пакете лишь через 30с. О чём говорят эти цифры?
Инженеры жалуются, что редактору нужны десятки секунд, чтобы показать ошибки в одном пакете, хотя репозиторий не сказать чтобы огромный. Запуск tsc --noEmit --extendedDiagnostics на этом пакете печатает цифры ниже.
Ограничения: памяти на машине хватает, диск быстрый, число файлов не менялось месяцами. Ответы вида «купите машину помощнее» вне рассмотрения.
Files: 1842
Lines of TypeScript: 214930
Nodes: 912447
Symbols: 498112
Types: 1284037
Instantiations: 48120665
Memory used: 3184219K
Assignability cache size: 922841
Parse time: 1.42s
Bind time: 0.71s
Check time: 96.30s
Emit time: 0.00s
Total time: 98.43s
Определите причину.
Парсинг и биндинг — две секунды, проверка — девяносто шесть, а 48 миллионов инстанцирований на 1842 файла чудовищно непропорциональны. Это взрыв на уровне типов, а не число файлов и не диск. Снимите --generateTrace и найдите дженерик.
- ✗Винить число файлов или диск, когда время парсинга и биндинга уже почти нулевое
- ✗Читать большую цифру памяти как причину, а не как следствие инстанцирований
- ✗Гадать, какой дженерик виноват, вместо того чтобы снять трассу и посмотреть
- →О чём говорит размер кеша присваиваемости рядом с числом инстанцирований?
- →Какие конструкции типов чаще всего дают такой взрыв инстанцирований?
Читайте профиль сверху вниз. Парсинг и биндинг — 2.1 секунды на 1842 файла: ввод-вывод и объём исходников тут ни при чём. Всё время сидит в проверке. А 48 миллионов инстанцирований на 1842 файла — это примерно 26 000 инстанцирований на файл: типы раскрываются лавиной.
Трасса показывает виновника. Обычно это дженерик, который переинстанцируется на каждом использовании, потому что компилятору негде закешировать результат:
// Каждое обращение к DeepPartial<T> раскрывает всё дерево заново
type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] };
function patch<T extends object>(base: T, delta: DeepPartial<T>): T { /* ... */ }
Лечение — сузить работу типов, а не купить процессор: заменить рекурсивный mapped type на явный интерфейс на границе, поставить аннотации там, где вывод переинстанцирует дженерик, и разрезать пакет так, чтобы дорогое дерево типов жило в одном проекте. После правки перепроверьте по тому же счётчику Instantiations — падение на порядок и есть доказательство.