Пакет workbox-window
— это набор модулей, предназначенных для запуска в контексте window
, то есть внутри ваших веб-страниц. Они являются дополнением к другим пакетам рабочей области, которые выполняются в сервисном работнике.
Ключевые особенности/цели workbox-window
:
- Упростить процесс регистрации и обновления Service Worker, помогая разработчикам определять наиболее критические моменты в жизненном цикле Service Worker и упрощая реагирование на эти моменты.
- Чтобы помочь разработчикам не допускать наиболее распространенных ошибок .
- Чтобы упростить взаимодействие между кодом, выполняющимся в сервисном работнике, и кодом, выполняющимся в окне.
Импорт и использование рабочего окна
Основной точкой входа для пакета workbox-window
является класс Workbox
, и вы можете импортировать его в свой код либо из нашей CDN, либо с помощью любого из популярных инструментов связывания JavaScript.
Используя наш CDN
Самый простой способ импортировать класс Workbox
на ваш сайт — из нашей CDN:
<script type="module">
import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/6.4.1/workbox-window.prod.mjs';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
</script>
Обратите внимание, что в этом примере используется <script type="module">
и оператор import
для загрузки класса Workbox
. Хотя вы можете подумать, что вам нужно транспилировать этот код, чтобы он работал в старых браузерах, на самом деле в этом нет необходимости.
Все основные браузеры, поддерживающие Service Worker, также поддерживают собственные модули JavaScript , поэтому этот код можно использовать в любых браузерах (старые браузеры просто проигнорируют его).
Загрузка Workbox с помощью сборщиков JavaScript
Хотя для использования workbox-window
не требуется абсолютно никаких инструментов, если ваша инфраструктура разработки уже включает в себя такой сборщик, как webpack или Rollup , который работает с зависимостями npm , их можно использовать для загрузки workbox-window
.
Первым шагом является установка workbox-window
как зависимости вашего приложения:
npm install workbox-window
Затем в одном из файлов JavaScript вашего приложения import
рабочий ящик, указав имя пакета workbox-window
:
import {Workbox} from 'workbox-window';
if ('serviceWorker' in navigator) {
const wb = new Workbox('/sw.js');
wb.register();
}
Если ваш сборщик поддерживает разделение кода с помощью операторов динамического импорта , вы также можете условно загрузить workbox-window
, что должно помочь уменьшить размер основного пакета вашей страницы.
Несмотря на то, что workbox-window
довольно маленькое, нет причин загружать в него основную логику приложения вашего сайта, поскольку сервис-воркеры по своей природе являются прогрессивным усовершенствованием.
if ('serviceWorker' in navigator) {
const {Workbox} = await import('workbox-window');
const wb = new Workbox('/sw.js');
wb.register();
}
Расширенные концепции комплектации
В отличие от пакетов Workbox, которые запускаются в сервис-воркере, файлы сборки, на которые ссылаются поля main
и module
workbox-window
в package.json
, передаются в ES5. Это делает их совместимыми с современными инструментами сборки, некоторые из которых не позволяют разработчикам переносить какие-либо зависимости node_module
.
Если ваша система сборки позволяет вам транспилировать ваши зависимости (или если вам не нужно транспилировать какой-либо код), то лучше импортировать конкретный исходный файл, а не сам пакет.
Вот различные способы импорта Workbox
вместе с объяснением того, что каждый из них вернет:
// Imports a UMD version with ES5 syntax
// (pkg.main: "build/workbox-window.prod.umd.js")
const {Workbox} = require('workbox-window');
// Imports the module version with ES5 syntax
// (pkg.module: "build/workbox-window.prod.es5.mjs")
import {Workbox} from 'workbox-window';
// Imports the module source file with ES2015+ syntax
import {Workbox} from 'workbox-window/Workbox.mjs';
Примеры
Импортировав класс Workbox
, вы можете использовать его для регистрации и взаимодействия с вашим сервис-воркером. Вот несколько примеров того, как вы можете использовать Workbox
в своем приложении:
Зарегистрируйте сервис-воркера и уведомляйте пользователя при первой активности сервис-воркера.
Многие веб-приложения используют службу поддержки пользователей для предварительного кэширования ресурсов, чтобы их приложение работало в автономном режиме при последующих загрузках страниц. В некоторых случаях имеет смысл сообщить пользователю, что приложение теперь доступно в автономном режиме.
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', event => {
// `event.isUpdate` will be true if another version of the service
// worker was controlling the page when this version was registered.
if (!event.isUpdate) {
console.log('Service worker activated for the first time!');
// If your service worker is configured to precache assets, those
// assets should all be available now.
}
});
// Register the service worker after event listeners have been added.
wb.register();
Уведомлять пользователя, если сервисный работник установился, но зависает в ожидании активации.
Когда страница, контролируемая существующим сервис-воркером, регистрирует нового сервис-воркера, по умолчанию этот сервис-воркер не активируется до тех пор, пока все клиенты, контролируемые исходным сервис-воркером, не будут полностью выгружены.
Это распространенный источник путаницы для разработчиков, особенно в тех случаях, когда перезагрузка текущей страницы не приводит к активации нового сервис-воркера .
Чтобы свести к минимуму путаницу и прояснить ситуацию, класс Workbox
предоставляет waiting
событие, которое вы можете прослушивать:
const wb = new Workbox('/sw.js');
wb.addEventListener('waiting', event => {
console.log(
`A new service worker has installed, but it can't activate` +
`until all tabs running the current version have fully unloaded.`
);
});
// Register the service worker after event listeners have been added.
wb.register();
Уведомлять пользователя об обновлениях кэша из пакета workbox-broadcast-update
Пакет workbox-broadcast-update
— это отличный способ обслуживать контент из кэша (для быстрой доставки), а также информировать пользователя об обновлениях этого контента (с использованием стратегии устаревшего при повторной проверке ).
Чтобы получать эти обновления из окна, вы можете прослушивать события message
типа CACHE_UPDATED
:
const wb = new Workbox('/sw.js');
wb.addEventListener('message', event => {
if (event.data.type === 'CACHE_UPDATED') {
const {updatedURL} = event.data.payload;
console.log(`A newer version of ${updatedURL} is available!`);
}
});
// Register the service worker after event listeners have been added.
wb.register();
Отправьте сервисному работнику список URL-адресов для кэширования.
Для некоторых приложений можно знать все ресурсы, которые необходимо предварительно кэшировать во время сборки, но некоторые приложения обслуживают совершенно разные страницы в зависимости от того, на какой URL-адрес пользователь попадает первым.
Для приложений последней категории может иметь смысл кэшировать только те ресурсы, которые нужны пользователю для конкретной страницы, которую он посетил. При использовании пакета workbox-routing
вы можете отправить маршрутизатору список URL-адресов для кэширования, и он будет кэшировать эти URL-адреса в соответствии с правилами, определенными на самом маршрутизаторе.
В этом примере список URL-адресов, загруженных страницей, отправляется на маршрутизатор каждый раз, когда активируется новый сервис-воркер. Обратите внимание: можно отправлять все URL-адреса, поскольку кэшироваться будут только те URL-адреса, которые соответствуют определенному маршруту в сервис-воркере:
const wb = new Workbox('/sw.js');
wb.addEventListener('activated', event => {
// Get the current page URL + all resources the page loaded.
const urlsToCache = [
location.href,
...performance.getEntriesByType('resource').map(r => r.name),
];
// Send that list of URLs to your router in the service worker.
wb.messageSW({
type: 'CACHE_URLS',
payload: {urlsToCache},
});
});
// Register the service worker after event listeners have been added.
wb.register();
Важные моменты жизненного цикла сервис-воркера
Жизненный цикл сервисного работника сложен, и его полное понимание может оказаться непростой задачей. Одна из причин, по которой он настолько сложен, заключается в том, что он должен обрабатывать все крайние случаи для всех возможных вариантов использования сервис-воркера (например, регистрация более одного сервис-воркера, регистрация разных сервис-воркеров в разных кадрах, регистрация сервис-воркеров с разными именами и т. д.).
Но большинству разработчиков, реализующих Service Worker, не стоит беспокоиться обо всех этих крайних случаях, поскольку их использование довольно просто. Большинство разработчиков регистрируют только одного сервис-воркера при каждой загрузке страницы и не меняют имя файла сервис-воркера, который они развертывают на своем сервере.
Класс Workbox
реализует это более простое представление жизненного цикла сервис-воркера, разбивая все регистрации сервис-воркера на две категории: собственный зарегистрированный сервис-воркер экземпляра и внешний сервис-воркер:
- Зарегистрированный сервисный работник : сервисный работник, который начал установку в результате вызова
register()
экземпляраWorkbox
, или уже активный сервисный работник, если вызовregister()
не вызвал событиеupdatefound
при регистрации. - Внешний сервисный работник: сервисный работник, который начал установку независимо от экземпляра
Workbox
, вызвавregister()
. Обычно это происходит, когда у пользователя открыта новая версия вашего сайта на другой вкладке. Если событие исходит от внешнего сервисного работника, для свойстваisExternal
события будет установлено значениеtrue
.
Имея в виду эти два типа сервис-воркеров, ниже приводится разбивка всех важных моментов жизненного цикла сервис-воркера, а также рекомендации разработчика о том, как с ними обращаться:
Самый первый раз, когда устанавливается сервисный работник
Вероятно, вы захотите относиться к самой первой установке Service Worker иначе, чем ко всем будущим обновлениям.
В workbox-window
вы можете различать первую установку версии и будущие обновления, проверив свойство isUpdate
в любом из следующих событий. Для самой первой установки isUpdate
будет иметь false
.
const wb = new Workbox('/sw.js');
wb.addEventListener('installed', event => {
if (!event.isUpdate) {
// First-installed code goes here...
}
});
wb.register();
При обнаружении обновленной версии сервис-воркера
Когда новый сервис-воркер начинает установку, но существующая версия в настоящее время управляет страницей, свойство isUpdate
всех следующих событий будет иметь true
.
Ваша реакция в этой ситуации обычно отличается от самой первой установки, поскольку вам нужно управлять тем, когда и как пользователь получит это обновление.
При обнаружении непредвиденной версии сервисного работника
Иногда пользователи очень долго оставляют ваш сайт открытым на фоновой вкладке. Они могут даже открыть новую вкладку и перейти на ваш сайт, не осознавая, что ваш сайт уже открыт на фоновой вкладке. В таких случаях возможно одновременное использование двух версий вашего сайта, и это может создать некоторые интересные проблемы для вас как разработчика.
Рассмотрим сценарий, в котором на вкладке A запущена версия 1 вашего сайта, а на вкладке B — версия 2. Когда вкладка B загружается, она будет контролироваться версией вашего сервис-воркера, поставляемой с версией 1, но страница, возвращаемая сервером (при использовании стратегии сетевого кэширования для ваших навигационных запросов), будет содержать все ваши ресурсы версии 2.
Однако для вкладки B это, как правило, не проблема, поскольку, когда вы писали код версии 2, вы знали, как работает код версии 1. Однако это может стать проблемой для вкладки A, поскольку ваш код версии 1 не мог предсказать, какие изменения может внести ваш код версии 2.
Чтобы помочь справиться с такими ситуациями, workbox-window
также отправляет события жизненного цикла, когда обнаруживает обновление от «внешнего» сервис-воркера, где внешний просто означает любую версию, которая не является версией, зарегистрированной текущим экземпляром Workbox
.
Начиная с Workbox v6 и более поздних версий, эти события эквивалентны событиям, описанным выше, с добавлением свойства isExternal: true
установленного для каждого объекта события. Если вашему веб-приложению необходимо реализовать определенную логику для обработки «внешнего» сервисного работника, вы можете проверить это свойство в своих обработчиках событий.
Как избежать распространенных ошибок
Одна из наиболее полезных функций Workbox — это ведение журнала разработчиков. И это особенно актуально для workbox-window
.
Мы знаем, что разработка с использованием Service Worker часто может сбивать с толку, и когда что-то происходит вопреки вашим ожиданиям, может быть трудно понять, почему.
Например, когда вы вносите изменения в своего сервис-воркера и перезагружаете страницу, вы можете не увидеть это изменение в своем браузере. Наиболее вероятная причина этого в том, что ваш сервисный работник все еще ожидает активации.
Но при регистрации сервисного работника в классе Workbox
вы будете получать информацию обо всех изменениях состояния жизненного цикла в консоли разработчика, что должно помочь при отладке того, почему все идет не так, как вы ожидаете.
Кроме того, распространенной ошибкой разработчиков при первом использовании Service Worker является регистрация Service Worker в неправильной области видимости .
Чтобы этого не произошло, класс Workbox
предупредит вас, если страница, регистрирующая сервис-воркера, не находится в области действия этого сервис-воркера. Он также предупредит вас в случаях, когда ваш сервис-воркер активен, но еще не контролирует страницу:
Окно для обслуживания связи с работниками
Наиболее продвинутое использование сервис-воркера включает в себя множество сообщений между сервис-воркером и окном. Класс Workbox
также помогает в этом, предоставляя метод messageSW()
, который отправляет postMessage()
зарегистрированному сервисному работнику экземпляра и ожидает ответа.
Хотя вы можете отправлять данные сервис-воркеру в любом формате, формат, общий для всех пакетов Workbox, представляет собой объект с тремя свойствами (последние два являются необязательными):
Сообщения, отправленные с помощью метода messageSW()
используют MessageChannel
, чтобы получатель мог на них ответить. Чтобы ответить на сообщение, вы можете вызвать event.ports[0].postMessage(response)
в прослушивателе событий сообщения. Метод messageSW()
возвращает обещание, которое будет соответствовать любому response
которым вы ответите.
Вот пример отправки сообщений из окна сервис-воркеру и получения обратно ответа. Первый блок кода — это прослушиватель сообщений в сервис-воркере, а второй блок использует класс Workbox
для отправки сообщения и ожидания ответа:
Код в sw.js:
const SW_VERSION = '1.0.0';
addEventListener('message', event => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
Код в main.js (работает в окне):
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
Управление несовместимостью версий
В приведенном выше примере показано, как можно реализовать проверку версии сервис-воркера из окна. Этот пример используется потому, что когда вы отправляете сообщения туда и обратно между окном и сервис-воркером, очень важно помнить, что ваш сервис-воркер может использовать не ту же версию вашего сайта, что и код вашей страницы, и Решение этой проблемы различается в зависимости от того, обслуживаете ли вы свои страницы сначала по сети или по кэшу.
Сеть прежде всего
При первом обслуживании сети страниц ваши пользователи всегда будут получать последнюю версию вашего HTML с вашего сервера. Однако при первом повторном посещении пользователем вашего сайта (после развертывания обновления) HTML-код, который он получит, будет для последней версии, но сервис-воркер, работающий в его браузере, будет версией, установленной ранее (возможно, многие старые версии). .
Важно понимать эту возможность, потому что если JavaScript, загруженный текущей версией вашей страницы, отправляет сообщение более старой версии вашего сервис-воркера, эта версия может не знать, как ответить (или она может ответить в несовместимом формате).
В результате рекомендуется всегда проверять версию вашего сервис-воркера и проверять наличие совместимых версий, прежде чем приступать к какой-либо критической работе.
Например, в приведенном выше коде, если версия сервисного работника, возвращаемая этим вызовом messageSW()
, старше ожидаемой версии, было бы разумно подождать, пока не будет найдено обновление (что должно произойти при вызове register()
). На этом этапе вы можете либо уведомить пользователя или об обновлении, либо вручную пропустить фазу ожидания , чтобы сразу активировать нового сервис-воркера.
Сначала кэшируйте
В отличие от того, когда вы обслуживаете страницы в первую очередь по сети, при обслуживании страниц в первую очередь в кеше вы знаете, что ваша страница изначально всегда будет той же версии, что и ваш сервис-воркер (потому что именно она ее обслуживает). И в результате можно безопасно сразу использовать messageSW()
.
Однако если обновленная версия вашего сервис-воркера найдена и активируется, когда ваша страница вызывает register()
(т. е. вы намеренно пропускаете фазу ожидания ), отправлять ему сообщения может быть уже небезопасно.
Одной из стратегий управления этой возможностью является использование схемы управления версиями, которая позволяет вам различать критические обновления и некритические обновления, и в случае критического обновления вы будете знать, что отправлять сообщение сервисному работнику небезопасно. Вместо этого вы хотели бы предупредить пользователя о том, что он использует старую версию страницы, и предложить ему перезагрузить страницу, чтобы получить обновление.
Пропустить помощник по ожиданию
Обычное соглашение об использовании окна для обмена сообщениями с работником службы заключается в отправке сообщения {type: 'SKIP_WAITING'}
чтобы дать указание установленному работнику службы пропустить фазу ожидания и активировать.
Начиная с Workbox v6, метод messageSkipWaiting()
можно использовать для отправки сообщения {type: 'SKIP_WAITING'}
ожидающему сервисному работнику, связанному с текущей регистрацией сервисного работника. Он ничего не будет делать, если нет ожидающего сервисного работника.
Типы
Workbox
Класс, помогающий обрабатывать регистрацию, обновления и реагирование на события жизненного цикла Service Worker.
Характеристики
- конструктор
пустота
Создает новый экземпляр Workbox с URL-адресом сценария и параметрами сервисного работника. URL-адрес и параметры сценария такие же, как и при вызове navigator.serviceWorker.register(scriptURL, options) .
Функция
constructor
выглядит так:(scriptURL: string | TrustedScriptURL, registerOptions?: object) => {...}
- URL-адрес сценария
строка | TrustedScriptURL
Сценарий сервисного работника, связанный с этим экземпляром. Поддерживается использование
TrustedScriptURL
. - РегистрацияОпции
объект необязательный
- возвращает
- активный
Обещание<ServiceWorker>
- контроль
Обещание<ServiceWorker>
- получитьSW
пустота
Разрешается с помощью ссылки на работника службы, который соответствует URL-адресу сценария этого экземпляра, как только он станет доступен.
Если во время регистрации уже существует активный или ожидающий сервис-воркер с соответствующим URL-адресом сценария, он будет использоваться (при этом ожидающий сервис-воркер имеет приоритет над активным сервис-воркером, если оба совпадают, поскольку ожидающий сервис-воркер был бы зарегистрирован совсем недавно). Если во время регистрации нет подходящего активного или ожидающего сервис-воркера, то обещание не будет выполнено до тех пор, пока обновление не будет найдено и не начнет установку, после чего будет использован установочный сервис-воркер.
Функция
getSW
выглядит так:() => {...}
- возвращает
Обещание<ServiceWorker>
- сообщениеSW
пустота
Отправляет переданный объект данных сервисному работнику, зарегистрированному этим экземпляром (через
workbox-window.Workbox#getSW
), и разрешает ответ (если таковой имеется).Ответ можно установить в обработчике сообщений в сервис-воркере, вызвав
event.ports[0].postMessage(...)
, который разрешит обещание, возвращенноеmessageSW()
. Если ответ не задан, обещание никогда не будет выполнено.Функция
messageSW
выглядит так:(data: object) => {...}
- данные
объект
Объект для отправки сервисному работнику
- возвращает
Обещание <любое>
- сообщениеПропуститьОжидание
пустота
Отправляет сообщение
{type: 'SKIP_WAITING'}
сервисному работнику, который в данный момент находится в состоянииwaiting
, связанном с текущей регистрацией.Если текущая регистрация отсутствует или работник службы не
waiting
, этот вызов не будет иметь никакого эффекта.Функция
messageSkipWaiting
выглядит так:() => {...}
- зарегистрироваться
пустота
Регистрирует сервисного работника для URL-адреса сценария этого экземпляра и параметров сервисного работника. По умолчанию этот метод откладывает регистрацию до тех пор, пока окно не загрузится.
Функция
register
выглядит так:(options?: object) => {...}
- параметры
объект необязательный
- немедленный
логическое значение необязательно
- возвращает
Обещание<ServiceWorkerRegistration>
- обновлять
пустота
Проверяет наличие обновлений зарегистрированного сервисного работника.
Функция
update
выглядит так:() => {...}
- возвращает
Обещание<void>
WorkboxEventMap
Характеристики
- активирован
- активация
- контроль
- установлен
- установка
- сообщение
- избыточный
- ожидающий
WorkboxLifecycleEvent
Характеристики
- isExternal
логическое значение необязательно
- isUpdate
логическое значение необязательно
- оригинальное событие
Событие необязательно
- SW
Сервисворкер необязательно
- цель
WorkboxEventTarget необязательно
- тип
типОператор
WorkboxLifecycleEventMap
Характеристики
- активирован
- активация
- контроль
- установлен
- установка
- избыточный
- ожидающий
WorkboxLifecycleWaitingEvent
Характеристики
- isExternal
логическое значение необязательно
- isUpdate
логическое значение необязательно
- оригинальное событие
Событие необязательно
- SW
Сервисворкер необязательно
- цель
WorkboxEventTarget необязательно
- тип
типОператор
- былоОжиданиеBeforeРегистрация
логическое значение необязательно
WorkboxMessageEvent
Характеристики
- данные
любой
- isExternal
логическое значение необязательно
- оригинальное событие
Событие
- порты
типОператор
- SW
Сервисворкер необязательно
- цель
WorkboxEventTarget необязательно
- тип
"сообщение"
Методы
messageSW()
workbox-window.messageSW(
sw: ServiceWorker,
data: object,
)
Отправляет объект данных сервисному работнику через postMessage
и разрешает ответ (если таковой имеется).
Ответ можно установить в обработчике сообщений в сервис-воркере, вызвав event.ports[0].postMessage(...)
, который разрешит обещание, возвращенное messageSW()
. Если ответ не задан, обещание не будет разрешено.
Параметры
- SW
Сервисворкер
Работник службы, которому нужно отправить сообщение.
- данные
объект
Объект для отправки сервисному работнику.
Возврат
Обещание <любое>