Миграция JS в TS: стратегия

Миграция JavaScript-проекта в TypeScript — пошаговый процесс: от добавления tsconfig с мягкими настройками и переименования файлов до постепенного устранения any и включения строгого режима.

Зачем нужно

Единовременная полная типизация крупного JS-проекта нереалистична. Постепенная миграция позволяет добавить TypeScript в работающий продукт без остановки разработки, получая пользу сразу по мере продвижения.

Где используется

  • Legacy frontend-проекты (React/Vue без TypeScript)
  • Node.js backends, переходящие на TypeScript
  • npm-библиотеки, добавляющие типы для пользователей
  • Монорепозитории с постепенным переходом

Основной контент

Шаг 1: Начальная настройка (мягкий режим)

// tsconfig.json — минимально строгий для начала
{
  "compilerOptions": {
    "target": "ES2017",
    "module": "CommonJS",
    "allowJs": true,          // разрешить .js файлы
    "checkJs": false,         // не проверять .js пока
    "noImplicitAny": false,   // разрешить implicit any
    "strict": false,          // отключить строгий режим
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Шаг 2: Переименование файлов

# Переименовать по одному файлу или всё сразу
for f in src/**/*.js; do mv "$f" "${f%.js}.ts"; done

# Или постепенно — начать с утилит и типов данных
mv src/utils/format.js src/utils/format.ts
mv src/models/user.js src/models/user.ts

Шаг 3: Добавление базовых типов

// Было (JS):
function getUser(id) {
  return db.users.find(u => u.id === id);
}

// Стало (TS с any — первый шаг):
function getUser(id: string): any {
  return db.users.find(u => u.id === id);
}

// Финал — после добавления интерфейса:
interface User {
  id: string;
  name: string;
  email: string;
}

function getUser(id: string): User | undefined {
  return db.users.find(u => u.id === id);
}

Шаг 4: Установка @types пакетов

# Основные типы для Node.js и популярных библиотек
npm install -D @types/node
npm install -D @types/express
npm install -D @types/lodash
npm install -D @types/jest

# Проверить доступность типов
npm info lodash types
# или ищи @types/имя-пакета на npmjs.com

Шаг 5: Постепенное ужесточение tsconfig

// Этап 1 → 2: включить checkJs
{ "checkJs": true }

// Этап 2 → 3: включить noImplicitAny
{ "noImplicitAny": true }

// Этап 3 → 4: включить strictNullChecks
{ "strictNullChecks": true }

// Финал: включить полный strict
{ "strict": true }

Паттерны замены any

// any → unknown + narrowing
function process(data: any) { // было
  return data.name;
}

function process(data: unknown): string { // стало
  if (typeof data === "object" && data !== null && "name" in data) {
    return String((data as { name: unknown }).name);
  }
  return "";
}

// any → generic
function identity<T>(x: any): any { // было
  return x;
}

function identity<T>(x: T): T { // стало
  return x;
}

Работа с нетипизированными модулями

// Создать кастомные объявления типов
// src/types/legacy-module.d.ts
declare module "legacy-module" {
  export function doSomething(x: string): number;
  export const VERSION: string;
}

// Или быстрое решение (временно):
declare module "untyped-lib";
// всё из этого модуля будет any

Частые ошибки

  • Мигрировать всё сразу — лучше один файл за раз, начиная с утилит и типов данных.
  • Сразу включать strict: true — слишком много ошибок убьёт мотивацию; начните с мягкого режима.
  • Игнорировать @types пакеты — без типов библиотек весь код взаимодействия с ними будет any.
  • Не добавлять .d.ts для legacy кода — для JS-файлов которые нельзя переписать, создайте отдельные объявления типов.

Связанные темы

Ресурсы