В этом посте мы обсудим Progressive Web Apps и Service Workers. Как они могут помочь современным пользователям мобильного Интернета и как мы экспериментируем с ними на Booking.com? Мы поделимся некоторыми проблемами, с которыми мы столкнулись, а также некоторыми из наших уроков.

Что такое прогрессивное веб-приложение?

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

До этой инициативы некоторые из обсуждаемых функций уже были доступны для пользователей мобильного Интернета (хотя и в ограниченной степени):

  • Добавить на главную 1 экран 2 (требует ручных действий)
  • Полноэкранный режим 3
  • Кэш приложения для офлайн-доступа 4
  • API уведомлений 5

Однако веб-страницы по-прежнему не являются первым выбором, когда речь идет о предоставлении наилучшего возможного опыта на мобильном устройстве (несмотря на то, что они более заметны в поисковых системах и потенциально избавляют от неудобств, связанных с загрузкой и установкой мегабайт, что особенно важно для начинающих посетителей и посетителей в соединениях 2G / 3G). Слишком часто мы видим веб-сайты, добавляющие баннеры или межстраничные всплывающие окна 6, умоляющие пользователей загрузить свои приложения, вплоть до того, что полностью отказываются от своей мобильной версии 7 (только для того, чтобы воскреснуть 8 через 5 месяцев). Обосновывающие аргументы, которые повторяются: нативные приложения работают более плавно и имеют лучшие средства для повторного взаимодействия с клиентами, а веб-среде просто не хватает изящных откатов в нестабильных сетевых условиях.

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

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

Большинство из этих функций стало возможным благодаря работникам службы поддержки.

Что такое сервис-воркер?

Сервис-воркеры, по сути, действуют как прокси-серверы, которые находятся между веб-приложениями, а также между браузером и сетью (если они доступны). Они предназначены (среди прочего) для обеспечения эффективного взаимодействия в автономном режиме, перехвата сетевых запросов и принятия соответствующих мер в зависимости от доступности сети и наличия обновленных активов на сервере. Они также позволят получить доступ к push-уведомлениям и API фоновой синхронизации. - MDN

Короче говоря, сервис-воркер - это асинхронный фоновый поток, который берет на себя управление всеми сетевыми запросами на странице.

Краткие факты

  • Сервис-воркеры работают в другом контексте, поэтому не имеют доступа к элементам DOM или переменным JavaScript в основном потоке.
  • По соображениям безопасности клиентская страница (основной поток) должна быть в https, а сценарий сервисного воркера должен быть в том же источнике, но все запросы, исходящие с этой страницы, могут быть перехвачены сервисными воркерами, даже если они не находятся в https или обслуживаются из другой домен
  • В worker предоставляется CacheStorage, так что вы можете хранить ответы сервера (включая заголовки и тело ответа) локально и обслуживать их для будущих запросов.
  • При необходимости ответы сервера могут быть подделаны на стороне клиента.
  • Все асинхронно, и большинство API возвращают обещание.

Поддержка браузера

На данный момент только Chrome, Firefox и Opera имеют адекватную поддержку для обслуживающего персонала. Для мобильных устройств это означает, что поддерживается только Android. Поскольку такие функции, как значки на главном экране и push-уведомления, интегрированы в ОС, вся инициатива Progressive Web App действительно зависит от того, насколько энтузиазмом поставщики ОС относятся к ней.

Что касается обслуживающего персонала, отношение Apple таково:

Люди думают, что хотят этого, некоторые действительно хотят. Наверное, нам стоит это сделать. 9

(в таком случае, похоже, нам не придется долго ждать, пока сервис-воркеры появятся в iPhone.)

Подробную таблицу совместимости всех функций сервис-воркеров можно найти в этом документе: Готов ли ServiceWorker?

Что могут делать сервисные работники?

ServiceWorker API предоставляет разработчикам очень подробные методы для перехвата запросов, кэширования и подделки ответов, открывая двери для всех видов интересных действий, таких как:

  • Автономный доступ к определенным страницам (подтверждение заказа, электронный билет и т. Д.)
  • Предварительное кэширование ресурсов на основе прогнозов следующих действий пользователя (прогнозы не зависят от сервисных воркеров как таковых, но управляемый кеш может быть более программируемым с сервисными воркерами. Вы даже можете ввести время истечения срока действия или алгоритм LRU, если хотите)
  • Обслуживание кэшированной версии, когда загрузка некоторых ресурсов занимает слишком много времени
  • Перезапись URL-адресов, чтобы всегда запрашивать канонический URL-адрес 10

Ознакомьтесь с Offline Cookbook для получения более подробной информации о стратегиях кэширования.

Кроме того, сервис-воркеры также используются для организации фоновой связи с серверами (воспринимайте это как службу). Такие функции, как push-уведомления, фоновая синхронизация, планировщик задач, в некоторой степени зависят от сервис-воркеров.

Сервис-воркеры в действии

Теперь давайте запачкаем руки и разберемся с сервис-воркером в действии.

Регистрация

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

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('service-worker.js', { scope: './' }).then(function() {
    if (navigator.serviceWorker.controller) {
        console.log('The service worker is currently handling network operations.');
    } else {
        console.log('Failed to register.');
    }
  });
}

Этот фрагмент регистрирует сервис-воркер с файлом service-worker.js. После регистрации код в этом файле сможет управлять всеми запросами, исходящими с любой страницы в параметре scope.

По умолчанию scope - это базовое расположение сценария сервис-воркера. Например, если вы зарегистрировали «/static/js/serviceworker.js», тогда область действия по умолчанию будет «/ static / js /». Сам скрипт должен находиться в том же источнике, что и клиентская страница, поэтому невозможно обслуживать скрипты сервис-воркера с CDN в разных доменах. Но можно переопределить область видимости вне базового местоположения скрипта:

navigator.serviceWorker.register('/scripts/service-worker.js', { scope: '/' })

Этот код позволяет работнику службы управлять всеми страницами в корневом пути источника ({ scope: '/' }). Но вам нужно добавить дополнительный заголовок ответа Service-Worker-Allowed, чтобы он заработал.

Например, в конфигурации nginx это можно сделать так:

Server {

    listen www.example.com:443 ssl;

    ...

    location /scripts/service-worker.js {
        add_header 'Service-Worker-Allowed' '/';
    }
}

(Обратите внимание, что этот заголовок добавляется для самого сценария сервис-воркера, а не для страницы, на которой он был зарегистрирован.)

Внутри рабочего

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

Скрипт запускается в контексте с именем ServiceWorkerGlobalScope 11. В этом контексте доступны несколько глобальных переменных и методов:

  • клиенты - информация о клиентских страницах, используемая для утверждения контроля над ними.
  • регистрация - отображает состояние регистрации.
  • cache - объект CacheStorage, в котором вы можете хранить ответы сервера.
  • skipWaiting () - разрешение процесса регистрации от ожидания до активного состояния.
  • fetch (..) - часть GlobalFetch API, также доступная в основном потоке.
  • importScripts (..) - синхронный импорт сценариев JS, идеально подходит для загрузки библиотеки сервис-воркеров.

Команда Google Chrome предоставила красивую высокоуровневую библиотеку 12, которая поможет вам справиться с задачами сервис-воркера. Он поставляется с маршрутизатором для выразительного применения общих шаблонов кеширования к различным ресурсам, а также с набором инструментов для предварительного кэширования и управления кешем с пространством имен. Настоятельно рекомендуется использовать эту библиотеку, если вы хотите создать что-то готовое к производству; это экономит вам много работы, а также является хорошим началом для знакомства с основными концепциями ServiceWorker. Посмотрите рецепты на примере использования.

Если вам действительно нужны подробности, обратитесь к документу MDN Service Worker API и обратите особое внимание на CacheStorage 13 и FetchEvent 14.

Сервисные работники на Booking.com

В Booking.com мы всегда открыты для новых технологий и поощряем любые инновации, которые повышают удовлетворенность клиентов. В настоящее время мы тесно сотрудничаем с командой сторонников PWA из Google над применением некоторых основных функций Progressive Web Apps на нашем мобильном веб-сайте, чтобы увидеть, в чем они могут помочь нашим клиентам.

Установить сервис-воркеров для пользователей относительно просто - вам просто нужно, чтобы они использовали поддерживаемый браузер (в настоящее время это означает использование Chrome в Android). Однако настоящая проблема заключается в том, как внедрить значимые функции при тщательном измерении воздействия. В Booking.com мы проводим каждый проект, ориентированный на клиентов, с помощью экспериментов с A / B-тестированием и стараемся достичь желаемых результатов «минимальными жизнеспособными шагами». Цель состоит в том, чтобы доставить правильные вещи как можно быстро. Даже для чего-то столь же целостного, как прогрессивное веб-приложение, мы работаем небольшими шагами, чтобы решать проблемы одну за другой и быстро учиться.

Мы собрали некоторые важные выводы по этой теме. Ниже приведены некоторые из обнаруженных нами уроков, которые могут быть интересны широкой публике.

Примеры стратегии кеширования

Offline Cookbook 15 резюмировал несколько стратегий кэширования для различных случаев использования.

  • cacheFirst - обслуживать кеш, если он существует, запросы по-прежнему будут запускаться, а новые ответы будут обновлять кеш.
  • cacheOnly - отвечать только с кешем, никогда не запускать фактический запрос.
  • networkFirst - всегда пытайтесь сначала получить из сети и сохранять последний успешный ответ в кеш, который будет обслуживаться при сбое сети.
  • networkOnly - никогда не использует локальный кеш.

Давайте посмотрим на несколько примеров того, как применить каждый из них в реальной жизни.

Для статических файлов, которые никогда не меняются, мы можем безопасно обслуживать их с помощью «cacheFirst»:

toolbox.router.get(/static\/(css|js|images|img)\//,
    toolbox.cacheFirst, {
       cache: { name: 'static-files' }
    }
);

Они редко меняются, и даже если они меняются, мы бы обновили URL-адреса. Кто-то может спросить: Какая польза от этого метода, если мы уже установили дату истечения срока действия в заголовках? Service worker дает вам более детальный контроль над тем, сколько кеша вы хотите сохранить и когда истечет срок их действия. Например, sw-toolbox предоставляет очень простые конфигурации для maxEntries 16 и maxAgeSeconds 17.

Для обычных HTML-документов мы можем использовать networkFirst:

toolbox.router.get(/\/(confirmation|mybooking|myreservations)/i, 
    toolbox.networkFirst, {
        networkTimeoutSeconds: 10,
        cache: { name: 'booking-confirm' }
    }
);

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

Для запросов, используемых для сбора данных о поведении пользователей, вы можете использовать networkOnly:

toolbox.router.any(/www.google-analytics.com/, toolbox.networkOnly);

Нет смысла возвращать кеш для запроса отслеживания, верно? Если запрос не выполняется, он не выполняется. При желании вы даже можете отслеживать статус запроса на отслеживание и повторно отправлять его в случае сбоя. Это будет невозможно, если (каким-то образом) сработает кеш в сервис-воркерах.

Местные ярлыки

Разве не было бы неплохо, если бы пользователи могли сохранять постоянную ссылку в закладках, которая всегда будет перенаправлять их к последнему подтверждению бронирования, которое они видели?

Давайте добавим собственный обработчик для страницы подтверждения:

toolbox.router.get("/confirmations/(.*)", function(request, values, options) {
    var url = request.url;
    var promise = toolbox.networkFirst(request, values, options);
    var confirmationId = values[0]; 
    if (confirmationId) {
        // when the request finishes
        promise.then(function(response) {
            if (!response || response.status !== 200) return;
            self.caches.open('last-confirmation').then(function(cache) {
                // save a 302 Redirect response to "/confirmation"
                var redirectResponse = new Response('Redirecting', {
                    status: 302,
                    statusText: 'Found',
                    headers: {
                        Location: url
                    }
                });
                cache.put('/confirmation', redirectResponse);
            });
        });
    }
    return promise;
}, {
    networkTimeoutSeconds: 10,
    cache: {
        name: 'confirmations',
    }
});

toolbox.router.get('/confirmation', toolbox.cacheOnly, {
    cache: {
        name: 'last-confirmation'
    }
});

Каждый раз, когда пользователи посещают страницу подтверждения, мы возвращаем ответ, как обычно, со стратегией «networkFirst». Но в дополнение к этому мы подделываем ответ перенаправления 302 локально, указывая на текущий URL-адрес, а затем сохраняем поддельный ответ в кеш-хранилище с именем last-confirmation с ключом URL /confirmation.

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

Поддельный ответ был помещен в отдельное пространство имен кеш-хранилища и обслуживается по стратегии cacheOnly. Потому что, видимо, URL действителен только локально. Мы, конечно, не хотим смешивать это с обычными запросами.

Проблема защищенного домена

Чтобы защитить данные пользователей, все части нашего процесса бронирования и страниц управления учетными записями пользователей обслуживаются через HTTPS в отдельном домене - secure.booking.com вместо www.booking.com, который используется для общедоступного контента. например, на странице результатов поиска и сведений об отеле.

Однако вы не можете зарегистрировать одного сервис-воркера в двух разных доменах, даже если они являются субдоменами одного корневого домена. И (по крайней мере, на данный момент) нет возможности позволить двум работникам службы общаться друг с другом.

Что, если вы хотите предварительно кэшировать ресурсы для secure.booking.com, когда пользователи все еще находятся в www.booking.com, или наоборот? У нас есть много людей, которые прыгают между двумя доменами, особенно когда они бронируют билеты. Кроме того, из-за того, что важные функции распределены по разным доменам, сервисный работник для одного домена просто не может предложить непрерывный автономный режим.

По этой причине мы объединяем все основные функции в одном домене, и это предоставит пользователям полный доступ по протоколу HTTPS ко всему их путешествию по Booking.com. Тем временем эксперты группы Service Worker Specs работают над новым API под названием external fetch ​​15, который даст сервисным работникам полномочия перехватывать любые запросы ресурсов в пределах их области действия (как определено при их регистрации). Эти запросы могут исходить с любой страницы, даже если страница находится в другом домене.

Последние мысли

ServiceWorker API нацелен на давнюю проблему мобильного Интернета - возможность подключения. Он может сделать работу пользователя более комфортной даже при плохом подключении. Он дает современным веб-приложениям возможность более интимно привлекать пользователей и, безусловно, повышает конкурентоспособность веб-приложений по сравнению с собственными.

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

Ресурсы

  1. Прогрессивные веб-приложения
  2. Спецификация сервисного работника
  3. Документация по ServiceWorker API на MDN
  4. Отладка Service Worker
  5. Рецепты 1
  6. Рецепты 2
  7. Демо от W3C web mobile group

Хотели бы вы стать разработчиком на Booking.com? "Работать с нами"!