Создание PWA в Google, часть 1

Что команда Bulletin узнала о сервис-воркерах при разработке PWA.

Дуглас Паркер
Douglas Parker
Дикла Коэн
Dikla Cohen

Это первая из серии публикаций в блоге об уроках, которые команда Google Bulletin извлекла при создании внешнего PWA. В этих статьях мы поделимся некоторыми проблемами, с которыми мы столкнулись, подходами, которые мы использовали для их преодоления, а также общими советами, как избежать ловушек. Это ни в коем случае не полный обзор PWA. Цель состоит в том, чтобы поделиться знаниями из опыта нашей команды.

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

Фон

Бюллетень находился в активной разработке с середины 2017 по середину 2019 года.

Почему мы решили создать PWA

Прежде чем мы углубимся в процесс разработки, давайте выясним, почему создание PWA оказалось привлекательным вариантом для этого проекта:

  • Возможность быстрого повторения . Особенно ценно, поскольку бюллетень будет опробован на нескольких рынках.
  • Единая кодовая база . Наши пользователи были примерно поровну разделены между Android и iOS. PWA означало, что мы могли создать одно веб-приложение, которое работало бы на обеих платформах. Это увеличило скорость и влияние команды.
  • Обновляется быстро и независимо от поведения пользователя . PWA могут автоматически обновляться, что уменьшает количество устаревших клиентов. Нам удалось внести критические изменения в серверную часть за очень короткое время миграции для клиентов.
  • Легко интегрируется с собственными и сторонними приложениями. Такая интеграция была обязательным требованием для приложения. В случае с PWA это часто означало простое открытие URL-адреса.
  • Устранены трудности с установкой приложения.

Наша структура

Для Bulletin мы использовали Polymer , но подойдет любой современный, хорошо поддерживаемый фреймворк.

Что мы узнали о работниках сферы услуг

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

Сгенерируйте его, если можете

Избегайте написания сценария сервисного работника вручную. Написание сервис-воркеров вручную требует ручного управления кэшированными ресурсами и переписывания логики, которая является общей для большинства библиотек сервис-воркеров, таких как Workbox .

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

Не все библиотеки совместимы с сервис-воркерами.

Некоторые библиотеки JS делают предположения, которые не работают должным образом при запуске сервис-воркера. Например, предполагая, что window или document доступны, или используя API, недоступный для сервисных работников ( XMLHttpRequest , локальное хранилище и т. д.). Убедитесь, что все важные библиотеки, необходимые для вашего приложения, совместимы с сервис-воркерами. Для этого конкретного PWA мы хотели использовать для аутентификации gaspi.js , но не смогли этого сделать, поскольку он не поддерживал сервис-воркеров. Авторы библиотек также должны уменьшить или удалить ненужные предположения о контексте JavaScript, где это возможно, для поддержки сценариев использования сервис-воркеров, например, избегая API, несовместимых с сервис-воркерами, и избегая глобального состояния .

Избегайте доступа к IndexedDB во время инициализации.

Не читайте IndexedDB при инициализации сценария сервисного работника, иначе вы можете попасть в нежелательную ситуацию:

  1. У пользователя есть веб-приложение с IndexedDB (IDB) версии N.
  2. Новое веб-приложение публикуется с версией IDB N+1.
  3. Пользователь посещает PWA, что запускает загрузку нового сервис-воркера.
  4. Новый сервисный работник читает из IDB перед регистрацией обработчика событий install , запуская цикл обновления IDB для перехода от N к N+1.
  5. Поскольку у пользователя есть старый клиент версии N, процесс обновления Service Worker зависает, поскольку активные подключения к старой версии базы данных все еще открыты.
  6. Сервисный работник зависает и никогда не устанавливает

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

Сделайте его устойчивым

Хотя сценарии сервисных работников выполняются в фоновом режиме, их также можно прервать в любой момент, даже когда они находятся в процессе операций ввода-вывода (сеть, IDB и т. д.). Любой длительный процесс должен быть возобновлен в любой момент.

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

Не зависеть от глобального состояния

Поскольку сервис-воркеры существуют в другом контексте, многие символы, которые вы могли бы ожидать, отсутствуют. Большая часть нашего кода выполнялась как в контексте window , так и в контексте сервисного работника (например, ведение журнала, флаги, синхронизация и т. д.). Код должен защищать используемые им службы, такие как локальное хранилище или файлы cookie. Вы можете использовать globalThis для ссылки на глобальный объект таким образом, чтобы он работал во всех контекстах. Также используйте данные, хранящиеся в глобальных переменных, с осторожностью, поскольку нет никакой гарантии относительно того, когда сценарий будет завершен и состояние будет удалено.

Местное развитие

Основным компонентом сервисных работников является локальное кэширование ресурсов. Однако во время разработки это полная противоположность тому, чего вы хотите, особенно когда обновления выполняются лениво. Вы по-прежнему хотите, чтобы рабочий сервер был установлен, чтобы вы могли отлаживать проблемы с ним или работать с другими API, такими как фоновая синхронизация или уведомления. В Chrome этого можно добиться с помощью Chrome DevTools, установив флажок «Обход сети» (панель «Приложение» > панель «Служебные работники» ), а также установив флажок «Отключить кеш» на панели «Сеть» , чтобы также отключить кеш памяти. Чтобы охватить больше браузеров, мы выбрали другое решение, включив флаг для отключения кеширования в нашем сервис-воркере, который включен по умолчанию в сборках для разработчиков. Это гарантирует, что разработчики всегда получат самые последние изменения без каких-либо проблем с кэшированием. Важно также включить заголовок Cache-Control: no-cache , чтобы браузер не кэшировал какие-либо ресурсы .

Маяк

Lighthouse предоставляет ряд инструментов отладки, полезных для PWA. Он сканирует сайт и генерирует отчеты, охватывающие PWA, производительность, доступность, SEO и другие лучшие практики. Мы рекомендуем использовать Lighthouse в режиме непрерывной интеграции, чтобы предупредить вас, если вы нарушите один из критериев статуса PWA. Это действительно случилось с нами однажды, когда работник службы не устанавливал, и мы не осознавали этого до запуска производства. Наличие Lighthouse в составе нашего CI предотвратило бы это.

Обеспечьте непрерывную доставку

Поскольку работники службы могут автоматически обновляться, у пользователей нет возможности ограничивать обновления. Это существенно снижает количество устаревших клиентов в дикой природе. Когда пользователь открывал наше приложение, сервисный работник обслуживал старый клиент, пока лениво загружал новый клиент. После загрузки нового клиента пользователю будет предложено обновить страницу для доступа к новым функциям. Даже если пользователь проигнорирует этот запрос, при следующем обновлении страницы он получит новую версию клиента. В результате пользователю довольно сложно отказаться от обновлений так же, как он это делает для приложений iOS/Android.

Нам удалось внести критические изменения в серверную часть за очень короткое время миграции для клиентов. Обычно мы даем пользователям месяц на обновление до новых клиентов, прежде чем вносить критические изменения. Поскольку приложение будет работать, пока оно устарело, на самом деле старые клиенты могли существовать в дикой природе, если пользователь не открывал приложение в течение длительного времени. В iOS сервисные работники выселяются через пару недель, поэтому такого случая не происходит. Для Android эту проблему можно решить, если не показывать контент, пока он устарел, или вручную удалить срок действия контента через несколько недель. На практике мы ни разу не столкнулись с проблемами со стороны устаревших клиентов. Насколько строгой хочет быть конкретная команда, зависит от ее конкретного варианта использования, но PWA обеспечивают значительно большую гибкость, чем приложения для iOS/Android.

Получение значений cookie в сервисном работнике

Иногда необходимо получить доступ к значениям файлов cookie в контексте сервисного работника. В нашем случае нам нужно было получить доступ к значениям файлов cookie, чтобы сгенерировать токен для аутентификации запросов API первой стороны. В сервис-воркере недоступны синхронные API, такие как document.cookies . Вы всегда можете отправить сообщение активным (оконным) клиентам от сервис-воркера, чтобы запросить значения файлов cookie, хотя сервис-воркер может работать в фоновом режиме без каких-либо доступных оконных клиентов, например, во время фоновой синхронизации. Чтобы обойти эту проблему, мы создали конечную точку на нашем внешнем сервере, которая просто отправляла значение cookie обратно клиенту. Работник службы отправил сетевой запрос к этой конечной точке и прочитал ответ, чтобы получить значения файлов cookie.

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

Подводные камни для негенерируемых сервисных работников

Убедитесь, что сценарий сервисного работника изменяется при изменении какого-либо статического кэшированного файла.

Распространенный шаблон PWA заключается в том, что работник службы устанавливает все статические файлы приложения на этапе install , что позволяет клиентам напрямую обращаться к кешу Cache Storage API для всех последующих посещений. Сервис-воркеры устанавливаются только тогда, когда браузер обнаруживает, что сценарий сервис-воркера каким-то образом изменился, поэтому нам нужно было убедиться, что сам файл сценария сервис-воркера каким-то образом изменился при изменении кэшированного файла. Мы сделали это вручную, встроив хэш набора статических файлов ресурсов в наш сценарий сервисного работника, поэтому каждый выпуск создавал отдельный файл JavaScript сервисного работника. Библиотеки сервисных работников, такие как Workbox, автоматизируют этот процесс за вас.

Модульное тестирование

API сервисного работника функционируют путем добавления прослушивателей событий к глобальному объекту. Например:

self.addEventListener('fetch', (evt) => evt.respondWith(fetch('/foo')));

Это может быть сложно тестировать, потому что вам нужно имитировать триггер события, объект события, дождаться обратного вызова respondWith() , а затем дождаться обещания, прежде чем окончательно утвердить результат. Более простой способ структурировать это — делегировать всю реализацию другому файлу, который легче тестировать.

import fetchHandler from './fetch_handler.js';
self.addEventListener('fetch', (evt) => evt.respondWith(fetchHandler(evt)));

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

Следите за частями 2 и 3

Во второй и третьей частях этой серии мы поговорим об управлении мультимедиа и проблемах, специфичных для iOS. Если вы хотите узнать больше о создании PWA в Google, посетите наши профили авторов и узнайте, как с нами связаться: