Паттерн ленивой загрузки откладывает инициализацию timeago.js до момента, когда она действительно нужна. Это ускоряет начальную загрузку страницы и снижает нагрузку на CPU.
Инициализация render для 200 элементов сразу создаёт 200
таймеров одновременно. Если большинство из них не видно пользователю
(например, находятся ниже fold или во вкладках), это бессмысленная
нагрузка.
// Основной бандл — timeago.js не включается
// Загружается только при первом использовании
async function initTimeago(elements) {
const { render, cancel } = await import('timeago.js');
const { default: ru } = await import('timeago.js/esm/lang/ru');
const { register } = await import('timeago.js');
register('ru', ru);
render(elements, 'ru');
return () => cancel(elements);
}
const initialized = new WeakSet();
const io = new IntersectionObserver(async (entries) => {
const toInit = entries
.filter(e => e.isIntersecting && !initialized.has(e.target))
.map(e => e.target);
if (toInit.length === 0) return;
const { render } = await import('timeago.js');
const { register } = await import('timeago.js');
const { default: ru } = await import('timeago.js/esm/lang/ru');
register('ru', ru);
toInit.forEach(el => {
initialized.add(el);
render(el, 'ru');
});
}, { threshold: 0.1 });
document.querySelectorAll('[datetime]').forEach(el => io.observe(el));
Загружать локаль только при первом использовании:
const loadedLocales = new Set();
async function formatLazy(date, locale = 'ru') {
if (!loadedLocales.has(locale)) {
const { register } = await import('timeago.js');
const mod = await import(`timeago.js/esm/lang/${locale}.js`);
register(locale, mod.default);
loadedLocales.add(locale);
}
const { format } = await import('timeago.js');
return format(date, locale);
}
import { useState, useEffect } from 'react';
function useLazyTimeAgo(date: Date | string, locale = 'ru'): string {
const [text, setText] = useState('');
useEffect(() => {
let cleanup: (() => void) | undefined;
import('timeago.js').then(({ format, render, cancel, register }) => {
import(`timeago.js/esm/lang/${locale}.js`).then(mod => {
register(locale, mod.default);
setText(format(date, locale));
// Автообновление после загрузки
const id = setInterval(() => {
setText(format(date, locale));
}, 60_000);
cleanup = () => clearInterval(id);
});
});
return () => cleanup?.();
}, [date, locale]);
return text;
}
<template>
<time :datetime="isoDate" ref="timeEl">{{ fallbackText }}</time>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue';
const props = defineProps<{ date: string; locale?: string }>();
const timeEl = ref<HTMLTimeElement | null>(null);
let cleanup: (() => void) | null = null;
const isoDate = computed(() => new Date(props.date).toISOString());
const fallbackText = computed(() => new Date(props.date).toLocaleDateString('ru-RU'));
onMounted(async () => {
const [{ render, cancel, register }, mod] = await Promise.all([
import('timeago.js'),
import(`timeago.js/esm/lang/${props.locale ?? 'ru'}.js`),
]);
register(props.locale ?? 'ru', mod.default);
if (timeEl.value) {
render(timeEl.value, props.locale ?? 'ru');
cleanup = () => { if (timeEl.value) cancel(timeEl.value); };
}
});
onUnmounted(() => cleanup?.());
</script>
// При использовании виртуальных списков (react-window, vue-virtual-scroller)
// элементы монтируются и размонтируются по мере прокрутки
// В обработчике монтирования виртуального элемента:
async function onItemMount(el) {
const { render } = await import('timeago.js');
render(el.querySelector('time'), 'ru');
}
async function onItemUnmount(el) {
const { cancel } = await import('timeago.js');
cancel(el.querySelector('time'));
}
// Инициализировать timeago.js один раз, переиспользовать
const timeagoPromise = import('timeago.js').then(async (lib) => {
const [ruMod] = await Promise.all([
import('timeago.js/esm/lang/ru'),
]);
lib.register('ru', ruMod.default);
return lib;
});
// Использование
async function formatDate(date) {
const { format } = await timeagoPromise;
return format(date, 'ru');
}
| Метрика | Без ленивой загрузки | С ленивой загрузкой |
|---|---|---|
| Начальный бандл | +2-3 KB timeago.js | 0 KB при загрузке страницы |
| Время до интерактивности | Медленнее | Быстрее |
| Активные таймеры | Все сразу | Только для видимых элементов |
| Нагрузка на CPU | Высокая при старте | Распределена во времени |