Workbox
Что такое Workbox
?
Workbox (далее -
WB
) - это библиотека (точнее, набор библиотек), основной целью которой является "предоставление лучших практик и избавление от шаблонного кода при работе с сервис-воркерами" (далее - СВ).
Если вы впервые слышите о СВ, то перед изучением данного руководства рекомендую ознакомиться со следующими материалами:
- Service Worker API - MDN
- Service Workers: an Introduction - Web Fundamentals
- Визуализация работы сервис-воркеров - Хабр
- Рецепты по приготовлению офлайн-приложений - Хабр
WB
предоставляет следующие возможности:
- предварительное кеширование
- кеширование во время выполнения
- стратегии (кеширования)
- обработка (перехват сетевых) запросов
- фоновая синхронизация
- помощь в отладке
На что похож WB API
?
Ниже приведены примеры основных подходов к разработке прогрессивных веб-приложений (приложений, в которых используются возможности, предоставляемые СВ).
Кеширование шрифтов Google
Хотели бы вы, чтобы ваши гугл-шрифты были доступны в режиме офлайн после того, как пользователь в первый раз посетил ваш сайт? Тогда запишите их в кеш:
import { ExpirationPlugin } from 'workbox-expiration'
import { registerRoute } from 'workbox-routing'
import { StaleWhileRevalidate } from 'workbox-strategies'
// Записываем гугл-шри фты в кеш с применением стратегии `stale-while-revalidate` (см. ниже) с
// ограничением максимального количества вхождений (записей в кеше)
registerRoute(
({ url }) =>
url.origin === 'https://fonts.googleapis.com' ||
url.origin === 'https://fonts.gstatic.com',
new StaleWhileRevalidate({
cacheName: 'google-fonts',
plugins: [new ExpirationPlugin({ maxEntries: 20 })]
})
)
Кеширование JavaScript
и CSS
Сделайте ваш JS
и CSS-код
быстрым за счет его доставки из кеша, будучи уверенными в его актуальности за счет периодического обновления (в фоновом режиме):
import { registerRoute } from 'workbox-routing'
import { StaleWhileRevalidate } from 'workbox-strategies'
registerRoute(
({ request }) =>
request.destination === 'script' || request.destination === 'style',
new StaleWhileRevalidate()
)
Кеширование изображений
Как правило, основной "вес" страницы - это изображения. Используйте следующее правило для сохранения изображений в кеше, будучи уверенными, что они не переполнят хранилище пользователя:
import { CacheableResponsePlugin } from 'workbox-cacheable-response'
import { CacheFirst } from 'workbox-strategies'
import { ExpirationPlugin } from 'workbox-expiration'
import { registerRoute } from 'workbox-routing'
registerRoute(
({ register }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images',
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200]
}),
new ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 дней
})
]
})
)
Предварительное кеширование
Используйте WB
для предварительного кеширования ресурсов, используемых вашим приложением, с помощью CLI
, Node-модуля
или Webpack-плагина
.
// CLI
workbox wizard
// Node
const { generateSW } = require('workbox-build')
generateSW({
swDest: './build/sw.js',
globDirectory: './app',
globPatterns: '**/*.{js, css, html, png}'
})
// Webpack
const { GenerateSW } = require('workbox-webpack-plugin')
module.exports = {
// другие настройки вебпака
plugins: [
// другие плагины
new GenerateSW()
]
}
Гугл-аналитика в режиме офлайн
Хотите, чтобы гугл-аналитика продолжала работать при отсутствии подключения к сети? Никаких проблем.
import * as googleAnalytics from 'wortkbox-google-analytics'
googleAnalytics.initialize()
Начало работы
WB
- это набор библиотек для помощи в создании и управлении СВ и кешированием через CacheStorage API
. СВ и CacheStorage API
при совместном использовании позволяют управлять тем, откуда загружаются ресурсы, используемые приложением (JavaScript
, CSS
, HTML
, изображения и т.д.), из сети или из кеша.
Установка
Начиная с 5 версии, WB
может быть использован внутри СВ с помощью JS-модулей
и npm
посредством установки необходимых модулей WB
и их импорта.
К сожалению, JS-модули
не работают в СВ, поэтому для компиляции СВ требуется сборщик модулей, такой как Webpack
, Rollup
или Parcel
(см. ниже).
Создание СВ
Перед использованием WB
, необходимо создать СВ и зарегистрировать его на странице. Начнем с создания файла service-worker.js
в корневой директории проекта:
console.log('Привет от сервис-воркера!')
Далее регистрируем СВ на странице:
<script>
// Проверяем поддержку СВ
if ('serviceWorker' in navigator) {
// Ожидаем полной загрузки страницы
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
})
}
</script>
Это сообщает браузеру, что данный СВ должен использоваться для текущей страницы. Если вы перезагрузите страницу, откроете инструменты разработчика Chrome
и перейдете во вкладку Console
, то увидите там сообщение от СВ.
Если переключиться на вкладку Application
, то можно увидеть, что СВ был успешно зарегистрирован.
После этого можно подключать WB
.
Использование WB
WB
- это набор npm-модулей
. Сначала требуется установить нужный модуль, затем импортировать его. Одними из основных возможностей WB
является маршрутизация и стратегии кеширования, с них и начнем.
Маршрутизация и стратегии кеширования
WB
позволяет управлять кешировани ем HTTP-запросов с помощью различных стратегий кеширования. Первым делом необходимо определить, соответствует ли поступивший запрос определенному критерию и, если это так, применить к нему соответствующую стратегию. Можно использовать предварительно определенную стратегию (предоставляемую WB
) или определить собственную. Базовая реализация маршрутизации и кеширования может выглядеть так:
import { registerRoute } from 'workbox-routing'
import {
NetworkFirst,
StaleWhileRevalidate,
CacheFirst
} from 'workbox-strategies'
// Используется для фильтрации запросов на основе статус-кодов, заголовков или обоих сразу (см. ниже)
import { CacheableResponsePlugin } from 'workbox-cacheable-response'
// Используется для ограничения количества записей в кеше и удаления записей по истечении определенного времени (см. ниже)
import { ExpirationPlugin } from 'workbox-expiration'
// Кешируем страницы (`HTML`) с помощью стратегии `Network First` (сначала сеть)
registerRoute(
// проверяем, что запрос - это переход на новую страницу
({ request }) => request.mode === 'navigate',
new NetworkFirst({
// помещаем все файлы в кеш с названием 'pages'
cacheName: 'pages',
plugins: [
// кешируем только результаты со статусом 200
new CacheableResponsePlugin({
statuses: [200]
})
]
})
)
// Кешируем запросы на получение `CSS`, `JS` и веб-воркеров с помощью стратегии `Stale While Revalidate` (считается устаревшим после запроса)
registerRoute({
// проверяем, что цель запроса - это таблица стилей, скрипт или воркер
({ request }) =>
request.destination === 'style' ||
request.destination === 'script' ||
request.destination === 'worker',
new StaleWhileRevalidate({
// помещаем файлы в кеш с названием 'assets'
cacheName: 'assets',
plugins: [
new CacheableResponsePlugin({
statuses: [200]
})
]
})
})
// Кешируем изображения с помощью стратегии `Cache First` (сначала кеш)
registerRoute(
// проверяем, что цель запроса - изображение
({ request }) => request.destination === 'image',
new CacheFirst({
// помещаем файлы в кеш с названием 'images'
cacheName: 'images',
plugins: [
new CacheableResponsePlugin({
statuses: [200]
}),
// кешируем до 50 изображений в течение 30 дней
new ExpirationPlugin({
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24 * 30
})
]
})
)
Предварительное кеширование
В дополнение к кешированию после выполнения запроса (кешированию во время выполнения), WB
поддерживает предварительное кеширование - кеширование ресурсов во время установки СВ. Хорошими кандидатами для предварительного кеширования являются начальный URL
приложения, резервная (офлайн) страница, а также ключевые JS
и CSS-файлы
. Предварительное кеширование позволяет гарантиров ать, что основные ресурсы приложения являются доступными в момент, когда СВ получает контроль над страницей.
Предварительное кеширование можно использовать внутри СВ с помощью плагина, предоставляемого сборщиком модулей, поддерживающим встраивание (внедрение) “манифеста предварительного кеширования” (см. ниже):
import { precacheAndRoute } from 'workbox-precaching'
precacheAndRoute(self.__WB_MANIFEST)
Данный СВ предварительно кеширует файлы во время установки, заменяя self.__WB_MANIFEST
списком вхождений, добавляемых в СВ во время сборки.
Резервный контент
Как правило, для подготовки приложения к работе в режиме офлайн вместо "дефолтной" страницы с ошибкой предоставляется резервная страница. С помощью WB
этот паттерн можно реализовать при помощи нескольких строк кода:
import { precacheAndRoute } from 'workbox-precaching'
import { setCatchHandler } from 'workbox-routing'
// Обеспечиваем кеширование `/offline.html` во время сборки
precacheAndRoute(self.__WB_MANIFEST)
// Перехватываем ошибки, связанные с маршрутизацией, такие как отсутствие подключения к сети
setCatchHandler(async ({ event }) => {
// Возвращаем предварительно сохраненную резервную страницу при запросе “документа”
if (event.request.destination === 'document') {
return matchPrecache('/offline.html')
}
return Response.error()
})
Данный СВ предварительно сохраняет резервную страницу и возвращает ее при отсутствии подключения к сети вместо ошибки.
Использование сборщиков модулей с WB
Как было отмечено ранее, начиная с 5 версии, WB
может использоваться в СВ с помощью JS-модулей
.
Создание сборки
Выбор сборщика модулей
Подойдет любой сборщик, поддерживающий модули, например, Webpack
, Rollup
или Parcel
.
Написание кода СВ
Ниже приведен пример гипотетического СВ, импортирующего несколько WB-библиотек
. Данный СВ должен быть обработан сборщиком перед запуском в браузере.
import { precacheAndRoute } from 'workbox-precaching'
import { registerRoute } from 'workbox-routing'
import { CacheFirst } from 'workbox-strategies'
// Используем импортированные библиотеки для реализации кеширования,
// маршрутизации и другой логики
precacheAndRoute(self.__WB_MANIFEST)
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({ cacheName: 'images' })
)
// и т.д.
Следует отметить, что WB
поддерживает расширенное логгирование в режиме для разработки и минимальное логгирование в производственном режиме.
Опционально: встраивание манифеста предварительного кеширования
Для того, чтобы кешировать ресурсы во время установки СВ, необходимо предоставить WB
оформленный определенным образом список URL
. Данный список называется “манифестом предварительного кеширования” (precaching manifest).
Место для вставки такого манифеста во время сборки обозначается с помощью self.__WB_MANIFEST
.
Настройка вебпака
Плагин Inject Manifest
отвечает как за сборку СВ, так и за встраивание манифеста. При его использование не требуется отдельно настраивать СВ.
Настройка CLI
Вместо плагина для сборщика можно использовать workbox-cli
в режиме injectManifest
.
При использов ании данного подхода после каждой успешной сборки необходимо запускать injectManifest
, передавая ему свежего СВ в качестве настройки swSrc
.
GenerateSW
В режиме generateSW
при использовании workbox-build
, workbox-cli
или GenerateSW
в workbox-webpack-plugin
не нужно настраивать процесс сборки - готовый к использованию СВ будет сгенерирован автоматически.
Обработка запросов
Маршрутизация в WB
- это процесс определения совпадения маршрута с запросом и обработка совпавшего запроса (отправка ответа на него).
Существует три способа реализации определения совпадений с помощью workbox-routing
:
- Строка
- Регулярное выражение
- Функция обратного вызова
Поиск совпадений
С помощью строк
Запрашиваемый URL
сравнивается со строкой и, если они равны, запрос передается обработчику (handler
). Маршрутизатор для /logo.png
может быть реализован так:
import { registerRoute } from 'workbox-routing'
registerRoute('/logo.png', handler)
Проблема такого подхода состоит в том, что запросы из другого источника (origin) не будут совпадать со строкой. Поэтому лучше определять абсолютный путь:
import { registerRoute } from 'workbox-routing'
registerRoute('https://some-other-origin.com/logo.png', handler)
С помощью регулярных выражений
Лучшим способом обработки группы запросов является использование регулярного выражения.
Данный подход является гораздо более гибким, чем предыдущий. Например, вот как можно реализовать маршрутизацию на основе расширений файлов:
import { registerRoute } from 'workbox-routing'
registerRoute(new RegExp('\\.js$'), jsHandler)
registerRoute(new RegExp('\\.css$'), cssHandler)
Регулярные выражения также могут использоваться для поиска совпадения с определенным форматом URL
, например:
// /blog/<год>/<месяц>/<пост заголовок идентификатор>
import { registerRoute } from 'workbox-routing'
registerRoute(new RegExp('/blog/\\d{4}/\\d{2}/.+'), handler)
И снова наш маршрут не будет совпадать с запросом из другого источника. Эту проблему можно решить с помощью wildcard
(.+
):
import { registerRoute } from 'workbox-routing'
registerRoute(new RegExp('.+/blog/\\d{4}/\\d{2}/.+'), handler)
Такой же фокус можно проделать с первым примером:
import { registerRoute } from 'workbox-routing'
registerRoute(new RegExp('.+\\.js$'), jsHandler)
registerRoute(new RegExp('.+\\.css$'), cssHandler)
С помощью колбеков
Для дальнейшей кастомизации определения совпадений можно воспользоваться функцией matchCallback
. Данная функция принимает ExtendableEvent
, Request
и объект URL
.
Простой пример:
import { registerRoute } from 'workbox-routing'
const matchFunction = ({ url, request, event }) => {
return url.href === 'https://example.com/app.js'
}
registerRoute(matchFunction, handler)
Обработка запросов
Существует два способа обработки запросов:
- Использование одной из стратегий, предоставляемых
workbox-strategies
- Предоставление функции обратного вызова, возвращающей промис, который разрешается объектом
Response
С помощью стратегий
WB
предоставляет следующие стратегии кешир ования:
Stale While Revalidate
(ресурс считается устаревшим после запроса) - ответ возвращается из кеша, после чего выполняется обновление кеша в фоновом режиме (если ответ еще не кеширован, он возвращается из сети). Данная стратегия является безопасной, поскольку кеш все время обновляется. Ее недостатком является постоянный запрос ресурсов из сетиNetwork First
(сначала сеть) - сперва предпринимается попытка вернуть ответ из сети. Если данная попытка увенчалась успехом, ответ возвращается и после этого записывается в кеш. В противном случае, возвращается ответ из кешаCache First
(сначала кеш) - если ответ имеется в кеше, он возвращается. Иначе отправляется запрос к сети и полученный (валидный) ответ записывается в кеш и после этого передается браузеруNetwork Only
(только сеть)Cache Only
(только кеш)
import { registerRoute } from 'workbox-routing'
import * as strategies from 'workbox-strategies'
registerRoute(match, new strategies.StaleWhileRevalidate())
registerRoute(match, new strategies.NetworkFirst())
registerRoute(match, new strategies.CacheFirst())
registerRoute(match, new strategies.NetworkOnly())
registerRoute(match, new strategies.CacheOnly())
Поведение маршрутизатора при использовании любой стратегии может быть кастомизировано посредством определения используемого кеша или добавления плагинов:
import { StaleWhileRevalidate } from 'workbox-strategies'
new StaleWhileRevalidate({
// используем кастомный кеш
cacheName: 'my-cache-name',
// добавляем массив плагинов (например, `ExpirationPlugin`)
plugins: [
...
]
})
С помощью колбеков
Для дальнейшей кастомизации обработки запросов можно воспользоваться асинхронным колбеком, возвращающим объект Response
. Данный колбек принимает объект со свойствами url
и event
:
import { registerRoute } from 'workbox-routing'
const handler = async ({ url, event }) => {
return new Response(`Ответ от кастомного обработчика.`)
}
registerRoute(match, handler)
Следует отметить, что значение, возвращаемое колбеком match
, передается в функцию обратного вызова handler
в виде аргумента params
:
import { registerRoute } from 'workbox-routing'
const match = ({ url, event }) => {
if (url.pathname === '/example') {
return {
name: 'Workbox',
type: 'Руководство'
}
}
}
const handler = async ({ url, event, params }) => {
// ответ будет выглядеть как "Руководство по Workbox"
return new Response(`${params.type} по ${params.name}`)
}
registerRoute(match, handler)
Это может быть полезным, например, в случае, когда у нас имеется какая-то информации, “зашитая” в URL, которая извлекается в match
(путем разбора URL
) и используется в handler
.
Настройка WB
WB
предоставляет названия для кеша и уровни логгирования из коробки. Эти значения можно изменять.
Настройка названия кеша
При использовании различных WB API
можно заметить, что некоторый кеш создается автоматическ и.
При отсутствии названий кеша WB
использует дефолтные названия, определенные в workbox-core
. Предопределенные названия кеша выглядят так:
import { cacheNames } from 'workbox-core'
const precacheCacheName = cacheNames.precache
const runtimeCacheName = cacheNames.runtime
const googleAnalyticsCacheName = cacheNames.googleAnalytics
Каждое название состоит из трех частей:
<prefix>-<cacheId>-<suffix>
Эти названия можно менять как целиком, так и по частям:
import { setCacheNameDetails } from 'workbox-core'
setCacheNameDetails({
prefix: 'my-app',
suffix: 'v1'
})
Или так:
import { setCacheNameDetails } from 'workbox-core'
setCacheNameDetails({
prefix: 'my-app',
suffix: 'v1',
precache: 'custom-precache-name',
runtime: 'custom-runtime-name',
googleAnalytics: 'custom-google-analytics-name'
})
Кастомные названия кеша в стратегиях
Некоторые части WB API
принимают настройку cacheName
с названием кеша. К таким API
, в частности, относятся стратегии. В случае установки настройки cacheName
, префикс и суффикс не используются. Например, вот как можно определить название для кеша изображений:
import { registerRoute } from 'workbox-routing'
import { CacheFirst } from 'workbox-strategies'
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'my-image-cache'
})
)
Теперь изображения буд ут записываться в кеш my-image-cache
.
Настройки запросов в стратегиях
При использовании кастомной стратегии для кеширования во время выполнения может потребоваться настройка некоторых аспектов исходящих запросов. Например, в запросе могут отсутствовать необходимые данные для авторизации.
Для реализации такого сценария мы можем передать в конструктор стратегии настройку fetchOptions
, соответствующую настройкам init
Fetch API
. Эти настройки будут применяться в отношении всех исходящих запросов, которые обрабатываются данной стратегией.
import { registerRoute } from 'workbox-routing'
import { NetworkFirst } from 'workbox-strategies'
registerRoute(
({ url }) => url.origin === 'https://third-party.example.com',
new NetworkFirst({
fetchOptions: {
credentials: 'include'
}
})
)
Обработка запросов к другим источникам
Запросы к другим источникам обрабатываются WB
особым образом.
Запро сы к другим источникам и непрозрачные ответы
Одним из основных защитных механизмов, используемых браузером, является запрет доступа к телу и другим частям ответа, поступившего из другого источника без CORS-заголовков
. Такие ответы являются "непрозрачными" (opaque). СВ в этом отношении не является исключением.
CORS
Если вы запрашиваете ресурсы с источника, поддерживающего CORS
, но получаете непрозрачные ответы, возможно, вы неправильно их запрашиваете.
Например, следующий HTML
запускает запросы, приводящие к непрозрачным ответам, даже если example.com
поддерживает CORS
:
<link rel="stylesheet" href="https://example.com/path/to/style.css" />
<img src="https://example.com/path/to/image.png" alt="" />
В данном случае, для отправки запросов, в ответ на которые возвращаются прозрачные ответы, следует включить режим CORS
с помощью атрибута crossorigin
:
<link
crossorigin="anonymous"
rel="stylesheet"
href="https://example.com/path/to/style.css"
/>
<img crossorigin="anonymous" src="https://example.com/path/to/image.png" alt="" />
Опасность кеширования непрозрачных ответов
Обычно, WB
не кеширует непрозрачные ответы. Причина этого заключается в том, что такие ответы могут привести к плохому "состоянию" приложения. Предположим, что мы определили такой маршрутизатор:
import { registerRoute } from 'workbox-routing'
import { CacheFirst } from 'workbox-strategies'
registerRoute('https://cdn.google.com/example-script.min.js', new CacheFirst())
В данном случае, любой ответ будет записываться в кеш и возвращаться в ответ на запрос. Сохранение и возврат непрозрачного ответа могут привести к поломке приложения.
Тем не менее, данный подход можно использовать с такими стратегиями как StaleWhileRevalidate
и NetworkFirst
, поскольку в этом случае время жизни плохого ответа будет непродолжительным за счет регулярного обновления кеша.
import { registerRoute } from 'workbox-routing'
import { NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'
registerRoute(
'https://cdn.google.com/example-script.min.js',
new NetworkFirst()
)
// или
registerRoute(
'https://cdn.google.com/example-script.min.js',
new StaleWhileRevalidate()
)
При использовании другой стратегии и получении непрозрачного ответа WB
выведет в консоль предупреждение о том, что ответ не кеширован.
Принудительное кеширование непрозрачных ответов
Непрозрачные ответы могут принудительно записываться в кеш с помощью плагинов, таких как workbox-cacheable-response
:
import { registerRoute } from 'workbox-routing'
import { NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'
import { CacheableResponsePlugin } from 'workbox-cacheable-response'
registerRoute(
'https://cdn.google.com/example-script.min.js',
new CacheFirst({
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200]
})
]
})
)
Использование плагинов
Плагины позволяют определять дополнительное поведение, связанное с обработкой запросов или ответов, в течение жизненного цикла запроса.
Плагины, предоставляемые WB
WB
предоставляет следующие плагины:
BackgroundSyncPlugin
- при отсутствии подключения к сети запрос помещается в очередь фоновой синхронизации (background sync queue) и повторно отправляется при следующем возникновении событияsync
BroadcastUpdatePlugin
- при любом обновлении кеша отправляется (dispatch) сообщение вBroadcastChannel
или черезpostMessage()
CacheableResponsePlugin
- кеширование только тех запросов, которые соответствуют установленным критериямExpirationPlugin
- определение количества или максимального возраста записей в кешеRangeRequestsPlugin
- отправка ответов на запросы, включающие заголовокRange
(частичный контент из кеша)
import { registerRoute } from 'workbox-routing'
import { CacheFirst } from 'workbox-strategies'
import { ExpirationPlugin } from 'workbox-expiration'
registerRoute(
({ request }) => request.destination === 'image',
new CacheFirst({
cacheName: 'images',
plugins: [
new ExpirationPlugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 дней
})
]
})
)
Кастомные плагины
Пользовательский плагин - это объект, содержащий один из следующих методов:
cacheWillUpdate
: вызывается перед тем, какResponse
используется для обновления кеша. Он позволяет изменять ответ перед его добавлением в кеш или возвращатьnull
для предотвращения обновленияcacheDidUpdate
: вызывается перед добавлением новой записи в кеш или обновлением существующей записи. Может использоваться для выполнения каких-либо операций после обновления кешаcacheKeyWillBeUsed
: вызывается перед использованием запроса в качестве ключа для кеша, как для поиска кеша (в режимеread
), так и для записи в кеш (в режимеwrite
). Может использоваться для перезаписи или нормализацииURL
пе ред их использованием для доступа к кешуcacheResponseWillBeUsed
: вызывается перед использованием ответа из кеша. Позволяет проверять ответ и возвращатьnull
или другой ответrequestWillFetch
: вызывается перед выполнением сетевого запроса. Позволяет изменятьRequest
fetchDidFail
: вызывается при провале запроса, чаще всего связанным сNetworkError
. Обратите внимание, что данный метод не вызывается при получении ошибки, например,404 Not Found
fetchDidSucceed
: вызывается при успешном запросе, независимо от статуса ответаhandlerWillStart
: вызывается перед запуском обработчика ответа. Может использоваться для установки начального состояния обработчикаhandlerWillRespond
: вызывается перед возвращением ответа обработчиком стратегии. Может использоваться для модификации ответа, возвращаемого обработчикомhandlerDidRespond
: вызывается после возвращения ответа методомhandle()
. Может использоваться для записи любых деталей ответа, например, после изменений, произведенных с ним плагинамиhandleDidComplete
: вызывается после добавления вevent
всех "промисов для расширения жизненного цикла". Может использоваться для обработки данных, которые становятся доступными после выполнения своих задач обработчиком (например, кеширование статуса обращений, задержка кеширования, задержка сетевого запроса и т.д.)handleDidError
: вызывается, когда обработчик не может предоставить валидный ответ ни из кеша, ни из сети. Может использоваться для предоставления резервного контента при возникновении сетевой ошибки
Все эти функции вызываются с ключевым словом await
при достижении кешем (или событием fetch
) контрольной точки.
Вот как выглядит использование этих колбеков:
const myPlugin = {
cacheWillUpdate: async ({ request, response, event, state }) => {
// Возвращаем `response`, другой объект `Response` или `null`
return response
},
cacheDidUpdate: async ({
cacheName,
request,
oldResponse,
newResponse,
event,
state
}) => {
// Ничего не возвращаем.
// Обратите внимание `newResponse.bodyUsed` имеет значение `true` при вызове.
// Это означает, что тело запроса уже прочитано. Если вам требуется доступ
// к телу свежего ответа:
// const freshResponse = await caches.match(request, { cacheName })
},
cacheKeyWillBeUsed: async ({ request, mode, params, event, state }) => {
// `request` - это объект `Request`, который может быть использован в качестве ключа кеша.
// `mode` может иметь значение 'read' или 'write'.
// Возвращаем строку или `Request`, свойство `url` которого будет использовано как ключ кеша.
// Возврат оригинального `request` лишает смысла использование этого колбека
return request
},
cachedResponseWillBeUsed: async ({
cacheName,
request,
matchOptions,
cachedResponse,
event,
state
}) => {
// Возвращаем `cachedResponse`, другой объект `Response` или `null`
return cachedResponse
},
requestWillFetch: async ({ request, event, state }) => {
// Возвращаем `request` или другой объект `Request`
return request
},
fetchDidFail: async ({ originalRequest, request, error, event, state }) => {
// Ничего не возвращаем.
// Обратите внимание: `originalRequest` - это запрос браузера, `request` - это
// запрос, обработанный плагинами в
// колбеках `requestWillFetch`, а `error` - это исключение, вызвавшее
// провал `fetch()`
},
fetchDidSucceed: async ({ request, response, event, state }) => {
// Возвращаем `response` для использования сетевого ответа как есть или создаем и возвращаем новый объект `Response`
return response
},
handlerWillStart: async ({ request, event, state }) => {
// Ничего не возвращаем.
// Здесь можно установить начальное состояние обработчика
},
handlerWillRespond: async ({ request, response, event, state }) => {
// Возвращаем `response` или другой объект `Response`
return response
},
handlerDidRespond: async ({ request, response, event, state }) => {
// Ничего не возвращаем.
// Здесь можно зафиксировать конечный результат
},
handlerDidComplete: async ({ request, response, error, event, state }) => {
// Ничего не возвращаем.
// Здесь можно работать с дополнительными данными
},
handlerDidError: async ({ request, event, error, state }) => {
// Возвращаем `response` или другой объект `Response` (в качестве подстраховки), или `null`
return response
}
}
Как правило, объект event
, передаваемый в колбек - это исходное событие, вызвавшее операцию fetch
или cache
. Тем не менее, всегда следует проверять наличие данного объекта перед его использованием. При вызове метода handle()
в стратегии event
, передаваемый в него, будет передан в колбеки плагинов.
Все колбеки также приним ают объект state
, который является уникальным по отношению к объекту конкретного плагина и вызову стратегии. Это делает возможным реализацию плагинов, где один колбек выполняет операции на основе данных, полученных от другого колбека в том же плагине (например, вычисление дельты времени между запуском requestWillFetch()
и fetchDidSucceed()
или fetchDidFail()
).
Квота хранилища
Каждый браузер устанавливает определенный лимит объема памяти, который может использоваться приложением. Данный лимит зависит от браузера. Объем используемой и доступной памяти можно проверить с помощью navigator.storage.estimate()
. WB
позволяет настроить автоматическую очистку хранилища при достижении квоты (storage quota).
import { registerRoute } from 'workbox-routing'
import { CacheFirst } from 'workbox-strategies'
import { ExpirationPlugin } from 'workbox-expiration'
registerRoute(
({ request }) => request.destination === 'image',
// Используем стратегию "сначала кеш" со следующими настройками
new CacheFirst({
// Название кеша
cacheName: 'images',
plugins: [
new ExpirationPlugin({
// Максимальное количество записей в кеше (сохраняемых файлов)
maxEntries: 50,
// Срок хранения файлов в кеше
maxAgeSeconds: 30 * 24 * 60 * 60,
// Автоматическая очистка хранилища при достижении квоты
purgeOnQuotaError: true
})
]
})
)
Общие ре комендации
Ниже приведено несколько примеров использования стратегий кеширования для разных случаев.
Гугл-шрифты
Сервис гугл-шрифтов состоит из двух частей:
- таблица стилей с определениями
@font-face
, содержащими ссылки на шрифты - статические файлы со шрифтами
Таблица стилей может часто изменяться, поэтому для нее лучше использовать стратегию stale-while-revalidate
, которая выполняет обновление кеша после каждого запроса. Для файлов шрифтов, которые меняются редко, можно использовать стратегию cache-first
.
import { registerRoute } from 'workbox-routing'
import { CacheFirst, StaleWhileRevalidate } from 'workbox-strategies'
import { CacheableResponsePlugin } from 'workbox-cacheable-response'
import { ExpirationPlugin } from 'workbox-expiration'
// Кешируем таблицу стилей с помощью стратегии `stale-while-revalidate`
registerRoute(
({ url }) => url.origin === 'https://fonts.googleapis.com',
new StaleWhileRevalidate({
cacheName: 'google-fonts-stylesheets'
})
)
// Кешируем файлы со шрифтами с помощью стратегии `cache-first` на 1 год
registerRoute(
({ url }) => url.origin === 'https://fonts.gstatic.com',
new CacheFirst({
cacheName: 'google-fonts-webfonts',
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200]
}),
new ExpirationPlugin({
maxAgeSeconds: 60 * 60 * 24 * 365,
maxEntries: 30
})
]
})
)