Принудительное обновление сервис-воркера

Основы обновления сервис-воркера

Сервис-воркеры в браузерах по умолчанию следуют строгой модели обновления: новый сервис-воркер загружается в фоновом режиме, но не активируется до тех пор, пока не будут закрыты все вкладки, использующие старую версию. Это может создавать задержку между публикацией нового кода и его фактическим использованием пользователями. Для приложений с динамичным контентом или быстрыми изменениями важно обеспечить механизм принудительного обновления сервис-воркера.

Workbox предоставляет инструменты для управления жизненным циклом сервис-воркера и позволяет реализовать стратегию мгновенного перехода на новую версию.

skipWaiting() и clients.claim()

skipWaiting() — метод, который заставляет сервис-воркер переходить в активное состояние сразу после установки, минуя фазу ожидания:

import { skipWaiting, clientsClaim } from 'workbox-core';

skipWaiting();
clientsClaim();
  • skipWaiting(): активирует новый сервис-воркер сразу после установки, не дожидаясь закрытия всех открытых вкладок.
  • clientsClaim(): позволяет сервис-воркеру немедленно контролировать все клиентские страницы, даже если они были загружены до активации нового воркера.

Эти два метода часто используются вместе для обеспечения мгновенного перехода на новую версию приложения.

Настройка Workbox для автообновления

При использовании Workbox можно включить автоматическую интеграцию методов обновления через модуль workbox-core:

import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
import { skipWaiting, clientsClaim } from 'workbox-core';

// Активируем мгновенное обновление
skipWaiting();
clientsClaim();

// Прекэширование статических ресурсов
precacheAndRoute(self.__WB_MANIFEST);

// Кэширование динамических ресурсов
registerRoute(
  ({ request }) => request.destination === 'image',
  new StaleWhileRevalidate({
    cacheName: 'images-cache',
  })
);

В данном примере новый сервис-воркер будет установлен и активирован немедленно, при этом статические и динамические ресурсы будут закэшированы согласно указанным стратегиям.

Варианты уведомления пользователя о новой версии

Даже с skipWaiting() и clientsClaim() иногда требуется информировать пользователя о том, что доступна новая версия. Для этого можно использовать события waiting и controllerchange:

self.addEventListener('install', event => {
  console.log('Сервис-воркер установлен');
});

self.addEventListener('activate', event => {
  console.log('Сервис-воркер активирован');
});

self.addEventListener('message', event => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    skipWaiting();
  }
});

На стороне клиента:

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js').then(registration => {
    registration.addEventListener('updatefound', () => {
      const newWorker = registration.installing;
      newWorker.addEventListener('statechange', () => {
        if (newWorker.state === 'installed') {
          if (navigator.serviceWorker.controller) {
            // Новая версия готова
            console.log('Доступна новая версия приложения');
          }
        }
      });
    });
  });
}

Такая схема позволяет сохранять контроль над процессом обновления и при необходимости инициировать его по событию пользователя (например, при нажатии кнопки “Обновить”).

Интеграция с Workbox Build

При сборке приложения через Workbox CLI или webpack-плагин важно учитывать:

  • Использование clientsClaim() и skipWaiting() в кастомном сервис-воркере.
  • Поддержка стратегий кэширования с актуализацией (Stale-While-Revalidate или NetworkFirst) для динамических ресурсов.
  • Возможность внедрения уведомления пользователя о новой версии с последующим вызовом skipWaiting().

Пример конфигурации webpack-плагина WorkboxWebpackPlugin.GenerateSW:

const { GenerateSW } = require('workbox-webpack-plugin');

module.exports = {
  plugins: [
    new GenerateSW({
      clientsClaim: true,
      skipWaiting: true,
      runtimeCaching: [
        {
          urlPattern: /\.(?:png|jpg|jpeg|svg)$/,
          handler: 'StaleWhileRevalidate',
          options: {
            cacheName: 'images-cache',
            expiration: {
              maxEntries: 50,
            },
          },
        },
      ],
    }),
  ],
};

Ловушки и особенности

  1. Преждевременное обновление: skipWaiting() активирует воркер сразу, что может привести к конфликтам с открытыми вкладками, особенно если приложение активно взаимодействует с локальными данными.
  2. Согласованность состояния: необходимо убедиться, что новые ресурсы корректно синхронизированы с текущими данными клиента, иначе возможны расхождения.
  3. Кэширование API-запросов: даже с мгновенным обновлением сервис-воркера важно управлять стратегиями кэширования для динамических данных, чтобы приложение всегда получало актуальный контент.

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

  • Использовать skipWaiting() и clientsClaim() в сочетании с уведомлением пользователя о доступной новой версии.
  • Разделять статический и динамический кэш, применяя разные стратегии обновления.
  • Контролировать процесс обновления через события сервис-воркера и сообщения между клиентом и воркером.
  • Проверять работу обновлений на нескольких вкладках и устройствах, чтобы избежать конфликтов и непредвиденного поведения.

Эти подходы позволяют полностью контролировать жизненный цикл сервис-воркера и обеспечивают оперативное распространение новых версий веб-приложений с использованием Workbox.