Кеширование ресурсов

Page.js — это легковесная библиотека маршрутизации для одностраничных приложений (SPA) на JavaScript. Одной из ключевых задач при работе с SPA является эффективное управление ресурсами, чтобы минимизировать повторные сетевые запросы и ускорить загрузку страниц. Кеширование ресурсов позволяет хранить данные и шаблоны на стороне клиента, обеспечивая мгновенную реакцию интерфейса при переходах между маршрутами.


Механизм кеширования

Page.js изначально не предоставляет встроенного кеша данных или шаблонов. Однако библиотека позволяет интегрировать любые подходы к кешированию за счет гибкой системы middleware и возможности работы с асинхронными функциями внутри маршрутов.

Основные варианты кеширования:

  1. Кеширование шаблонов HTML

    • Часто шаблоны загружаются с сервера через AJAX.
    • Для ускорения последующих переходов шаблоны можно сохранять в объекте, где ключом служит путь или идентификатор шаблона.
    • Пример:
const templateCache = {};

function loadTemplate(name) {
  if (templateCache[name]) return Promise.resolve(templateCache[name]);
  return fetch(`/templates/${name}.html`)
    .then(res => res.text())
    .then(html => {
      templateCache[name] = html;
      return html;
    });
}

page('/about', () => {
  loadTemplate('about').then(html => {
    document.getElementById('app').innerHTML = html;
  });
});
  1. Кеширование данных API

    • Для данных, которые часто запрашиваются с сервера, логично использовать локальный кеш.
    • Можно хранить данные в объекте, Map или sessionStorage/localStorage в зависимости от необходимого срока жизни.
const dataCache = new Map();

function fetchData(endpoint) {
  if (dataCache.has(endpoint)) return Promise.resolve(dataCache.get(endpoint));
  return fetch(endpoint)
    .then(res => res.json())
    .then(data => {
      dataCache.set(endpoint, data);
      return data;
    });
}

page('/users', () => {
  fetchData('/api/users').then(users => renderUsers(users));
});
  1. Кеширование компонентов или функций рендеринга

    • Можно кешировать не только готовые HTML-шаблоны, но и функции генерации интерфейса с уже подготовленными данными.
    • Позволяет сократить повторные вычисления и минимизировать манипуляции с DOM.

Интеграция кеширования с маршрутизацией

Page.js строится на обработчиках маршрутов, поэтому кеширование удобно интегрировать как middleware. Middleware выполняется до основного рендеринга, что позволяет предварительно загружать ресурсы.

Пример middleware с кешированием данных:

function cacheMiddleware(ctx, next) {
  const endpoint = `/api${ctx.path}`;
  if (dataCache.has(endpoint)) {
    ctx.data = dataCache.get(endpoint);
    next();
  } else {
    fetch(endpoint)
      .then(res => res.json())
      .then(data => {
        dataCache.set(endpoint, data);
        ctx.data = data;
        next();
      });
  }
}

page('/products', cacheMiddleware, ctx => {
  renderProducts(ctx.data);
});
  • ctx — объект контекста маршрута, позволяет передавать данные между middleware и основным обработчиком.
  • next() — передает управление следующему middleware или обработчику маршрута.

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


Стратегии обновления кеша

Кеширование без стратегии обновления может привести к устаревшим данным. Основные подходы к обновлению:

  1. Time-to-Live (TTL)

    • Кеш хранится ограниченное время, после чего автоматически обновляется при следующем запросе.
const dataCache = new Map();

function fetchDataWithTTL(endpoint, ttl = 60000) {
  const cached = dataCache.get(endpoint);
  const now = Date.now();
  if (cached && (now - cached.timestamp < ttl)) return Promise.resolve(cached.data);
  
  return fetch(endpoint)
    .then(res => res.json())
    .then(data => {
      dataCache.set(endpoint, { data, timestamp: now });
      return data;
    });
}
  1. Ручное сбрасывание кеша

    • Можно сбрасывать кеш при определенных событиях: после POST/PUT/DELETE запросов, при смене пользователя, при логике обновления данных.
function invalidateCache(endpoint) {
  dataCache.delete(endpoint);
}
  1. Стратегия “Stale-While-Revalidate”

    • Сначала используется кешированная версия, сразу отображается пользователю, а параллельно выполняется обновление данных.
    • После получения новых данных интерфейс обновляется автоматически.

Практические рекомендации

  • Хранить только необходимые данные и шаблоны, чтобы не перегружать память браузера.
  • Разделять кеш для разных маршрутов, чтобы минимизировать коллизии и ошибки обновления.
  • Использовать слабые ссылки (WeakMap) для кеширования объектов, которые не должны блокировать сборку мусора.
  • Всегда учитывать асинхронность операций: кеш может быть пустым в момент первого запроса, поэтому обработка Promise обязательна.

Оптимизация производительности

  • Предзагрузка ресурсов: можно заранее загружать шаблоны и данные для вероятных маршрутов при idle-времени или на событии hover.
  • Ленивая загрузка: кешировать только часто используемые ресурсы, а редко используемые загружать по необходимости.
  • Объединение данных: если несколько маршрутов используют одни и те же ресурсы, хранить их в общем кеш-объекте.

Такое комбинированное использование кеша с маршрутизацией Page.js позволяет существенно сократить время отклика приложения и уменьшить количество сетевых запросов, сохраняя при этом простоту и предсказуемость работы SPA.