Миграцию с JavaScript на TypeScript оценили в 2 недели. Ушло 19 рабочих дней
В 17:20 в пятницу менеджер сказал в Zoom: "Там без изменения логики, просто перевести фронт на TypeScript".
На 11-й рабочий день в чате уже висело другое сообщение: "А почему у нас заказ иногда без deliveryAddress?"
Вот примерно в этот момент стало понятно, что типы тут не главный объем.
Где сломалась оценка
Приложение у клиента работало. E-com кабинет, обычный React, несколько витрин, личный кабинет, интеграция с CRM и складом. Снаружи все выглядело спокойно: заказы создаются, статусы меняются, Озон и ВБ рядом не взрываются.
Стартовая оценка держалась на допущении, что код уже описывает домен, а TypeScript нужно наложить сверху. Ну ок, поставить tsconfig, переименовать файлы, пройтись по компонентам, убрать очевидные any.
Но код домен не описывал. Он его угадывал.
Часть данных приходила из API в одной форме, часть собиралась на фронте, часть лежала в localStorage с прошлой версии кабинета. И пока это был JS, проект просто молча ехал дальше.
19 дней по слоям
Если разложить фактический срок, получилось не очень похоже на "типизацию".
4 дня ушло на прямую миграцию: настройка tsconfig, переименование файлов, типы для props, базовые интерфейсы, поправить импорты. Это была та самая работа, которую все и представляли на созвоне.
Еще 5 дней забрал implicit any. Там всплыли функции, которые принимали объект заказа, строку, иногда массив, а возвращали то boolean, то объект ошибки, то вообще undefined, если "так исторически сложилось".
4 дня ушло на API-контракты. Документация в Confluence говорила одно, фактический ответ бэка через devtools показывал другое. Поле phone могло быть строкой, null и числом. Поле items иногда приходило пустым объектом. Не массивом.
3 дня съели сборка и тулчейн: старые плагины Babel, алиасы, mixed CJS/ESM, тесты на Jest, которые до этого держались на честном слове.
2 дня - побочные баги после ужесточения проверок. Еще 1 день - ожидание решений от клиента: "это чинить на фронте или бэк поправит контракт?"
Итого 19 рабочих дней.
Самые дорогие места были не в типах
По работе на проектах я уже видел этот фокус: TypeScript редко сам по себе съедает оценку. Он просто включает свет.
В одном helper для расчета суммы была ветка "если промокод", ветка "если юрлицо" и ветка "если старый заказ". Сигнатура была примерно как у черной дыры: принимает все, возвращает что получится. Пока UI показывал правильную цифру в 95% случаев, никто туда не лез.
После типизации выяснилось, что два экрана передают разные формы одной и той же сущности. В одном месте discount был числом, в другом объектом с value и type. Оба варианта "работали", потому что перед рендером стоял слой условных проверок.
Самый неприятный кусок был не в React. Посыпались тесты и сборка. Один алиас резолвился Vite, но не резолвился Jest. Один пакет жил как CommonJS, другой уже как ESM. Copilot бодро предлагал типы, но чинить это все равно пришлось руками.
Когда это нельзя оценивать как проставить типы
Если между фронтом и API нет стабильного контракта, миграция перестает быть миграцией. Это уже инвентаризация данных.
Если в проекте много универсальных helper-функций без явных входов и выходов, это не "быстро типизируем". Это раскопки бизнес-логики, которую когда-то засунули в удобное место.
Если сборка старая, моки динамические, тесты запускаются "только у Пети", а структура данных проверяется глазами в devtools, то 2 недели лучше сразу делить на две части: исследование и работа. И отдельно держать буфер на дефекты, которые были в проекте до TypeScript.
На бумаге это была миграция на TypeScript. По факту - инвентаризация проекта, которую просто долго называли по-другому.
Потом перестали.