TS миграция с JavaScript
Миграция с JavaScript на TypeScript — постепенный процесс добавления статической типизации в существующий JS-проект без полной переписки кода, с помощью пошагового ужесточения настроек компилятора.
Зачем нужно
- TypeScript полностью совместим с JavaScript — можно мигрировать файл за файлом
- Статическая типизация сразу начинает выявлять скрытые ошибки в унаследованном коде
- Постепенная миграция снижает риски: проект продолжает работать на каждом шаге
Где используется
- Любой существующий JS-проект (React-приложение, Node.js сервер, библиотека)
- Монорепозитории — миграция отдельных пакетов
- Проекты с нетипизированными зависимостями (legacy npm-пакеты)
Основной контент
Стратегия миграции (4 шага)
Шаг 1: Настройка компилятора (мягкий режим)
tsconfig.json с allowJs: true, checkJs: false, noImplicitAny: false
Шаг 2: Переименование файлов .js → .ts по одному
Компилятор даёт ошибки — исправляем постепенно
Шаг 3: Устранение implicit any
noImplicitAny: true — аннотируем параметры функций
Шаг 4: Включение strict-режима
strict: true — strictNullChecks, strictFunctionTypes и т.д.
Начальный tsconfig.json для миграции
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"allowJs": true,
"checkJs": false,
"outDir": "./dist",
"rootDir": "./src",
"strict": false,
"noImplicitAny": false,
"skipLibCheck": true,
"esModuleInterop": true
},
"include": ["src"]
}
Типизация существующих функций
До (JavaScript):
function processOrder(order, user, options) {
if (!options.notify) return;
sendEmail(user.email, `Order ${order.id} processed`);
}
После (TypeScript — постепенно):
interface Order { id: number; total: number; }
interface User { id: number; email: string; name: string; }
interface ProcessOptions { notify: boolean; priority?: "high" | "low"; }
function processOrder(order: Order, user: User, options: ProcessOptions): void {
if (!options.notify) return;
sendEmail(user.email, `Order ${order.id} processed`);
}
Declaration files для JS-модулей
Для JS-файлов без миграции создаём .d.ts:
// legacy/utils.d.ts
export function formatDate(date: Date, locale: string): string;
export function slugify(text: string): string;
export const VERSION: string;
@types — типы для npm-пакетов
# Установка типов для популярных пакетов
npm install -D @types/lodash @types/node @types/express
# Проверить наличие типов
npm info lodash types # встроенные типы
npm info @types/lodash # отдельный пакет типов
// После установки @types/lodash — полная поддержка типов
import _ from "lodash";
const result = _.groupBy([1, 2, 3, 4], n => n % 2 === 0 ? "even" : "odd");
// result: Dictionary<number>
Подавление ошибок при миграции (временно)
// @ts-ignore — игнорировать следующую строку (избегать)
// @ts-ignore
const legacy = require("./old-module");
// @ts-expect-error — ожидаем ошибку (удалить когда исправим)
// @ts-expect-error TODO: типизировать этот модуль
const oldLib = require("./untyped-lib");
// as any — временное приведение (добавить eslint-disable комментарий)
const data = fetchLegacyData as any;
Финальный tsconfig.json (strict-режим)
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": true
}
}
Частые ошибки
- Мигрировать весь проект сразу — лучше начать с одного файла или модуля
- Использовать
anyкак долгосрочное решение — накапливается технический долг по типизации - Включать
strict: trueс первого дня — вызывает лавину ошибок в legacy-коде; лучше включать постепенно - Не добавлять
@types/*— типы для популярных библиотек уже готовы, не нужно писать вручную - Игнорировать ошибки
checkJs— файлы.jsтоже можно проверять, добавляя JSDoc-аннотации