Стратегии миграции

Стратегия миграции определяет, как перейти с одной версии timeago.js на другую (или на другую библиотеку) с минимальным риском для production и минимальными затратами на изменение кода.


Стратегия 1: Прямая замена

Подходит для небольших проектов с ограниченным использованием timeago.js.

  1. Создать ветку chore/timeago-upgrade.
  2. Обновить пакет.
  3. Запустить TypeScript и тесты.
  4. Исправить все ошибки.
  5. Смёрджить.
git checkout -b chore/timeago-upgrade
npm install timeago.js@^4.0.0
npx tsc --noEmit
npm test
git commit -am "chore: upgrade timeago.js to v4"

Риск: При наличии breaking changes все проблемы выявляются сразу.


Стратегия 2: Адаптерный слой

Для больших проектов, где timeago.js используется во многих местах.

// src/lib/timeago.ts — адаптер
import { format as _format, render as _render, cancel as _cancel, register } from 'timeago.js';

export type DateInput = Date | string | number;
export type Locale    = 'ru' | 'en_US' | 'de' | 'fr';

// Обёртки фиксируют совместимый API для всего приложения
export const format = (date: DateInput, locale: Locale = 'ru'): string => {
  return _format(date, locale);
};

export const render = (el: Element | Element[], locale: Locale = 'ru'): void => {
  _render(el, locale);
};

export const cancel = (el?: Element | Element[]): void => {
  _cancel(el);
};

export { register };

При обновлении изменяется только адаптер. Остальной код не трогается.


Стратегия 3: Постепенная миграция

Для смены библиотеки (например, переход с timeago.js на Intl.RelativeTimeFormat):

// Фаза 1: Добавить новую реализацию под флагом
const USE_NATIVE = process.env.FEATURE_NATIVE_RELATIVE_TIME === 'true';

function formatDate(date: Date, locale = 'ru'): string {
  if (USE_NATIVE) {
    return formatNative(date, locale); // Новая реализация
  }
  return format(date, locale);         // timeago.js
}

Постепенно включать флаг для части пользователей, наблюдать за метриками, затем переключить полностью.


Стратегия 4: Параллельный запуск

// Обе реализации запускаются одновременно, результаты сравниваются
function dualFormat(date: Date, locale = 'ru'): string {
  const v3Result = oldFormat(date, locale);
  const v4Result = newFormat(date, locale);

  if (v3Result !== v4Result) {
    console.warn('Format mismatch:', { v3: v3Result, v4: v4Result, date, locale });
  }

  return v3Result; // В production используется старый результат
}

Обнаружить все расхождения до переключения.


Адаптация кастомных локалей

Если кастомные локали написаны для старого API, их нужно адаптировать:

// Старый формат — проверить, что возвращает именно кортеж
const oldLocale = (n, i) => {
  const forms = [...];
  return forms[i]; // Возможно, возвращал строку в старых версиях
};

// Новый формат — явный кортеж
const newLocale = (n, i): [string, string] => {
  const forms: [string, string][] = [...];
  return forms[i] ?? ['давно', 'скоро'];
};

Миграция ESM импортов

// До: CommonJS стиль или старый путь
const { format } = require('timeago.js');
import ru from 'timeago.js/locales/ru'; // Старый путь

// После: именованный ESM импорт
import { format, register } from 'timeago.js';
import ru from 'timeago.js/esm/lang/ru'; // Новый путь

Миграция TypeScript типов

// До: использование @types/timeago.js или самописные декларации
// @types/timeago.js — удалить
// Самописный .d.ts — заменить

// После: встроенные типы
import type { LocaleFunc, FormatOptions } from 'timeago.js';

Автоматическое обнаружение устаревших паттернов

// .eslintrc.js — запретить устаревшие паттерны
module.exports = {
  rules: {
    'no-restricted-imports': ['error', {
      name: 'timeago.js',
      importNames: ['default'],
      message: 'Используйте именованные импорты: import { format } from "timeago.js"',
    }],
  },
};

Чеклист стратегии миграции