Автоматизация миграции

Миграция на date-fns в крупных проектах редко представляет собой одномоментную замену одной библиотеки другой. Обычно процесс затрагивает множество слоёв: утилиты форматирования, парсинг дат, работу с таймзонами, бизнес-логику и тестовые сценарии. Автоматизация в этом контексте направлена на снижение количества ручных правок и минимизацию риска регрессий.

Ключевые стратегии автоматизации сводятся к трём направлениям:

  • синтаксическая трансформация кода через AST (codemods)
  • внедрение промежуточного слоя совместимости
  • контроль миграции через линтеры и тестовую инфраструктуру

Каждое направление решает свою задачу и часто используется совместно.

Инвентаризация использования дат в кодовой базе

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

  • moment() и цепочки вызовов (add, subtract, format)
  • new Date() с ручными вычислениями
  • кастомные утилиты форматирования
  • смешанные подходы внутри одного модуля

Типичная задача — выявить все места, где требуется замена на функции date-fns, такие как:

  • format
  • parse
  • addDays, subDays
  • differenceInDays
  • isAfter, isBefore

Автоматизация невозможна без полного понимания покрытия.

Кодмоды как основной механизм трансформации

Наиболее мощный инструмент автоматизации — codemod-скрипты на базе jscodeshift. Они позволяют модифицировать AST JavaScript-кода и выполнять структурные замены.

Пример базового кодмода, заменяющего moment().add на date-fns add:

export default function transformer(file, api) {
  const j = api.jscodeshift;
  const root = j(file.source);

  root
    .find(j.CallExpression, {
      callee: {
        object: {
          callee: { name: 'moment' }
        },
        property: { name: 'add' }
      }
    })
    .forEach(path => {
      const args = path.node.arguments;

      j(path).replaceWith(
        j.callEx * pression(
          j.identifier('add'),
          [
            j.callEx * pression(j.identifier('moment'), []),
            ...args
          ]
        )
      );
    });

  return root.toSource();
}

В реальных миграциях такие трансформации усложняются:

  • учитываются цепочки вызовов
  • обрабатываются разные единицы времени
  • сохраняется контекст локализации

Преобразование Moment.js API в Date-fns

Одним из самых сложных этапов является миграция цепочечных API moment в функциональный стиль date-fns.

Пример различий

Moment.js:

moment(date).add(2, 'days').format('yyyy-MM-DD');

Date-fns:

import { addDays, format } from 'date-fns';

format(addDays(date, 2), 'yyyy-MM-dd');

Автоматизация такой трансформации требует перестройки AST в несколько шагов:

  1. извлечь исходный аргумент даты
  2. преобразовать методы add, subtract
  3. заменить format
  4. перестроить порядок вызовов в функциональный

Автоматизация импортов

Отдельная задача — управление импортами. При миграции важно не только заменить вызовы, но и корректно добавить нужные функции:

import { addDays, format, parseISO } from 'date-fns';

Codemod может автоматически агрегировать используемые функции:

  • анализировать добавленные вызовы
  • собирать уникальные идентификаторы
  • формировать оптимизированный import statement

Дополнительно применяются правила сортировки и дедупликации импортов.

Работа с форматированием дат

Одной из проблем миграции является несовместимость форматов. moment использует собственную систему токенов, тогда как date-fns требует стандартизированных шаблонов.

Пример автоматической замены:

Moment date-fns
YYYY yyyy
DD dd
HH HH
mm mm

Codemod может выполнять строковые преобразования:

function convertFormatTokens(formatStr) {
  return formatStr
    .replace(/YYYY/g, 'yyyy')
    .replace(/DD/g, 'dd')
    .replace(/mm/g, 'MM');
}

При масштабной миграции важно учитывать контексты, где строка форматирования может быть динамической.

Локализация и региональные настройки

date-fns требует явного подключения локалей, в отличие от некоторых старых библиотек.

Автоматизация включает:

  • добавление импортов локалей
  • замену строковых идентификаторов
  • нормализацию конфигурации

Пример:

import { ru } from 'date-fns/locale';

и использование:

format(date, 'dd MMMM yyyy', { locale: ru });

Codemod может автоматически определять региональные параметры, если они зашиты в конфигурационных файлах или переменных окружения.

ESLint как механизм контроля миграции

После первичной автоматической трансформации требуется предотвращение возврата к старым паттернам.

Для этого используются кастомные ESLint-правила:

  • запрет moment()
  • запрет chain-методов даты
  • контроль использования new Date() в бизнес-логике

Пример правила:

module.exports = {
  create(context) {
    return {
      CallEx * pression(node) {
        if (node.callee.name === 'moment') {
          context.report({
            node,
            message: 'Использование moment запрещено, используйте date-fns'
          });
        }
      }
    };
  }
};

ESLint становится инструментом поддержания консистентности после миграции.

Промежуточный слой совместимости

В больших системах миграция редко происходит мгновенно. Для снижения риска вводится compatibility layer:

import { addDays as dfAddDays } from 'date-fns';

export function addDays(date, amount) {
  return dfAddDays(date, amount);
}

Преимущества подхода:

  • возможность постепенной замены вызовов
  • централизованный контроль поведения
  • упрощение отката изменений

Автоматизация может генерировать такие обёртки на основе анализа использования API.

Инкрементальная миграция модулей

Автоматизация часто строится по модульному принципу:

  1. выделение независимых пакетов
  2. миграция утилитного слоя
  3. миграция сервисов
  4. миграция UI-слоя

Каждый этап сопровождается отдельными codemod-скриптами.

Пример стратегии:

  • сначала преобразуются чистые функции
  • затем бизнес-логика
  • затем UI-компоненты с форматированием дат

Такой порядок снижает количество конфликтов и упрощает отладку.

Тестирование автоматических трансформаций

Любая автоматизация требует валидации корректности преобразований.

Используются следующие методы:

  • snapshot-тестирование AST-результатов
  • сравнение выходных значений функций дат
  • параллельный прогон старой и новой реализации

Пример теста:

expect(format(addDays(new Date('2020-01-01'), 2), 'yyyy-MM-dd'))
  .toBe('2020-01-03');

Также применяются тестовые наборы с историческими данными, чтобы исключить дрейф поведения.

Интеграция в CI/CD пайплайн

Автоматизация миграции становится частью сборочного процесса:

  • запуск codemod на pull request
  • проверка ESLint-правил
  • контроль появления запрещённых импортов
  • сравнение тестовых результатов

Типичный сценарий:

  1. разработчик добавляет новый код
  2. CI запускает проверку на использование legacy API
  3. при нарушении pipeline блокируется
  4. предлагается автоматический фикс через codemod

Таким образом миграция превращается из разовой операции в контролируемый процесс.

Масштабирование автоматизации на большие кодовые базы

В проектах с сотнями тысяч строк кода появляются дополнительные сложности:

  • неоднородные паттерны использования дат
  • устаревшие утилиты поверх moment
  • сторонние зависимости

Для решения применяются:

  • многоуровневые codemod-цепочки
  • частичная миграция через feature flags
  • анализ зависимостей через static code analysis

Автоматизация в таких условиях становится не скриптом, а системой трансформации кода, включающей анализ, изменение и проверку результата на каждом этапе.