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-аннотации

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

Ресурсы