Чистый JS умер. Да здравствует TS?
В 2026 году писать на чистом JavaScript для серьезного проекта — это как сдавать тесты без санитайзера. Вроде работает, но в любой момент может рвануть. Наш легаси-монолит на JS дорос до 200 000 строк — и это только бизнес-логика, без тестов и конфигов. Типизация стала необходимостью, как кислород. Но миграция силами команды? Три месяца ада. Решили рискнуть — доверить процесс Claude Code.
Не путайте с простым переименованием .js в .ts. Нам нужно было не просто расширение, а полноценная типизация с кастомными дженериками, строгими интерфейсами и вычищенным any.
Звучит как самоубийство? Возможно. Но мы пошли другим путем. И выжили.
Почему Claude Code, а не сборник codemods?
Ручные codemods на jscodeshift — это точечная артиллерия. Они хороши для замены конкретных паттернов, но не справляются с семантикой: «этот объект на самом деле User, а этот — Product». Claude Code же работает как AI-агент: анализирует контекст, использует окно в 200K токенов и может генерировать типы на основе структуры кода. В статье Cursor против Warp против Claude Code мы уже сравнивали инструменты — Claude выиграл по качеству рефакторинга. Он умеет держать в голове всю кодовую базу модуля, а не кусок.
Готовим почву: контекст-инжиниринг и бутерброд с промптами
Просто сказать «переведи всё на TS» — верный способ получить 10K ошибок компиляции. Подход позаимствовали из статьи Контекст-инжиниринг для coding-агентов: подали Claude Code структуру проекта, примеры типов, tsconfig и правила игры.
Вот как выглядел наш seed-промпт:
claude code --context-file ./migration-context.md --prompt """
Ты — старший разработчик TypeScript. Мигрируешь проект с JS на TS.
Правила:
- Все новые файлы — .ts. Старые .js не трогать.
- Для каждого модуля создавай отдельный файл с типами в папке @types/module.
- Избегай any. Используй дженерики если не уверен.
- Декларации для сторонних библиотек — в отдельный файл.
- Включай strict:true.
- После миграции каждого файла запускай tsc --noEmit и исправляй ошибки.
"""
Идем по слонам: разбиение на модули
Делить код на 200 штук — можно. Но лучше кластеризовать по смыслу. Мы разбили на 12 доменных модулей (users, orders, payments и пр.) и отдавали Claude Code по одному модулю за раз. В каждом сеансе — не больше 2000 строк, чтобы модель не теряла фокус.
Процесс:
1 Анализ AST и поиск зависимостей
Перед запуском AI прогнали код через статический анализатор, чтобы выявить циклические импорты и неиспользуемые переменные — их лучше вычистить заранее, иначе Claude Code может плохо угадать типы.
2 Генерация базовых интерфейсов
Первый проход — только типы. Модель создавала файл types.ts для модуля, описывая все используемые сущности. Проверяли вручную — 80% угадывала с первого раза. Остальное докручивали промптами «не хватает полей, посмотри на использование».
3 Итеративная типизация файлов
Каждый .js файл превращался в .ts с внедрением типов. Claude Code запускал tsc --noEmit и автоматически исправлял ошибки. Если ошибка не фиксилась с трех попыток — файл маркировался как «ручная доработка».
Цифры, которые заставят плакать или радоваться
| Метрика | Claude Code | Ручная работа (оценка) |
|---|---|---|
| Общее время | 3 дня (72 часа работы AI + 20 часов проверки) | ~60 рабочих дней (3 месяца для команды из 5 чел) |
| Файлов переведено | 1850 из 2000 (92.5%) | 100% |
| Доля строгих типов (strict: true, без any) | 67% | ~85% (лучше контролируем) |
| Баг в продакшене после миграции (в первые 2 недели) | 34 | ~12 (примерно) |
| Стоимость токенов Claude Code | ~$340 (включая исправление ошибок) | $0 (только зарплата разработчиков) |
34 бага — звучит страшно. Но сравните: 3 месяца ручной работы обошлись бы компании в $75 000 (средняя ставка сеньора). А 34 бага за 3 недели — это примерно 1.6 бага в день, что укладывается в нормальный темп продакшен-релизов. Большая часть багов — из-за неправильно угаданных типов для динамических импортов.
Зона боли: конкретные грабли
Проблема 1: Динамические require и плагины
Код был на CommonJS с require, которые импортировали модули по условию. Claude Code честно переводил в import, но забывал про dynamic imports. Получалось:
// Было
const adapter = require('./adapters/' + type);
// Стало (сгенерировано неверно)
import adapter from './adapters/' + type; // Синтаксическая ошибка
// Нужно
const adapter = await import(`./adapters/${type}`);
Пришлось добавить к промпту правило: «если require используется с динамической строкой — оставь его как dynamic import или замени на await import».
Проблема 2: Any-шлюзы и потерянный контекст
Когда модуль импортировал что-то из непереведенного .js файла, Claude Code по умолчанию ставил any. Это создавало цепную реакцию: 30% типов терялись. Решение — перед миграцией модуля форсированно переводить все его зависимости. Помогла тактика из статьи Как переписать миллион строк за 9 дней: обрабатывали граф зависимостей топологически.
Проблема 3: Странные типы для enum-подобных объектов
В JS использовались const для псевдо-енумов. Claude Code иногда генерировал вместо union-типов просто string. Пришлось добавить в контекст конкретные примеры конвертации.
Антипаттерн: слепо доверять AI при добавлении типов к объектам с вложенными данными. Всегда проверяй, не потерялись ли поля.
Что мы предприняли, чтобы снизить баги?
Во-первых, пропустили через Claude Code не всю базу сразу, а частями, каждый раз проверяя PR. Да, это замедлило процесс, но без этого багов было бы под сотню. Во-вторых, использовали горячие клавиши Claude Code для быстрых ретраев — если tsc выдавал ошибку, модель по Ctrl+R запускала исправление.
В-третьих, написали автоматические тесты на основе property-based testing (fast-check), которые генерировали данные на основе выведенных типов. Если тест падал — значит тип не соответствует реальному формату данных в продакшене.
FAQ: вопросы, которые вы боитесь задать
Вопрос: Стоит ли вообще автоматизировать миграцию, если есть риск багов?
Если у вас больше 50К строк кода — да. Ручная миграция такой базы почти гарантированно внесёт свои баги (человеческий фактор). AI хотя бы типизирует однотипно. Но готовьтесь к периоду стабилизации в 2-3 недели.
Вопрос: Какую версию Claude Code использовали?
На момент эксперимента (май 2026) — Claude Code v2.5 с моделью Claude Opus 4.0. Разница с предыдущей версией — в лучшем понимании дженериков и рекурсивных типов.
Вопрос: Почему не использовали jscodeshift или ts-migrate от Airbnb?
Эти инструменты хорошо находят типы для стандартных паттернов, но плохо справляются с бизнес-спецификой (например, кастомные функции-обертки). Claude Code показал себя гибче за счет контекста. Однако для простой замены var на let мы всё равно прогнали ручные коды — AI не нужен для тривиальщины.
Вопрос: Как тестировали результат?
Юнит-тесты (Jest), интеграционные тесты + мониторинг на Sentry. Первые дни после выкатки — режим полного логирования и мгновенного отката по кнопке.
На что потратить сэкономленное время?
Мы думали, что сэкономим 2.5 месяца. Реально — около 2 месяцев. Но сэкономленное время ушло на фикс багов и рефакторинг типов. Чистая экономия — около 40% трудозатрат. Неплохо. Но не обольщайтесь: Claude Code — не серебряная пуля, а мощный, но глуповатый помощник, который не понимает бизнес-логику. Каждый второй файл требует ручной корректировки.
Если вы решите повторить наш путь — советую сначала прочитать GestaltSyntax — семантический пресс для старого кода, особенно если ваш JS похож на фортран. И не забывайте про сравнение моделей для TypeScript — возможно, для вашей кодовой базы лучше подойдет Claude Opus, а не быстрая версия.
И последнее: если после миграции вы не задумались о рефакторинге самой архитектуры — вы просто перенесли грязь из JS в TS. Типизация обнажает проблемы, но не лечит их. Возможно, настоящая причина багов — не отсутствие типов, а кривая доменная модель. И никакой AI вам здесь не поможет.