Offline функциональность

Фреймворк Fresh в JavaScript построен на концепции рендеринга на сервере с минимальной нагрузкой на клиент. Несмотря на это, поддержка offline функциональности становится ключевым аспектом для приложений с высокой надежностью и быстродействием. Offline режим позволяет обеспечивать доступ к контенту и функционалу даже при отсутствии соединения с сервером, используя локальные ресурсы и кэширование.

Service Workers

Ключевой инструмент для реализации offline режима — Service Worker. Это отдельный скрипт, работающий в фоне браузера, который перехватывает сетевые запросы, управляет кэшами и может обеспечивать работу приложения без подключения к сети.

Пример регистрации Service Worker во Fresh:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js')
    .then(() => console.log('Service Worker зарегистрирован'))
    .catch(err => console.error('Ошибка регистрации Service Worker:', err));
}

Файл sw.js содержит логику кэширования и обработки запросов:

const CACHE_NAME = 'fresh-app-cache-v1';
const URLS_TO_CACHE = [
  '/',
  '/styles.css',
  '/main.js',
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(URLS_TO_CACHE))
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetch(event.request))
  );
});

Ключевые моменты:

  • install — событие, при котором создается кэш с необходимыми ресурсами.
  • fetch — перехват запросов к серверу с возвратом локальной копии из кэша при отсутствии сети.
  • Постоянное обновление кэша позволяет синхронизировать данные при восстановлении соединения.

Кэширование данных API

Для приложений Fresh важно кэшировать не только статические файлы, но и ответы API. Используется подход stale-while-revalidate, который возвращает старые данные мгновенно и одновременно обновляет их в фоне.

Пример Service Worker с API кэшированием:

self.addEventListener('fetch', event => {
  if (event.request.url.includes('/api/')) {
    event.respondWith(
      caches.open('api-cache').then(cache =>
        cache.match(event.request).then(response =>
          fetch(event.request).then(networkResponse => {
            cache.put(event.request, networkResponse.clone());
            return response || networkResponse;
          }).catch(() => response)
        )
      )
    );
  }
});

Такой подход обеспечивает минимальную задержку при запросе данных и гарантирует работу при отсутствии сети.

Локальное хранилище и IndexedDB

Для хранения сложных данных можно использовать IndexedDB, которая позволяет сохранять объекты с произвольной структурой. В Fresh можно интегрировать IndexedDB для кэширования данных форм, пользовательских настроек или офлайн-сессий.

Пример использования IndexedDB с оберткой idb:

import { openDB } from 'idb';

const dbPromise = openDB('fresh-db', 1, {
  upgrade(db) {
    db.createObjectStore('posts', { keyPath: 'id' });
  },
});

async function savePost(post) {
  const db = await dbPromise;
  await db.put('posts', post);
}

async function getPost(id) {
  const db = await dbPromise;
  return await db.get('posts', id);
}

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

Offline маршрутизация

Во Fresh маршрутизация строится на серверной стороне. Для offline режима необходимо предусмотреть обработку навигации локально. Service Worker может возвращать кэшированные HTML-страницы для известных маршрутов, а динамические маршруты кэшировать через стратегию Network falling back to cache.

Пример:

self.addEventListener('fetch', event => {
  if (event.request.mode === 'navigate') {
    event.respondWith(
      fetch(event.request).catch(() => caches.match('/offline.html'))
    );
  }
});

offline.html — это резервная страница, отображаемая при отсутствии сети, что обеспечивает плавный пользовательский опыт даже при недоступном сервере.

Интеграция с Fresh Islands

Fresh использует концепцию Islands, где отдельные компоненты клиентской логики подключаются к статическому HTML. Для offline работы необходимо, чтобы Islands могли работать с локальными данными. Это достигается использованием локальных кэшей или IndexedDB внутри компонентов, а также подпиской на события восстановления сети (online/offline) для синхронизации данных.

Пример подписки на события сети:

window.addEventListener('online', () => {
  console.log('Соединение восстановлено, синхронизация данных');
  syncLocalChanges();
});

window.addEventListener('offline', () => {
  console.log('Сеть недоступна, работа в офлайн режиме');
});

Оптимизация кэширования

Для больших приложений важно управлять версионированием кэшей и лимитами хранения:

  • Использовать Cache-Control и ETag для автоматического обновления ресурсов.
  • Очистка старых кэшей при activate Service Worker:
self.addEventListener('activate', event => {
  const cacheWhitelist = [CACHE_NAME];
  event.waitUntil(
    caches.keys().then(keyList =>
      Promise.all(keyList.map(key => {
        if (!cacheWhitelist.includes(key)) {
          return caches.delete(key);
        }
      }))
    )
  );
});
  • Разделение кэшей для статических ресурсов и данных API повышает скорость отклика и гибкость управления хранилищем.

Реализация Push-уведомлений

Offline функциональность может включать Push-уведомления, которые оповещают пользователя о новых данных, даже если приложение закрыто. Fresh интегрируется с Push API через Service Worker:

self.addEventListener('push', event => {
  const data = event.data.json();
  self.registration.showNotification(data.title, {
    body: data.body,
    icon: '/icon.png'
  });
});

Push-уведомления обеспечивают асинхронное обновление информации и повышают вовлеченность пользователей.

Итоговая архитектура offline Fresh-приложения

  • Service Worker — основной механизм перехвата запросов и управления кэшем.
  • Кэширование статических файлов и API — минимизация задержек и работа без сети.
  • IndexedDB — гибкое хранение структурированных данных.
  • Offline маршрутизация и Islands — плавный пользовательский опыт.
  • Push-уведомления — информирование и синхронизация данных.

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