Каналы обновлений (Update Channels) — это механизм управления версией сервис-воркера и контроля поведения приложения при выпуске новых версий. Workbox предоставляет инструменты для тонкой настройки этого процесса, позволяя автоматически или вручную управлять обновлениями и уведомлять клиентов о доступности новых ресурсов.
Для организации канала обновлений необходимо правильно
зарегистрировать сервис-воркер и настроить событие
updatefound:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(registration => {
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// Сервис-воркер обновился
console.log('Новая версия доступна');
} else {
// Первый запуск сервис-воркера
console.log('Контент доступен оффлайн');
}
}
});
});
}).catch(error => {
console.error('Ошибка при регистрации сервис-воркера:', error);
});
}
Ключевые моменты:
updatefound срабатывает при появлении нового
установочного воркера.statechange позволяет отследить момент, когда воркер
полностью установлен.navigator.serviceWorker.controller проверяет, управляет
ли текущий сервис-воркер страницей.Workbox поддерживает автоматическое обновление сервис-воркера через
опцию skipWaiting():
import { precacheAndRoute } from 'workbox-precaching';
import { clientsClaim } from 'workbox-core';
precacheAndRoute(self.__WB_MANIFEST);
self.addEventListener('install', event => {
self.skipWaiting();
});
self.addEventListener('activate', event => {
clientsClaim();
});
Объяснение:
skipWaiting() заставляет новый сервис-воркер
активироваться сразу после установки, минуя стандартный этап
ожидания.clientsClaim() позволяет новому воркеру сразу управлять
всеми открытыми клиентами, обеспечивая моментальный доступ к новым
ресурсам.Для уведомления страниц о новой версии можно использовать
postMessage. Workbox предоставляет удобный способ
коммуникации через сервис-воркер:
self.addEventListener('message', event => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('Сервис-воркер сменился. Перезагрузите страницу для применения обновлений.');
});
Особенности:
skipWaiting() по запросу страницы.controllerchange информирует, что сервис-воркер
обновился, и новые ресурсы готовы к использованию.Workbox предоставляет модуль workbox-broadcast-update
для автоматического уведомления клиентов о новых версиях кэшируемых
ресурсов:
import { BroadcastUpdatePlugin } from 'workbox-broadcast-update';
import { StaleWhileRevalidate } from 'workbox-strategies';
import { registerRoute } from 'workbox-routing';
const broadcastUpdatePlugin = new BroadcastUpdatePlugin();
registerRoute(
({ request }) => request.destination === 'script' || request.destination === 'style',
new StaleWhileRevalidate({
cacheName: 'static-resources',
plugins: [broadcastUpdatePlugin]
})
);
Принципы работы:
BroadcastUpdatePlugin отправляет сообщение на все открытые
страницы.navigator.serviceWorker.addEventListener('message', event => {
if (event.data && event.data.type === 'CACHE_UPDATED') {
console.log(`Файл ${event.data.meta.url} обновлён`);
}
});
Для управления обновлениями можно разделять ресурсы на категории:
registerRoute(
({ request }) => request.destination === 'image',
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
new BroadcastUpdatePlugin({
channelName: 'images-update'
})
]
})
);
registerRoute(
({ request }) => request.destination === 'script',
new StaleWhileRevalidate({
cacheName: 'scripts',
plugins: [
new BroadcastUpdatePlugin({
channelName: 'scripts-update'
})
]
})
);
Важное отличие:
channelName позволяет создавать отдельные каналы
сообщений для различных типов ресурсов.Иногда необходимо, чтобы обновления не применялись немедленно. Можно
комбинировать skipWaiting и логику проверки версии:
self.addEventListener('install', event => {
event.waitUntil(
caches.open('versioned-cache-v2').then(cache => {
return cache.addAll(['/index.html', '/main.js']);
})
);
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keys => {
return Promise.all(
keys
.filter(key => key !== 'versioned-cache-v2')
.map(key => caches.delete(key))
);
})
);
});
Принципы: