Breaking changes

Breaking changes — изменения, нарушающие обратную совместимость. При обновлении библиотеки важно знать, что именно изменилось, чтобы не потратить время на отладку регрессий.


Изменение сигнатуры LocaleFunc

Одно из ключевых изменений между версиями — стандартизация возвращаемого типа функции локали.

Старый формат (некоторые ранние версии):

// Возвращала только строку или массив строк переменной длины
register('old', (n, i) => 'давно');
register('old', (n, i) => ['давно', 'скоро', 'только что']);

Новый формат (3.x, 4.x — обязательно):

// Строго кортеж из двух строк: [прошлое, будущее]
register('new', (n, i) => ['давно', 'скоро']);

Код с неверным возвращаемым типом молча работает некорректно — без исключений.


Изменение путей ESM импортов

До 3.x:

import timeago from 'timeago.js';
const { format, render, cancel, register } = timeago;

С 3.x — именованные экспорты:

import { format, render, cancel, register } from 'timeago.js';

Дефолтный импорт может не работать в ESM-режиме. Старый код с деструктуризацией из default требует обновления.


Изменение путей локалей

До 3.x:

const ru = require('timeago.js/locales/ru'); // Старый путь

С 3.x:

import ru from 'timeago.js/esm/lang/ru';      // ESM
const ru = require('timeago.js/lib/lang/ru'); // CJS

Изменение типов TypeScript

В 3.x появились официальные типы. Если проект использовал @types/timeago.js (сообщественный пакет), возможны конфликты:

npm uninstall @types/timeago.js  # Удалить — типы встроены в пакет

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


Изменение поведения format для невалидных дат

В некоторых версиях format с невалидной датой:

  • Возвращал строку “NaN years ago”.
  • Бросал исключение.
  • Возвращал “just now” или “только что”.

Поведение нестабильно между версиями. Самостоятельная проверка на валидность обязательна:

const d = new Date(input);
const result = isNaN(d.getTime()) ? fallback : format(d, locale);

Удаление глобальной переменной

В ранних версиях библиотека могла добавлять window.timeago:

<!-- Старый CDN -->
<script src="timeago.js"></script>
<script>
  timeago.format(date); // глобальная переменная
</script>

Современные версии используют модульную систему. CDN-версия может не создавать глобал или создавать с другим именем.


Изменение расположения конфига relativeDate

В некоторых версиях:

format(date, locale, relativeDate); // Третий аргумент — дата, не объект

В текущей версии:

format(date, locale, { relativeDate: date }); // Третий аргумент — объект опций

Обнаружение breaking changes при обновлении

# Сравнить версии
npm show timeago.js versions --json

# Обновить и сразу запустить тесты
npm update timeago.js && npm test

Если тесты покрывают вызовы format, render, cancel — регрессия будет поймана автоматически.


Проверка CHANGELOG

# Просмотреть историю изменений на GitHub
# github.com/hustcc/timeago.js/blob/master/CHANGELOG.md

# Или через npm
npm show timeago.js changelog

Написание миграционного теста

// Перед обновлением — зафиксировать текущее поведение
describe('timeago.js behavior contract', () => {
  const NOW = new Date('2025-06-01T12:00:00Z').getTime();

  beforeAll(() => { jest.setSystemTime(NOW); });

  it('format возвращает строку для Date', () => {
    expect(typeof format(new Date(NOW - 60_000), 'ru')).toBe('string');
  });

  it('format возвращает непустую строку', () => {
    expect(format(new Date(NOW - 60_000), 'ru').length).toBeGreaterThan(0);
  });

  it('render не бросает для валидного элемента', () => {
    const el = document.createElement('time');
    el.setAttribute('datetime', new Date(NOW - 60_000).toISOString());
    document.body.appendChild(el);
    expect(() => render(el, 'ru')).not.toThrow();
    cancel(el);
    document.body.removeChild(el);
  });
});

Запустить до и после обновления — если тесты проходят в обоих случаях, обновление безопасно.


Стратегия при обнаружении breaking change

  1. Прочитать CHANGELOG для понимания масштаба.
  2. Написать тест, воспроизводящий проблему.
  3. Внести минимальное изменение в код приложения.
  4. Убедиться, что тест проходит.
  5. Запустить полный набор тестов.
  6. Обновить версию в lock-файле через npm ci.