Кривой Typescript тип выжрал 15 гигов оперативы

—

от автора

Всем привет! В этой статье хочу рассказать об одном интересном(для меня) случае, который произошел со мной на прошлой неделе.

Посреди дня поступила новая задача. Я с охотой принялся вайбкодить. Для тасок такого рода довольно обычный флоу работы: верстка форм по макету, добавление новых полей в Zod схему, обновление JSX , вывод типов из Zod схемы, etc.

В процессе работы замечаю, что VSCode начал лагать: не работал вывод типов, автокомплит, подсказки.

Я уже замечал подобное около полугода назад, и в тот момент помогло отключение расширение GitHub Copilot (нашел ишью на гитхабе).

Сейчас я опять подумал на проблему с расширением и начал искать ее. Для дебага проблем в VSCode есть инструмент, который запускается через Help > Start Extension Bisect.

Он поочередно дизейблит расширения и спрашивает воспроизводится баг или нет. Таким способом, подозрения пали на расширение гитлаба. Забизейблив его, ситуация улучшилась. Вывод типов появился и автокомплит тоже.

Однако, через пару часов ситуация ухудшилась, поискав ишью на гитхабе об этой проблеме (не работающий по-сути vscode) я ничего не нашел.

Попробовал ребутнуть комп — помогло, vscode снова заработал, ура подумал я!

Но спустя какое-то время ситуация повторилась, это был вечер пятницы, конец рабочего дня, ладно, может какая-то проблема с антивирусами или еще с каким-то корпоративным ПО, которое установлено на ноуте, подумал я. Вернусь в понедельник, и все заработает

ПОНЕДЕЛЬНИК

Как полагается проблема осталась. Ребут снова помог временно, после этого вдобавок начал лагать уже и хром.

В середине рабочего дня пользоваться ноутом уже стало невыносимо. Решил открыть «Мониторинг системы». Открываю «Память» и офигеваю. При запущенных на компе Spotify, Chrome, VSCode и Time используется 28 гигов оперативы из 32 доступных!

Начинаю смотреть, что там за процессы такие: все процессы хрома в сумме занимают 6 гб, много конечно, но ок, смотрю дальше и вижу полотно запущенных node процессов. Каждый из них занимает ~700 мб оперативы. Что за процесс такой — думаю я. Открываю трейс и вижу, что запущена команда вида:

npm exec tsc --project /Users/Developer/projects/front/node_modules/.federation/tsconfig.bc0bb64cb574b1df5f6fb9bf2cef34a9.json (94412)

Она висит уже 13 минут и не может завершиться. И таких команд висят десятки:

Смотрю инициатора: какая-то либа из node_modules. По итогу понимаю, у меня запущен vite dev-сервер, и в нем либа @module-federation примерно раз в 5 мин запускает генерацию контракта для микрофронта. За час получается 12 висячих node процессов.

Окей, то что либа генерит какой-то неиспользуемый контракт в папку неиндексируемую гитом это ок, но почему генерация контракта выполняется так долго и по-сути вообще не заканчивается?

Ранее, этим же днем, давая простые таски клоду, я замечал, что он выполняет их очень долго, по 30-60 минут, задачи которые делать клоду 2 мин от силы. Заметил висячую уже 20 минут shell команду npm run check . Что-то странное подумал я и просто отменил ее.

По логике — если генерация d.ts контракта выполняется слишком долго, то логично попробовать найти ошибку в типах. Это я и начал делать. Однако явных проблем косаемо типов я не заметил.

Тогда на ум приходит посмотреть всю цепочку того как TS собирает контракт, я запустил ту же самую команду с флагом --generateTrace и заметил что компилятор упёрся в предел глубины инстанцирования типов и продолжал пересчитывать одну и ту же пару типов больше 500 000 раз подряд, не завершаясь:

Вывод компилятора

Вывод компилятора

И происходит это на одном конкретном файле, назовем его useSomeForm.tsx. На первый взгляд ничего подозрительно — в нем описывается форма в связке React Hook Form + Zod. При детальном рассмотрении замечаю корень проблемы:

// useSomeForm.tsxconst getBaseSchema = () => {  return z.object({    // fields here  })}export type BaseSchemaInferred = ReturnType<typeof getBaseSchema>;

В другом файле:

// SomeForm.tsxconst { control } = useFormContext<BaseSchemaInferred>();

То есть получалась такая цепочка: React Hook Form под капотом рекурсивно строит тип Path (все возможные строки‑пути до полей формы), обходя каждое поле T. Когда вместо простого объекта я ему подставил весь внутренний тип Zod‑схемы (с её union’ами.or(),.extend() и собственной рекурсивной generic‑структурой), два рекурсивных generic‑механизма (zod ↔ React Hook Form) начинали бесконечно вызывать друг друга.

Корректный вариант должен был выглядеть следующим образом:

 export type BaseSchemaInferred = z.infer<BaseSchema>;

В большом файле, который описывает множество полей формы с валидацией и наследованием такую ошибку было сложно заметить.

Подводя итог хочется сделать вывод о том что нужно быть внимательное и вдумчивее читать тот код, который пишет ИИ.

Спасибо за внимание 🙂

ссылка на оригинал статьи https://habr.com/ru/articles/1105172/