При интеграции timeago.js в TypeScript-проекты возникает несколько типичных проблем: несоответствие типов, конфликты деклараций, ошибки при строгих режимах компилятора. Большинство решаются стандартными инструментами TypeScript.
import { format } from 'timeago.js';
const date: string | null = getDate(); // может вернуть null
format(date, 'ru'); // Ошибка: Argument of type 'string | null' is not assignable to...
Решение 1 — Non-null assertion (если гарантированно не null):
format(date!, 'ru');
Решение 2 — Условная проверка:
if (date !== null) {
format(date, 'ru'); // TypeScript знает: date — string
}
Решение 3 — Nullish coalescing:
format(date ?? new Date(), 'ru');
В strict-режиме входные данные могут иметь тип
unknown:
function processItem(item: unknown) {
format(item as any, 'ru'); // Небезопасно
}
// Безопасный вариант
function processItem(item: unknown): string | null {
if (
typeof item === 'string' ||
typeof item === 'number' ||
item instanceof Date
) {
return format(item, 'ru');
}
return null;
}
error TS2307: Cannot find module 'timeago.js' or its corresponding type declarations.
Решение 1 — Установить типы (если пакет поддерживает):
timeago.js включает типы в основной пакет, переустановить:
npm install timeago.js@latest
Решение 2 — Создать локальное объявление типов:
// types/timeago.js.d.ts
declare module 'timeago.js' {
type DateInput = Date | string | number;
export function format(date: DateInput, locale?: string): string;
export function render(nodes: Element | Element[], locale?: string): void;
export function cancel(nodes?: Element | Element[]): void;
export function register(locale: string, fn: (n: number, i: number) => [string, string]): void;
}
register('custom', (num, index) => {
return 'строка'; // Ошибка: нужен [string, string], не string
});
Правильное возвращаемое значение — кортеж:
register('custom', (num, index): [string, string] => {
const forms: [string, string][] = [
['только что', 'через секунду'],
// ...
];
return forms[index] ?? ['давно', 'скоро'];
});
const el = document.querySelector('time'); // HTMLElement | null
render(el, 'ru'); // Ошибка: Type 'null' is not assignable
Решение 1 — Проверка перед вызовом:
const el = document.querySelector('time');
if (el) render(el, 'ru');
Решение 2 — Non-null assertion:
render(document.querySelector('time')!, 'ru');
Решение 3 — Явная проверка с обработкой:
function safeRender(selector: string, locale = 'ru'): void {
const el = document.querySelector(selector);
if (el === null) {
console.warn(`Element not found: ${selector}`);
return;
}
render(el, locale);
}
При использовании esm-версии библиотеки:
// Ошибка: This expression is not callable
import timeago from 'timeago.js';
timeago.format(date);
Правильный именованный импорт:
import { format, render, cancel, register } from 'timeago.js';
// Попытка перегрузки — ошибка
function format(date: Date): string;
function format(date: string): string;
Функции из модуля не перегружаются напрямую. Используется declaration merging:
declare module 'timeago.js' {
export function format(date: Date): string;
export function format(date: string): string;
export function format(date: number): string;
export function format(date: Date | string | number, locale?: string): string;
}
interface Post { id: number; createdAt: string; }
const post: Post = { id: 1, createdAt: '2025-01-01' };
const enriched = { ...post, timeAgo: format(post.createdAt, 'ru') };
// Тип enriched: { id: number; createdAt: string; timeAgo: string; }
Тип не теряется — TypeScript корректно выводит пересечение. Проблема
возникает при попытке использовать enriched там, где
ожидается Post:
function accept(p: Post) { /* ... */ }
accept(enriched); // OK — лишнее поле timeAgo разрешено при передаче
Если объект типизирован через Record:
type PostRecord = Record<string, unknown>;
function formatRecord(record: PostRecord, dateField: string): string {
const value = record[dateField]; // тип: unknown
if (typeof value === 'string' || typeof value === 'number' || value instanceof Date) {
return format(value, 'ru');
}
throw new TypeError(`Field "${dateField}" is not a valid date`);
}
| Ошибка | Причина | Решение |
|---|---|---|
null не совместим с DateInput |
strictNullChecks: true |
Проверка или ?? |
unknown не совместим |
Входные данные без типа | Явная проверка типа |
| Module not found | Нет деклараций типов | Переустановить или создать .d.ts |
| LocaleFunc возвращает string | Неверный возвращаемый тип | Возвращать [string, string] |
| render принимает null | querySelector возвращает null | Проверка на null перед вызовом |
| Default import вместо named | Неверный синтаксис импорта | Использовать именованные импорты |