Это снова мы!

Добро пожаловать во вторую часть Дороги к PWA. Если вы здесь впервые, вы можете найти мой первый пост здесь.

Для начала я еще раз резюмирую основные функции PWA:

  1. Устанавливаемый: используйте веб-приложение с домашнего экрана.
  2. Более быстрая загрузка: улучшена загрузка благодаря кэшированию.
  3. Возможность работы в автономном режиме: даже без подключения мое приложение должно работать в некоторой степени.
  4. Прогрессивность: упомянутые функции являются опциональными, приложение должно нормально работать в неподдерживающих браузерах.

У всех элементов в этом списке есть одна общая черта: они в некотором роде полагаются на сервисных работников.

Итак, как вы, возможно, уже догадались, мы собираемся узнать довольно много о сервис-воркерах:

  • Что они вообще из себя представляют
  • На что они способны
  • Их жизненный цикл
  • Как мы можем зарегистрировать один

Я думаю, это звучит довольно интересно, так что давайте начнем!

Дорога к PWA — посещение сервисных работников

Чтобы углубиться в тему, Service Worker — это просто обычный JavaScript. Но в отличие от остального нашего веб-приложения, сервис-воркер работает в отдельном потоке, что имеет некоторые последствия:

  • Сервисные работники не имеют доступ к DOM
  • Связь между сервис-воркером и страницей происходит через postMessage()
  • Сервисные работники продолжают работать, даже когда:
  • Пользователь покинул/закрыл страницу
  • Пользователь закрыл свой браузер

Внутри сервис-воркера мы можем слушать и реагировать на определенные события. Существуют события жизненного цикла, а также события, связанные с нашим веб-приложением. Чуть позже мы подробнее рассмотрим эти события.

Итак, чтобы вычеркнуть первые два элемента из нашего списка вещей, о которых мы собираемся поговорить, я предпочитаю рассматривать сервис-воркеров как перехватчиков. Это фрагмент кода JavaScript, который выполняется в отдельном потоке и располагается прямо между нашим приложением и «интернетом». Мы можем реагировать на события жизненного цикла сервис-воркера, что идеально подходит для выполнения таких вещей, как предварительное кэширование ресурсов, но также можно перехватить любой сетевой запрос, который выполняется в нашем веб-приложении через сервис-воркера. Это позволяет сервис-воркеру манипулировать практически всем в перехваченном запросе (URL-адрес запроса, заголовки, полезная нагрузка, ответ и т. д.), но также дает ему возможность кэшировать динамические данные. Лучшее в этом то, что если все сделано аккуратно, вам не нужно вносить какие-либо изменения в существующее приложение, несмотря на добавление вызова register() для его улучшения с помощью сервис-воркеров.

Жизненный цикл сервис-воркера

Теперь, когда мы знаем, на что способны сервис-воркеры, давайте подробнее рассмотрим их жизненный цикл.

Всякий раз, когда пользователь посещает наше PWA, его браузер анализирует нашу index.html страницу. Где-то на этой странице должен быть тег <script>, который включает код для регистрации работника службы.

<script src="./src/js/registerserviceworker.js"></script>

Внутри registerserviceworker.js сервис-воркер регистрируется, вызывая

navigator.serviceWorker
    .register($pathToServiceWorkerFile);

Только HTTPS

Во время разработки можно установить сервис-воркер из localhost. Всякий раз, когда мы готовы опубликовать наше PWA, нам требуется правильная настройка HTTPS.

Как упоминалось ранее, сервис-воркеры действительно сильны в отношении манипулирования запросами. Вы бы не хотели устанавливать такие вещи из небезопасных мест.

Этапы жизненного цикла

После вызова метода register() сервис-воркер проходит следующие три стадии:

  1. Установить
  2. Ожидающий
  3. Активировать

Давайте подробнее рассмотрим каждый из этих этапов!

Этап 1: Установка Service Worker

Всякий раз, когда мы пытаемся зарегистрировать нового работника службы или вносим изменения в уже зарегистрированный, запускается событие install. Это событие является одним из событий жизненного цикла сервис-воркера, к которому мы можем подключиться, и оно идеально подходит для выполнения, например. предварительное кэширование для нашего приложения. event.waitUntil() дает нам возможность вручную продлить этап installдо тех пор, пока мы не закончим первоначальную настройку.

Мы обсудим предварительное кэширование и кэширование в целом в моем следующем посте.

Этап 2: Ожидание активации

Немедленное обновление сервис-воркера может быть плохой идеей. В случае, если мы обновили его поведение, например. вернуть другой ответ, чем раньше, мы хотели бы «чистую резку» до того, как новая версия будет активирована.

Для достижения этой чистоты каждый клиент, который в настоящее время находится под контролем нашего сервисного работника, должен быть перезагружен*. Как только это будет сделано, наш сервисный работник перейдет к следующему этапу.

  • Если мы действительно этого хотим, мы можем переопределить это поведение, вызвав self.skipWaiting() внутри сервис-воркера, чтобы немедленно перейти к следующему этапу.

Этап 3: Активация Service Worker

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

Как и в случае с событием install, мы можем вручную продлить этот этап, вызвав event.waitUntil(). Таким образом, мы можем выполнять задачи очистки, чтобы удалить устаревшие данные из других рабочих процессов.

Типичная задача на этом этапе — очистить устаревшие кэши. Еще раз, мы более подробно рассмотрим это в моем следующем посте.

Регистрация сервисного работника

В следующем фрагменте показано содержимое одного из моих registerserviceworker.js файлов:

import {
    capabilities
} from "./capabilities";
console.log('Trying to register service worker.');
if (capabilities.sw) {
    navigator.serviceWorker
        .register('../../sw.js')
        .then(registration => {
          console.log("Registered service worker with scope: " + registration.scope);
        });
} else {
    console.log('Service workers not supported, skipping registration.');
}

Этот довольно короткий фрагмент кода на самом деле содержит немало тем для обсуждения.

navigator.serviceWorker
    .register('../../sw.js');

Эта строка фактически отвечает за регистрацию нашего сервисного работника. В моем примере код сервис-воркера в sw.js находится в корневой папке моих веб-приложений, на два уровня выше регистрационного кода. Хотя может показаться, что в этом нет ничего особенного, на самом деле это приводит нас к важной теме.

Объем сервисного работника

Каждый запрос, который мы отправляем в нашем приложении, имеет origin. А область Service Worker настраивает, какие источники попадают под его контроль. По умолчанию область действия сервисного работника устанавливается в соответствии с его местоположением, поэтому, когда он находится на нашем корневом уровне, он контролирует всю область действия, и мы можем перехватывать каждый запрос. Если установлено, например. ./other/scope мы сможем перехватить только запрос, исходящий от https://toplevel.domain/other/scope. Область SW настраивается путем передачи объекта конфигурации вызову register().

{
  scope: './other/scope'
}

Вообще говоря, мы можем настроить только область действия, которая находится на том же уровне, что и наш файл сервис-воркера, или ниже. Таким образом, невозможно (по крайней мере, без дополнительной работы) настроить область действия / для сервис-воркера, расположенного, например, в /src/js/sw.js.

На тот случай, если мы действительно хотим настроить область над нашим файлом сервис-воркера, есть способ добиться этого. Предполагая, что мы можем настроить наш веб-сервер по своему вкусу, нам нужно будет добавить специальный заголовок к нашему ресурсу сервисного работника. Добавив специальный заголовок Service-Worker-Allowed, мы можем установить верхний путь для области видимости нашего сервис-воркера. Взгляните на спецификацию сервисного работника для получения дополнительной информации. Честно говоря, я просто размещаю свой файл сервис-воркера на корневом уровне, чтобы избежать дополнительной работы по настройке.

Служба поддержки работников службы поддержки

Еще одна деталь, о которой стоит упомянуть, это следующий импорт:

import {
    capabilities
} from "./capabilities";

Я ввел этот модуль для удобства.

export const capabilities = {
    sw: 'serviceWorker' in navigator,
    idb: 'indexedDB' in window,
    sync: 'serviceWorker' in navigator && 'SyncManager' in window
};

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

Есть еще несколько проверок, с которыми мы разберемся позже, сейчас мы просто проверяем

'serviceWorker' in navigator

caniuse.com предоставляет обзор версий браузеров, поддерживающих сервис-воркеров.

Вывод

В этом посте мы узнали о возможностях сервис-воркеров и их жизненном цикле. Небольшой пример кода демонстрирует, как зарегистрировать сервис-воркера и настроить необязательную область действия. Мы говорили о том, как мы можем манипулировать макс. Scope, добавив заголовок Service-Worker-Allowed и как проверить совместимость браузера.

Что дальше?

В следующем посте я подробно расскажу о кэшировании сервис-воркеров.

  • Предварительное кэширование
  • Динамическое кэширование
  • Кэширование динамического контента
  • Утилиты кэширования

К концу моего следующего поста у нас будет все, чтобы наш PWA можно было установить на мобильные устройства!

Увидимся в следующий раз!

Саймон