От автора: одна из лучших вещей, которую вы можете сделать для своего веб-сайта в 2022 году, — это добавить сервис-воркера, если у вас его еще нет. Сервис-воркеры наделяют ваш сайт сверхспособностями. Сегодня я хочу показать вам некоторые удивительные вещи, которые они могут делать, и дать вам шаблон применения, который вы можете использовать, чтобы сразу же начать пользоваться сервис-воркерами на своем сайте.
Что такое сервис-воркеры?
Сервис-воркер — это особый тип файла JavaScript, который действует как промежуточное ПО для вашего сайта. Любой запрос, поступающий с сайта, и любой ответ, который он получает, сначала проходят через файл сервис-воркера. Сервис-воркеры также имеют доступ к специальному кешу, где они могут локально сохранять ответы и ресурсы. Вместе эти функции позволяют…
Обслуживать часто используемые ресурсы из локального кэша, а не из сети, сокращая использование данных и повышая производительность.
Предоставлять доступ к важной информации (или даже ко всему сайту или приложению), когда посетитель выходит в офлайн.
Предварительно извлекать важные ресурсы и ответы API, чтобы они были готовы, когда они потребуются пользователю.
Предоставлять резервные ресурсы в ответ на ошибки HTTP.
Короче говоря, сервис-воркеры позволяют создавать более быстрые и устойчивые веб-интерфейсы. В отличие от обычных файлов JavaScript, сервис-воркеры не имеют доступа к модели DOM. Они также работают в своем собственном потоке и, как следствие, не блокируют выполнение другого JavaScript. Сервис-воркеры спроектированы так, чтобы быть полностью асинхронными.
Безопасность
Поскольку сервис-воркеры перехватывают каждый запрос и ответ от сайта или приложения, у них есть некоторые важные ограничения безопасности.
Cервис-воркеры следуют политике одной и той же локализации
Вы не можете запускать сервис-воркер из CDN или третьей стороны. Он должен быть размещен в том же домене, где он будет работать.
Сервис-воркеры работают только на сайтах с установленным SSL-сертификатом
Многие веб-хостинги предоставляют SSL-сертификаты бесплатно или за небольшую плату. Если вам удобно работать с командной строкой, вы также можете установить сертификат бесплатно с помощью Let’s Encrypt.
Существует исключение из требования SSL-сертификата для тестирования на локальном хосте, но вы не можете запускать свой сервис-воркер из протокола file://. У вас должен быть запущен локальный сервер.
Добавление сервис-воркера на сайт или в веб-приложение
Чтобы использовать сервис-воркера, первое, что нам нужно сделать, это зарегистрировать его в браузере. Вы можете зарегистрировать сервис-воркера с помощью метода navigator.serviceWorker.register(). Передайте путь к файлу сервис-воркера в качестве аргумента.
1 |
navigator.serviceWorker.register('sw.js'); |
Вы можете запустить этот код во внешнем файле JavaScript, но проедпочтительно запускать его непосредственно в элементе сценария, встроенного в HTML, чтобы он выполнялся как можно скорее.
В отличие от других типов файлов JavaScript, сервис-воркеры работают только с каталогом, в котором они находятся (и любым из его подкаталогов). Файл сервис-воркера, расположенный в /js/sw.js, будет работать только с файлами в каталоге /js. В результате вы должны поместить файл сервис-воркера в корневую директорию вашего сайта.
Хотя сервис-воркеры имеют хорошую поддержку браузера, рекомендуется убедиться, что браузер их поддерживает, прежде чем запускать сценарий регистрации.
1 2 3 |
if (navigator && navigator.serviceWorker) { navigator.serviceWorker.register('sw.js'); } |
После установки сервис-воркера браузер может его активировать. Как правило, это происходит только тогда, когда…
в настоящее время нет активных сервис-воркеров, или
пользователь обновляет страницу.
Сервис-воркер не будет запускать или перехватывать запросы, пока он не будет активирован.
Прослушивание запросов в сервис-воркере
Когда сервис-воркер активен, он может начать перехватывать запросы и выполнять другие задачи. Мы можем прослушивать запросы с помощью self.addEventListener().
1 2 3 4 |
// Listen for request events self.addEventListener('fetch', function (event) { // Do stuff... }); |
Внутри слушателя событий свойство event.request является самим объектом запроса. Для удобства мы можем сохранить его в переменной запроса.
В некоторых версиях браузера Chromium есть ошибка, которая выдает error, если страница открывается в новой вкладке. К счастью, есть простое решение от Пола Айриша, которое я на всякий случай включаю во все сервис-воркеры:
1 2 3 4 5 6 7 8 9 10 11 |
// Listen for request events self.addEventListener('fetch', function (event) { // Get the request let request = event.request; // Bug fix // https://stackoverflow.com/a/49719964 if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') return; }); |
Как только ваш сервис-воркер будет активирован, каждый отдельный запрос отправляется через него и будет перехватываться событием fetch.
Стратегии использования сервис-воркера
После того, как ваш сервис-воркер установлен и активирован, вы можете перехватывать запросы и ответы и обрабатывать их различными способами. Есть две основные стратегии, которые вы можете использовать в сервис-воркере:
Network-first. При таком подходе вы передаете запросы в сеть. Если запрос не найден или отсутствует подключение к сети, вы ищете запрос в кэше сервис-воркера.
Offline-first. При использовании офлайн подхода вы сначала проверяете запрошенный ресурс в кэше сервис-воркера. Если он не найден, то запрос отправляется в сеть.
Network-first и offline-first подходы работают в тандеме. Скорее всего, вы будете комбинировать подходы в зависимости от типа запрашиваемого ресурса.
Offline-first отлично подходит для больших ресурсов, которые не очень часто меняются: CSS, JavaScript, изображения и шрифты. Network-first лучше подходит для часто обновляемых ресурсов, таких как запросы HTML и API.
Стратегии кэширования ресурсов
Как помещать ресурсы в кеш вашего браузера? Обычно используется два подхода, в зависимости от типов ресурсов.
Предварительный кэш. У каждого сайта и веб-приложения есть набор основных ресурсов, которые используются практически на каждой странице: CSS, JavaScript, логотип и шрифты. Вы можете предварительно кэшировать и обслуживать их, используя оffline-first подход, когда они запрашиваются.
Кеш при просмотре. На вашем сайте или в приложении, вероятно, есть ресурсы, которые не будут доступны при каждом посещении или каждым посетителем; такие вещи, как сообщения в блогах и изображения, которые сопровождают статьи. Для таких ресурсов вы можете захотеть кэшировать их в режиме реального времени, когда посетитель обращается к ним.
Внедрение стратегий «Network-first» и «Offline-first» в сервис-воркере
Внутри события fetch в сервис-воркере метод request.headers.get(‘Accept’) возвращает MIME-тип содержимого. Мы можем использовать его, чтобы определить, к какому типу файла относится запрос. В MDN есть список общих файлов и их MIME-типы. Например, файлы HTML имеют тип MIME text/html.
Мы можем передать тип файла, который ищем, в метод String.includes() в качестве аргумента и использовать операторы if для различных ответов в зависимости от типа файла.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// Listen for request events self.addEventListener('fetch', function (event) { // Get the request let request = event.request; // Bug fix // https://stackoverflow.com/a/49719964 if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') return; // HTML files // Network-first if (request.headers.get('Accept').includes('text/html')) { // Handle HTML files... return; } // CSS & JavaScript // Offline-first if (request.headers.get('Accept').includes('text/css') || request.headers.get('Accept').includes('text/javascript')) { // Handle CSS and JavaScript files... return; } // Images // Offline-first if (request.headers.get('Accept').includes('image')) { // Handle images... } }); |
Network-first
Внутри каждого оператора if мы используем метод event.respondWith() для изменения ответа, отправляемого обратно в браузер.
Для ресурсов, использующих подход network-first, мы используем метод fetch(), для передачи запроса к HTML файлу. Если он возвращается успешно, мы вернем ответ в функцию обратного вызова. Это такое же поведение, как и при отсутствии сервис-воркера.
Если мы получим error, то можем использовать Promise.catch() для изменения ответа вместо отображения сообщения об ошибке браузера по умолчанию. Мы можем использовать метод caches.match() для поиска такой страницы и вернуть ее вместо ответа.
1 2 3 4 5 6 7 8 9 10 11 |
// Send the request to the network first // If it's not found, look in the cache event.respondWith( fetch(request).then(function (response) { return response; }).catch(function (error) { return caches.match(request).then(function (response) { return response; }); }) ); |
Offline-first
Для ресурсов, которые используют подход «Offline-first», мы сначала проверим кэш браузера с помощью метода caches.match(). Если совпадение будет найдено, мы вернем его. В противном случае мы будем использовать метод fetch() для передачи запроса в сеть.
1 2 3 4 5 6 7 8 9 |
// Check the cache first // If it's not found, send the request to the network event.respondWith( caches.match(request).then(function (response) { return response || fetch(request).then(function (response) { return response; }); }) ); |
Предварительное кэширование основных ресурсов
Внутри слушателя событий в сервис-воркере мы можем использовать метод caches.open(), чтобы открыть кеш сервис-воркера. Мы передаем имя, которое хотим использовать для кеша, в качестве аргумента.
Кэш ограничен вашим доменом. Другие сайты не могут получить к нему доступ, и если у них есть кеш с таким же именем, содержимое хранится совершенно отдельно.
Метод caches.open() возвращает промис. Если кеш с таким именем уже существует, промис будет связан с ним. Если нет, сервис-воркер сначала создаст кеш, а затем свяжет с ним промис.
1 2 3 4 |
// Listen for the install event self.addEventListener('install', function (event) { event.waitUntil(caches.open('app')); }); |
Потом мы можем связать метод then() с методом caches.open() с функцией обратного вызова. Чтобы добавить файлы в кеш, нам нужно запросить их, что мы можем сделать с помощью нового конструктора Request(). Мы можем использовать метод cache.add(), чтобы добавить файл в кеш сервис-воркера.
Затем — возвращаем объект кеша. Мы хотим, чтобы событие ожидало, пока файл не будет кэширован, поэтому давайте обернем наш код в метод event.waitUntil():
1 2 3 4 5 6 7 8 9 10 |
// Listen for the install event self.addEventListener('install', function (event) { // Cache the offline.html page event.waitUntil(caches.open('app').then(function (cache) { cache.add(new Request('offline.html')); return cache; })); }); |
Я считаю полезным создать массив с путями ко всем основным файлам. Затем, внутри слушателя событий, после того, как я открою кеш, я могу просмотреть каждый элемент и добавить его.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
let coreAssets = [ '/css/main.css', '/js/main.js', '/img/logo.svg', '/img/favicon.ico' ]; // On install, cache some stuff self.addEventListener('install', function (event) { // Cache core assets event.waitUntil(caches.open('app').then(function (cache) { for (let asset of coreAssets) { cache.add(new Request(asset)); } return cache; })); }); |
Кэш при просмотре
На вашем сайте или в приложении, вероятно, есть ресурсы, которые не будут доступны при каждом посещении или каждым посетителем; такие вещи, как сообщения в блогах и изображения, которые сопровождают статьи. Вы можете захотеть кэшировать такие ресурсы в режиме реального времени, когда посетитель обращается к ним. При последующих посещениях вы можете загружать их непосредственно из кеша (при подходе offline-first) или использовать их в качестве запасного варианта в случае сбоя сети (при подходе network-first).
Когда метод fetch() возвращает успешный ответ, мы можем использовать метод Response.clone() для создания его копии. Затем мы можем использовать метод caches.open(), чтобы открыть наш кеш.
Затем мы воспользуемся методом cache.put(), чтобы сохранить скопированный ответ в кеш, передав запрос и копию ответа в качестве аргументов. Поскольку это асинхронная функция, мы поместим наш код в метод event.waitUntil(). Это предотвращает завершение события до того, как мы сохраним нашу копию в кэше. Как только копия сохранена, мы можем вернуть ответ как обычно.
Мы используем cache.put() вместо cache.add(), потому что у нас уже есть ответ. Использование cache.add() создаст еще один сетевой вызов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// HTML files // Network-first if (request.headers.get('Accept').includes('text/html')) { event.respondWith( fetch(request).then(function (response) { // Create a copy of the response and save it to the cache let copy = response.clone(); event.waitUntil(caches.open('app').then(function (cache) { return cache.put(request, copy); })); // Return the response return response; }).catch(function (error) { return caches.match(request).then(function (response) { return response; }); }) ); } |
Соберем все вместе
Я подготовил шаблон кода на GitHub. Добавьте свои основные ресурсы в массив coreAssets и зарегистрируйте его на своем сайте.
Даже если вы больше ничего не добавите, это станет огромным толчком для вашего сайта в 2022 году. Но с сервис-воркерами можно сделать гораздо больше. Существуют расширенные стратегии кэширования для API. Вы можете предоставить офлайн страницу с важной информацией, если посетитель потеряет подключение к сети. Также вы можете очистить раздутые кеши, когда пользователь просматривает станицы вашего сайта.
Автор: Chris Ferdinandi
Источник: css-tricks.com
Редакция: Команда webformyself.
Читайте нас в Telegram, VK, Яндекс.Дзен