Service Workers: Магия фоновых скриптов для вашего сайта. Примеры и практика

Service Workers: Магия фоновых скриптов для вашего сайта. Примеры и практика

Представьте, что ваш сайт может работать без интернета, мгновенно загружаться при повторном посещении и отправлять push-уведомления, даже когда вкладка закрыта. Это не магия будущего, а реальность, которую дарят Service Workers — революционная технология, лежащая в основе современных Progressive Web Apps (PWA). В этой статье мы разберемся, что это такое, и рассмотрим конкретные, рабочие примеры, которые вы сможете применить уже сегодня.

Что такое Service Worker?

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

Service Worker работает в отдельном потоке (worker) и не имеет доступа к DOM. Общение с основной страницей происходит через API сообщений (postMessage).

Жизненный цикл Service Worker

Понимание жизненного цикла критически важно для работы:

  1. Регистрация: Скрипт регистрируется с главной страницы.
  2. Установка: Происходит один раз для новой версии. Идеальное время для предварительного кэширования ключевых ресурсов.
  3. Активация: Новый worker активируется, старый очищается. Здесь можно удалить старые кэши.
  4. Простой (Idle): Worker ждет событий (fetch, push, sync).
  5. Завершение (Terminated): Браузер может остановить worker для экономии ресурсов.

Практические примеры Service Worker

Пример 1: Базовый офлайн-кэш

Самый распространенный сценарий — кэширование статических ресурсов для работы без сети.

// sw.js
const CACHE_NAME = 'my-site-cache-v1';
const urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/app.js',
  '/images/logo.png'
];

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

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        if (response) {
          return response; // Отдаем из кэша
        }
        return fetch(event.request); // Иначе идем в сеть
      })
  );
});

Пример 2: Стратегия "Сеть, затем кэш" (Fallback)

Идеально для динамического контента, где важна актуальность.

self.addEventListener('fetch', event => {
  event.respondWith(
    fetch(event.request) // Пытаемся получить из сети
      .then(response => {
        // Клонируем ответ, т.к. поток можно прочитать один раз
        const responseToCache = response.clone();
        caches.open(CACHE_NAME)
          .then(cache => cache.put(event.request, responseToCache));
        return response;
      })
      .catch(() => caches.match(event.request)) // Если сеть недоступна — из кэша
  );
});

Всегда используйте event.respondWith() для асинхронного ответа на fetch-события. И помните про клонирование Response!

Пример 3: Фоновые push-уведомления

Service Worker — единственное место, где можно обрабатывать push-сообщения.

self.addEventListener('push', event => {
  const title = 'Новое уведомление!';
  const options = {
    body: event.data.text(),
    icon: '/icon-192.png',
    badge: '/badge-72.png'
  };
  event.waitUntil(
    self.registration.showNotification(title, options)
  );
});

self.addEventListener('notificationclick', event => {
  event.notification.close();
  event.waitUntil(
    clients.openWindow('https://ваш-сайт.ru/спец-страница') // Открываем страницу
  );
});

Регистрация Service Worker на странице

Самый worker бесполезен без его подключения в основном JavaScript вашего приложения.

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

Лучшие практики и подводные камни

  • HTTPS обязателен: В production Service Worker работает только на защищенных соединениях (localhost — исключение).
  • Контроль версий: Изменение скрипта даже на байт приводит к установке новой версии. Используйте разные имена кэшей (my-cache-v1, v2...).
  • Очистка старых кэшей в событии 'activate':
self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.filter(name => name !== CACHE_NAME)
          .map(name => caches.delete(name))
      );
    })
  );
});
  • Не кэшируйте всё подряд: Стратегически выбирайте ресурсы для кэша, чтобы не превышать лимиты браузера.

FAQ: Часто задаваемые вопросы

Service Worker — это то же самое, что Web Worker?

Нет. Web Worker предназначен для вычислительно тяжелых задач в фоне. Service Worker — это именно прокси для сети с событиями жизненного цикла, созданный для управления кэшем и офлайн-работой.

Можно ли использовать Service Worker с любым фреймворком (React, Vue, Angular)?

Да, абсолютно. Service Worker — это нативный браузерный API. Вы можете интегрировать его в любой проект. Многие фреймворки имеют готовые плагины или инструменты (например, workbox-webpack-plugin).

Как обновить уже установленный Service Worker?

При изменении файла sw.js браузер обнаружит это и начнет установку новой версии. Однако она активируется только когда не останется открытых вкладок, использующих старую версию. Вы можете ускорить это, вызвав self.skipWaiting() в событии install и clients.claim() в activate.

Service Worker замедляет сайт?

При правильной реализации — нет, а ускоряет! За счет отдачи ресурсов из локального кэша первый и повторный доступ становятся мгновенными. Главное — не перегружать логику обработки fetch-событий.

Где можно посмотреть зарегистрированных Service Worker?

В Chrome DevTools: вкладка Application -> раздел Service Workers. Там же можно их принудительно обновить, остановить или перевести в офлайн-режим для тестирования.