- Введение
- Начнем установку
- Архитектура оболочки приложения (App Shell)
- Реализация оболочки приложения (App Shell)
- Старт с быстрого запуска
- Использование service workers для предварительного кэширования App Shell
- Используем service workers для кэширования прогнозных данных
- Поддержка нативной интеграции
- Развертываем на безопасном хосте и пляшем
1. Введение
Прогрессивные веб-приложения — это опыт, который объединяет лучшее от веб-сайтов и лучшее от приложений. Они полезны пользователям с самого первого открытия вкладки браузера, без необходимости установки. Поскольку пользователь постепенно наращивает взаимодействие с приложением, оно становится все более и более мощным. Приложение быстро загружается, даже в медленных сетях, отправляет релевантные push-уведомления, имеет иконку на домашнем экране и загружается в полноэкранном режиме.
Что такое прогрессивное веб-приложение?
Прогрессивное веб-приложение это:
- Прогрессивность — работает для каждого пользователя, независимо от выбора браузера, потому что в качестве основного принципа заложено прогрессивное улучшение.
- Отзывчивость — подходит для любого форм-фактора: десктоп, мобильный телефон, планшет или что-либо еще.
- Независимость от подключения — с помощью service workers может работать в автономном режиме или в медленных сетях.
- Подобно приложению — выглядит как приложение, потому что модель оболочки приложения отделяет функциональность приложения от содержимого приложения.
- Свежесть — всегда обновляется благодаря процессу обновления service worker.
- Безопасность — обслуживается через HTTPS для предотвращения перехвата или подмены данных.
- Обнаруживаемый — идентифицируется как «приложение», однако благодаря манифесту W3C и регистрации service worker-а позволяет поисковым системам находить его.
- Возможность повторного подключения — упрощает повторное взаимодействие с помощью таких функций, как push-уведомления.
- Устанавливаемость — позволяет пользователям добавлять приложения на свой домашний экран, которые они находят наиболее полезными, без использования магазина приложений.
- Linkable — легко поделиться приложением по URL-адресу, не требует полноценной установки.
Этот кодлаб проведет вас через создание собственного прогрессивного веб-приложения (Progressive Web App), включая дизайн, а также детали реализации, чтобы ваше приложение соответствовало всем вышеуказанным ключевым принципам PWA.
Что мы будем делать?
В этом кодлабе мы собираемся сделать погодное веб-приложение — Weather, с использованием технологий Progressive Web App. Ваше приложение будет:
|
Что вы узнаете?
- Как спроектировать и создать приложение с помощью метода «app shell»
- Как заставить приложение работать в оффлайн режиме
- Как хранить данные для последующего использования в автономном режиме
Что вам потребуется?
- Последняя версия Chrome. Обратите внимание, что приложение работает и в других браузерах, но мы будем использовать некоторые функции Chrome DevTools, чтобы лучше понять, что происходит на уровне браузера.
- Web Server for Chrome, или ваш собственный веб-сервер на выбор.
- Пример кода.
- Текстовый редактор.
- Базовые знания HTML, CSS, JavaScript и Chrome DevTools.
Этот кодлаб ориентирован на Progressive Web Apps (PWA). Некоторые понятия и блоки кода не объясняются и предоставляются для простого копирования и вставки в проект.
2. Начнем установку
Скачиваем код
Нажмите следующую ссылку, чтобы загрузить весь код для этого кодлаба:
Распакуйте скачанный zip-файл. Файл распакуется в корневую папку your-first-pwapp-master
, которая содержит папку для каждого шага этого кодлаба, а также все необходимые вам ресурсы.
Папки step-NN
содержат конечный результат каждого шага этого кодлаба. Они там для справки. Мы будем кодить в каталоге work
.
Установка и проверка веб сервера
Вы можете свободно использовать свой собственный веб-сервер, но этот кодлаб рассматривает работу с веб сервером Chrome Web Server. Если у вас еще не установилено это расширение, вы можете установить его через интернет-магазин Chrome Web Store.
После установки расширения Web Server for Chrome, нажмите кнопку Apps в панели закладок:
Web Server
:Нажмите кнопку choose folder
и выберите рабочую папку. Это позволит вам отслеживать изменения по URL, приведенному в диалоговом окне веб-сервера (в разделе Web Server URL(s)
).
В разделе Options
установите флажок Automatically show index.html
, как показано ниже:
Затем остановите и перезапустите сервер, сдвинув переключатель Web Server: STARTED
влево, а затем обратно вправо.
Теперь посетите свой рабочий сайт в своем веб-браузере (нажав на выделенный Web Server URL
), и вы увидите страницу, которая выглядит примерно так:
Это приложение еще не делает ничего интересного — до этого момента мы делали всего лишь минимальный скелет с индикатором, который мы будем использовать, чтобы проверять функциональность нашего веб-сервера. Мы добавим функциональность и UI функции в последующих этапах.
3. Архитектура оболочки приложения (App Shell)
Что такое app shell?
App shell (оболочка приложения) — это минимальный код HTML, CSS и JavaScript, который необходим для запуска пользовательского интерфейса прогрессивного веб приложения и также является одним из компонентов, который обеспечивает производительность. Его первая загрузка должна быть чрезвычайно быстрой и немедленно кэшироваться. «Кэшироваться» означает то, что файлы оболочки загружаются один раз по сети и затем сохраняются на локальном устройстве. В каждый последующий момент, когда пользователь открывает приложение, файлы оболочки загружаются из кэша локального устройства, что приводит к молниеносному запуску.
Архитектура app shell отделяет ядро инфраструктуры приложения и UI от данных. Весь пользовательский интерфейс и инфраструктура кэшируются локально с помощью service worker, так что при последующих загрузках прогрессивного веб приложения требуется только получение необходимых данных, а не загрузка всего приложения целиком.
Service worker — это скрипт, который работает в фоновом режиме браузера, отдельно от веб-страницы, открывая дверь к возможностям, которые не нуждаются в веб-странице или взаимодействии с пользователем.
Иными словами, оболочка приложения похожа на тот код, который вы публикуете в магазине приложений (app store) при создании нативного приложения. Это ядро компонентов, необходимых для того, чтобы ваше приложение могло запуститься, но, не содержащее данных.
Зачем использовать архитектуру App Shell?
Использование оболочки приложения (app shell) позволяет сфокусироваться на скорости, придавая вашим прогрессивным веб-приложениям свойства аналогичные нативным приложениям: мгновенная загрузка и регулярные обновления, без использования магазина приложений.
Создаем App Shell
Первый шаг — разбить конструкцию на его основные компоненты.
Спросите себя:
- Что должно выводиться на экран немедленно?
- Какие другие UI компоненты являются ключевыми для нашего приложения?
- Какие вспомогательные ресурсы необходимы для оболочки приложения? Например, изображения, JavaScript, стили и т. д.
Мы собираемся создать приложение Weather в качестве нашего первого прогрессивного веб-приложения. Ключевыми компонентами будут:
|
При разработке более сложного приложения, контент, который не требуется для начальной загрузки, может запрашиваться позже, а затем кэшироваться для будущего использования. Например, мы могли бы отложить загрузку диалогового окна New City (Новый город) до первого взаимодействия с этим диалоговым окном.
4. Реализация оболочки приложения (App Shell)
Начать работу с любым проектом можно несколькими способами, и мы обычно рекомендуем использовать Web Starter Kit. Но в этом случае, чтобы максимально упростить наш проект и сосредоточиться на прогрессивном веб-приложении, мы предоставили вам все необходимые ресурсы.
Создание HTML для App Shell
Сейчас мы добавим основные компоненты, которые обсуждались в Архитектуре оболочки приложения (App Shell).
Вспомним, что ключевые компоненты будут состоять из:
- Хедер с заголовком и кнопками добавления/обновления
- Контейнер для прогнозов
- Шаблон карточки прогноза
- Диалоговое окно для добавления новых городов
- Индикатор загрузки
Файл index.html
, который уже находится в вашем рабочем каталоге, должен выглядеть примерно так (это часть всего контента, не копируйте этот код в свой файл):
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 32 33 34 35 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Weather PWA</title> <link rel="stylesheet" type="text/css" href="styles/inline.css"> </head> <body> <header class="header"> <h1 class="header__title">Weather PWA</h1> <button id="butRefresh" class="headerButton"></button> <button id="butAdd" class="headerButton"></button> </header> <main class="main"> <div class="card cardTemplate weather-forecast" hidden> . . . </div> </main> <div class="dialog-container"> . . . </div> <div class="loader"> <svg viewBox="0 0 32 32" width="32" height="32"> <circle id="spinner" cx="16" cy="16" r="14" fill="none"></circle> </svg> </div> <!-- Insert link to app.js here --> </body> </html> |
Обратите внимание, что лоадер виден по умолчанию. Это гарантирует, что пользователи сразу же увидят лоадер при загрузке страницы, что дает им четкое понимание, что контент загружается.
Чтобы сэкономить время, мы также создали для вас таблицу стилей. Потратьте пару минут на ее изучение и настройте по своему вкусу.
Добавляем ключевой начальный код JavaScript
Теперь, когда у нас есть большая часть пользовательского интерфейса, пришло время начать подключать код, чтобы все заработало. Как и с остальной частью оболочки приложения, определите, какой код является ключевым для работы, а что можно загрузить позже.
В вашей рабочей директории уже есть код приложения scripts/app.js
, в нем вы найдете:
- Объект
app
, который содержит некоторую ключевую информацию, необходимую для приложения. - Слушатели событий для всех кнопок в заголовке (
add/refresh
) и в диалоговом окне добавления города (add/cancel
). - Метод добавления или обновления карточек прогноза (
app.updateForecastCard
). - Метод получения последних данных прогноза погоды из Firebase Public Weather API (
app.getForecast
). - Метод итерации текущих карточек и вызов
app.getForecast
для получения последних данных прогноза (app.updateForecasts
). - Некоторые фейковые данные (
initialWeatherForecast
), которые используем для быстрого тестирования рендеринга элементов.
Протестируем следующее
Теперь, когда у вас есть основа в виде HTML, стилей и JavaScript, пришло время протестировать приложение.
Чтобы увидеть, как рендерятся фейковые данные о погоде, раскомментируйте следующую строку внизу вашего index.html
файла:
1 |
<!--<script src="scripts/app.js" async></script>--> |
Затем раскомментируйте следующую строку в нижней части файла app.js
:
1 |
// app.updateForecastCard(initialWeatherForecast); |
Перезагрузите приложение. Результатом должна быть хорошо отформатированная (хотя и фейковая, как вы можете узнать по дате) карточка прогноза с отключенным спиннером, например:
После того, как вы попробуете приложение и проверите, что оно работает так, как ожидалось, вы можете удалить вызов app.updateForecastCard
с фейковыми данными. Нам это нужно было только для того, чтобы проверить, все ли работает так, как ожидалось.
5. Старт с быстрого запуска
Прогрессивные веб приложения должны запускаться быстро и быть юзабельными сразу же. В текущем состоянии наше приложение Weather App запускается быстро, но оно безполезно, так как данных нет. Мы могли бы сделать AJAX запрос для получения этих данных, но это приводит к дополнительному запросу и увеличивает первоначальную нагрузку. Вместо этого давайте представим реальные данные при первой загрузке.
Внедрение данных прогноза погоды
Для этого кодлаба мы будем моделировать сервер, вводящий прогноз погоды непосредственно в JavaScript, но в реальном приложении последние данные прогноза погоды будут вводиться сервером на основе геолокации пользователя на основе IP-адреса.
Код уже содержит данные, которые мы будем вставлять. Это initialWeatherForecast
, который мы использовали в предыдущем шаге.
Дифференциируем первый запуск
Но как мы узнаем, что показываем информацию, которая возможно является устаревшей, когда приложение загружается из кеша? Когда пользователь загружает приложение в последующих своих посещениях, он может изменять города, поэтому нам нужно загружать информацию для этих городов, а информацию о первом городе например, который он когда-либо искал, грузить необязательно.
Пользовательские предпочтения, такие как список городов, на которые подписался пользователь, должны храниться локально с использованием IndexedDB или другого механизма быстрой записи. Чтобы максимально упростить этот кодлаб, мы использовали localStorage, который не идеален для продакшена, и на некоторых устройствах может быть очень медленным.
TODO
.
1 |
// TODO add saveSelectedCities function here |
Ниже комментария добавьте следующий код.
1 2 3 4 5 |
// Save list of cities to localStorage. app.saveSelectedCities = function() { var selectedCities = JSON.stringify(app.selectedCities); localStorage.selectedCities = selectedCities; }; |
Затем добавим код, проверяющий, есть ли у пользователя какие-либо сохраненные города и выводящий их, или использющий введенные данные. Найдите следующий комментарий:
1 |
// TODO add startup code here |
Ниже этого комментария добавьте следующий код:
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 |
/************************************************************************ * * Code required to start the app * * NOTE: To simplify this codelab, we've used localStorage. * localStorage is a synchronous API and has serious performance * implications. It should not be used in production applications! * Instead, check out IDB (https://www.npmjs.com/package/idb) or * SimpleDB (https://gist.github.com/inexorabletash/c8069c042b734519680c) ************************************************************************/ app.selectedCities = localStorage.selectedCities; if (app.selectedCities) { app.selectedCities = JSON.parse(app.selectedCities); app.selectedCities.forEach(function(city) { app.getForecast(city.key, city.label); }); } else { /* The user is using the app for the first time, or the user has not * saved any cities, so show the user some fake data. A real app in this * scenario could guess the user's location via IP lookup and then inject * that data into the page. */ app.updateForecastCard(initialWeatherForecast); app.selectedCities = [ {key: initialWeatherForecast.key, label: initialWeatherForecast.label} ]; app.saveSelectedCities(); } |
Код проверяет, есть ли в локальном хранилище какие-либо сохраненные города. Если есть, то он анализирует данные локального хранилища, а затем отображает карточку прогноза для каждого из сохраненных городов. В противном случае код запуска просто использует данные фейкового прогноза и сохраняет это как город по умолчанию.
Сохранение выбранных городов
Наконец, чтобы сохранять выбранный город в локальном хранилище, вам нужно изменить обработчик кнопки add city
.
Обновите обработчик clickAddCity
, чтобы он соответствовал следующему коду:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
document.getElementById('butAddCity').addEventListener('click', function() { // Add the newly selected city var select = document.getElementById('selectCityToAdd'); var selected = select.options[select.selectedIndex]; var key = selected.value; var label = selected.textContent; if (!app.selectedCities) { app.selectedCities = []; } app.getForecast(key, label); app.selectedCities.push({key: key, label: label}); app.saveSelectedCities(); app.toggleAddDialog(false); }); |
Добавлена проверка app.selectedCities
на существование, а также добавлены вызовы app.selectedCities.push()
и app.saveSelectedCities()
.
Протестируем
- При первом запуске ваше приложение должно немедленно показать пользователю прогноз от
initialWeatherForecast
. - Добавьте новый город (щелкнув на иконку + в правом верхнем углу) и убедитесь, что вторая карточка выводится.
- Обновите браузер и убедитесь, что приложение загружает оба прогноза и показывает самую последнюю информацию.
6. Использование service workers для предварительного кэширования App Shell
Прогрессивные веб-приложения должны быть быстрыми и инсталлируемыми, что означает, что они работают в режиме онлайн, оффлайн и на прерывистых, медленных соединениях. Чтобы достичь этого, нам нужно кэшировать нашу оболочку приложения с помощью service worker.
Если вы не знакомы с service worker, вы можете прочитать Введение В Service Workers, где написано о том, что они могут делать, как работает их жизненный цикл и многое другое. После того, как вы закончите этот кодлаб, обязательно загляните в Debugging Service Workers code lab для более глубокого понимания, как работать с service worker.
Функции, предоставляемые service worker, следует рассматривать как прогрессивное улучшение и добавлять, только если они поддерживаются браузером. Например, с service worker вы можете кэшировать оболочку приложения и данные для своего приложения, чтобы все это было доступно при отсутствии сети. Когда service worker не поддерживаются, оффлайн код не вызывается, и пользователь получает базовый функционал. Использование функции обнаружения для обеспечения прогрессивного улучшения не сильно повлияет на производительность и не будет ломаться в старых браузерах, не поддерживающих эту функцию.
Регистрация service worker если он доступен
Первым шагом к оффлайн приложению является регистрация service worker, скрипт, который обеспечивает фоновую работу без надобности открытой веб-страницы или взаимодействия с пользователем.
Это выполняется двумя простыми шагами:
- Сообщите браузеру зарегистрировать JavaScript файл в качестве service worker.
- Создайте JavaScript файл, содержащий service worker.
Во-первых, нам нужно проверить, поддерживает ли браузер работу service worker, и если да, регистрируем service worker. Добавьте следующий код в app.js
(после комментария // TODO add service worker code here
):
1 2 3 4 5 |
if ('serviceWorker' in navigator) { navigator.serviceWorker .register('./service-worker.js') .then(function() { console.log('Service Worker Registered'); }); } |
Кэширование ресурсов сайта
После регистрации service worker, при первом запуске пользователем на странице запускается событие install. В обработчике этого события мы будем кэшировать все данные, необходимые приложению.
Service worker должен открыть объект caches
и заполнить его ресурсами, необходимыми для загрузки оболочки приложения. Создайте файл в корневой папке приложения (в папке your-first-pwapp-master/work
) с именем service-worker.js. Этот файл должен находиться в корне приложения, поскольку область действия для сервис воркеров ограничивается той директорией, в котором располагается сам файл. В файл service-worker.js
добавьте следующий код:
1 2 3 4 5 6 7 8 9 10 11 12 |
var cacheName = 'weatherPWA-step-6-1'; var filesToCache = []; self.addEventListener('install', function(e) { console.log('[ServiceWorker] Install'); e.waitUntil( caches.open(cacheName).then(function(cache) { console.log('[ServiceWorker] Caching app shell'); return cache.addAll(filesToCache); }) ); }); |
Во-первых, нам нужно открыть кеш с помощью caches.open()
и передать имя кеша. Передавая имя кеша мы можем легко обновлять отдельные файлы в кеше, не затрагивая другие.
После открытия кэша, мы вызываем cache.addAll()
, который запрашивает данные с сервера по списку URL-адресов и добавляет ответы в кэш. К сожалению, cache.addAll()
является атомарным, т.е. если фейлится какой-либо из файлов, то с ошибкой завершается весь процесс!
Хорошо, давайте разберемся, как вы можете использовать DevTools для понимания и отладки сервис воркеров. Перед перезагрузкой страницы, откройте DevTools, перейдите в панель Service Worker
на вкладке Application
. Это должно выглядеть так.
Когда вы видите пустую страницу, как на примере, это означает, что на текущей открытой странице нет зарегистрированных сервис воркеров.
Перезагрузите страницу. Теперь панель Service Worker должна выглядеть так.
Когда вы видите такую информацию, это означает, что на странице запущен сервис воркер.
Хорошо, теперь мы собираемся сделать небольшой бриф и продемонстрируем, с чем вы можете столкнуться при разработке сервис воркеров. Чтобы продемонстрировать, добавим в файле service-worker.js
ниже прослушиватель событий install,
и activate
.
1 2 3 |
self.addEventListener('activate', function(e) { console.log('[ServiceWorker] Activate'); }); |
Событие activate
запустится при запуске сервис воркера.
Откройте консоль DevTools и перезагрузите страницу, перейдите в панель Service Worker
на вкладке Application
. Вы ожидаете увидеть в консоли сообщение [ServiceWorker] Activate
, но этого не произошло. Проверьте снова панель Service Worker
, и вы увидите, что новый сервис воркер (включающий в себя прослушиватель событий activate
) находится в состоянии ожидания («waiting»).
По умолчанию, старый сервис воркер контролирует страницу до тех пор, пока открыта вкладка с этой страницей. Таким образом, вы можете закрыть и повторно открыть страницу или нажать кнопку skipWaiting
, но более долгосрочное решение — просто включить флажок Update on Reload
в разделе Service Worker в панели DevTools. Когда этот флажок включен, сервис воркер будет обновляться при каждой перезагрузке страницы.
Установите чекбокс update on reload
и перезагрузите страницу, чтобы активировать новый сервис воркер.
Примечание: В разделе Service Worker панели Application может возникнуть ошибка, аналогичная приведенной ниже, игнорировать эту ошибку вполне безопасно.
На этом все, что касается проверки и отладки сервис воркеров в DevTools. Чуть позже мы покажем вам еще несколько трюков. Вернемся к созданию вашего приложения.
Давайте разберем прослушиватель событий activate
, для включения некоторой логики для обновления кеша. Обновите код как показано ниже.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
self.addEventListener('activate', function(e) { console.log('[ServiceWorker] Activate'); e.waitUntil( caches.keys().then(function(keyList) { return Promise.all(keyList.map(function(key) { if (key !== cacheName) { console.log('[ServiceWorker] Removing old cache', key); return caches.delete(key); } })); }) ); return self.clients.claim(); }); |
Этот код гарантирует, что ваш сервис воркер будет обновлять свой кеш всякий раз, когда изменяется какой-либо из файлов оболочки приложения. Чтобы этот код работал, вам нужно увеличивать значение переменной cacheName
в верхней части вашего сервис воркер файла.
Последнее утверждение фиксирует крайнее значение (corner-case), о котором вы можете прочитать в (необязательном) информационном поле ниже.
self.clients.claim()
фиксирует крайнее значение (corner-case), в котором приложение не возвращает последние данные. Вы можете воспроизвести это самое крайнее значение (corner-case), раскомментировав приведенную ниже строку и затем сделав следующие шаги: 1) загрузите приложение в первый раз, будут инициализированы и показаны данные города Нью-Йорк 2) нажмите в приложении кнопку обновления 3) выйдете в оффлайн режим 4 ) перезагрузите приложение. Вы ожидаете увидеть новые данные Нью-Йорка, но на самом деле увидите инициализированные данные. Это происходит потому, что сервис воркер еще не активирован. Self.clients.claim()
по сути позволяет активировать сервис воркер быстрее.Наконец, давайте обновим список файлов, необходимых для оболочки приложения. Нам нужно включить в массив все файлы, необходимые нашему приложению, включая изображения, JavaScript, CSS и т. д. В верхней части файла service-worker.js
замените var filesToCache = [];
кодом ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var filesToCache = [ '/', '/index.html', '/scripts/app.js', '/styles/inline.css', '/images/clear.png', '/images/cloudy-scattered-showers.png', '/images/cloudy.png', '/images/fog.png', '/images/ic_add_white_24px.svg', '/images/ic_refresh_white_24px.svg', '/images/partly-cloudy.png', '/images/rain.png', '/images/scattered-showers.png', '/images/sleet.png', '/images/snow.png', '/images/thunderstorm.png', '/images/wind.png' ]; |
index.html
, но его также можно запросить как /
, и когда запрашивается корневая папка, сервер отправляет index.html
. Вы можете обработать это в методе fetch
, но так это потребовало бы специального преобразования регистра, что может стать сложным.Наше приложение еще не полностью работает в оффлайн режиме. Мы закэшировали компоненты оболочки приложения, и нам теперь нужно загрузить их из локального кэша.
Запуск оболочки приложения из кэша
Сервис воркеры предоставляют возможность перехватывать запросы, сделанные из нашего прогрессивного веб приложения, и обрабатывать их внутри сервис воркера. Это означает, что мы можем определить, как мы будем обрабатывать запрос и потенциально вручить пользователю наш собственный кэшированный ответ.
Например:
1 2 3 |
self.addEventListener('fetch', function(event) { // Do something interesting with the fetch here }); |
Давайте теперь загрузим оболочку приложения из кэша. Добавьте следующий код в конец файла service-worker.js
:
1 2 3 4 5 6 7 8 |
self.addEventListener('fetch', function(e) { console.log('[ServiceWorker] Fetch', e.request.url); e.respondWith( caches.match(e.request).then(function(response) { return response || fetch(e.request); }) ); }); |
Caches.match()
оценивает веб-запрос, инициировавший событие fetch, и проверяет, доступен ли он в кэше. Затем он либо отвечает кэшированной версией, либо использует fetch
для получения копии из сети. Ответ response
возвращается на веб-страницу с помощью e.respondWith()
.
[ServiceWorker]
в консоли, убедитесь, что вы изменили переменную cacheName
и что вы инспектируете правильный сервис воркер, открыв раздел Service Worker
на панели Applications
и нажав кнопку inspect
на запущенном сервис воркере. Если это не сработает, см. раздел «Советы по тестированию живых сервис воркеров».Тестирование
Теперь ваше приложение оффлайн-совместимо! Давайте проверим.
Перезагрузите страницу и перейдите в раздел Cache Storage
на вкладке Application
панели DevTools. Щелкните правой кнопкой мыши Cache Storage
, выберите Refresh Caches
, разверните раздел и чуть ниже вы увидите имя кэша оболочки приложения. Нажав на кэш оболочки приложения, вы можете увидеть все ресурсы, которые он в настоящее время закэшировал.
Теперь давайте проверим оффлайн режим. Вернитесь на вкладку Service Worker
панели DevTools и установите флажок Offline
. После включения вы увидите желтый предупреждающий значок рядом с вкладкой Network
. Это означает, что вы в оффлайн режиме.
Перезагрузите свою страницу и … она работает! Ну по крайней мере внешне. Обратите внимание, как он загружает исходные (фейковые) данные о погоде.
Ознакомьтесь с условием else
в app.getForecast()
, чтобы понять, когда приложение будет загружать фейковые данные.
Следующим шагом будет изменение логики приложения и сервис-воркера, которая дает возможность кэшировать данные о погоде и, когда приложение в оффлайне, возвращать самые последние данные из кэша.
Совет: Чтобы очистить все сохраненные данные (localStorage
, базу indexedDB
, кэшированные файлы) и удалить все сервис воркеры, используйте Clear storage
на вкладке Application
.
Остерегайтесь edge case-ов (граничных случаев)
Как уже упоминалось ранее, этот код не должен использоваться в продакшене из-за большого количества необработанных edge case-ов.
Обновление кэша зависит от обновления ключа кэша после каждого изменения
Этот метод кэширования требует, чтобы при каждом изменении контента вы обновляли ключ кеша, иначе кеш не будет обновляться, и будет показываться старый контент. Поэтому при каждом изменении контента не забудьте изменять ключ кеша, когда работаете над своим проектом!
После каждого изменения необходимо, чтобы все было загружено снова
Другим недостатком является то, что когда изменяется хоть один файл, весь кэш становится недействительным и его необходимо загружать повторно. Это означает, что исправление простой орфографической ошибки, одного символа приведет к аннулированию кеша и потребует, чтобы все было загружено снова. Это не совсем эффективно.
Кэш браузера может препятствовать сервис воркеру обновлять кэш
Здесь есть еще одно важное предостережение. Крайне важно, чтобы HTTPS запрос, сделанный в течение установки обработчика, сразу попадал в сеть, а не возвращал ответ из кэша браузера. В противном случае браузер может вернуть старую кэшированную версию, в результате чего кэш сервис воркера никогда не обновится!
Остерегайтесь cache-first стратегий в продакшене
Наше приложение использует cache-first стратегию, это значит, что копия любого кэшированного содержимого возвращается без обращения к сети. Хотя стратегию, основанную на кэше, легко реализовать, она может вызвать проблемы в будущем. После того, как закэшируется копия главной страницы и регистрация сервис воркера, может стать чрезвычайно сложно изменить конфигурацию сервис воркера (поскольку конфигурация зависит от того, где она была определена), в итоге вы столкнетесь с тем, что ваши развернутые сайты чрезвычайно сложно обновить!
Как мне избежать этих edge case-ов?
Итак, как мы можем избежать эти edge case-ы? Используйте библиотеку sw-precache, которая обеспечивает прекрасный контроль над подключением к сети, также она гарантирует, что запросы будут идти непосредственно в сеть (при подключении к сети) и выполнит за вас всю тяжелую работу.
Советы по тестированию работающих сервис воркеров
Отладка сервис воркеров может стать испытанием, и может стать еще большим кошмаром при отладке кэша, который не обновляется, когда вы этого от него ожидаете. Одна ошибка — и все пойдет прахом. Но не стоит беспокоиться. Существуют несколько инструментов, используя которые вы упростите себе жизнь.
Очистка сохраненных данных
Иногда вы будете сталкиваться с необходимостью обновить кэшированные данные. Чтобы очистить все сохраненные данные (localStorage, indexedDB данные, кэшированные файлы) и удалить все сервис воркеры, используйте раздел Clear storage
на вкладке Application
.
Некоторые советы:
- После того, как сервис воркер стал незарегистрированым, ссылка на него сохраняется и он может продолжать работать до тех пор, пока не будет закрыто окно браузера где он содержится.
- Если открыть несколько окон для вашего приложения, новый сервис воркер не вступит в силу до тех пор, пока все они не будут перезагружены и обновлены до последнего сервис воркера.
- Отмена регистрации сервис воркера не очищает кэш, поэтому возможно, вы будете получать старые данные, пока имя кэша не изменится.
- Если зарегистрировать новый сервис воркер и при этом уже существует какой-либо сервис воркер, новый сервис воркер не будет получать контроль до тех пор, пока страница не будет перезагружена, если конечно вы не используете immediate control.
7. Используем service workers для кэширования прогнозных данных
Выбор правильной caching strategy для ваших данных жизненно важен и зависит от типа данных, которые представляет ваше приложение. Например, данные, зависящие от времени, такие как погода или котировки акций, должны быть как можно более свежими, в то время как изображения или содержимое статьи могут обновляться не так часто.
Стратегия cache-first-then-network идеально подходит для нашего приложения. Приложение выводит данные на экран как можно быстрее, а затем обновляет, как только из сети загрузятся последние данные. В сравнении с network-first-then-cache, пользователю не нужно ждать, пока по таймауту закончится fetch и не будут получены кэшированные данные.
Cache-first-then-network означает, что нам нужно запустить два асинхронных запроса, один — в кэш и один в сеть. Наш сетевой запрос из приложения не требует значительных изменений, но нам нужно изменить сервис воркер для кэширования ответа перед его выводом.
При нормальных обстоятельствах кэшированные данные будут возвращены почти сразу, обеспечивая приложение последними данными, которые оно может использовать. Затем, когда сетевой запрос вернется, приложение будет обновлено с использованием последних данных из сети.
Перехват сетевого запроса и кэширование ответа
Нам нужно изменить сервис воркер, чтобы перехватывать запросы к API погоды и сохранять ответы в кэше, чтобы позже мы могли легко получить к ним доступ. В стратегии cache-then-network мы ожидаем, что сетевой ответ станет ‘source of truth’ («источником правды»), всегда предоставляя нам самую свежую информацию. Если ответ не будет получен, ничего страшного, потому что мы уже извлекли последние кэшированные данные в наше приложении.
Давайте добавим dataCacheName
в сервис воркер, чтобы мы могли отделить данные приложения от оболочки приложения. Если оболочка приложения обновляется, и старый кэш очищается, наши данные остаются нетронутыми, и готовы к сверхбыстрой загрузке. Имейте в виду, что если формат ваших данных в будущем изменится, вам понадобится обработать эту ситуацию и обеспечить синхронизацию оболочки приложения и содержимого.
Добавьте следующую строку в начало файла service-worker.js
:
1 |
var dataCacheName = 'weatherData-v1'; |
Затем обновите обработчик события activate
, чтобы он не удалял кеш данных, когда он очищает кеш оболочки приложения.
1 |
if (key !== cacheName && key !== dataCacheName) { |
Наконец, обновите обработчик события fetch
для обработки запросов к API отдельно от других запросов.
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 32 |
self.addEventListener('fetch', function(e) { console.log('[Service Worker] Fetch', e.request.url); var dataUrl = 'https://query.yahooapis.com/v1/public/yql'; if (e.request.url.indexOf(dataUrl) > -1) { /* * When the request URL contains dataUrl, the app is asking for fresh * weather data. In this case, the service worker always goes to the * network and then caches the response. This is called the "Cache then * network" strategy: * https://jakearchibald.com/2014/offline-cookbook/#cache-then-network */ e.respondWith( caches.open(dataCacheName).then(function(cache) { return fetch(e.request).then(function(response){ cache.put(e.request.url, response.clone()); return response; }); }) ); } else { /* * The app is asking for app shell files. In this scenario the app uses the * "Cache, falling back to the network" offline strategy: * https://jakearchibald.com/2014/offline-cookbook/#cache-falling-back-to-network */ e.respondWith( caches.match(e.request).then(function(response) { return response || fetch(e.request); }) ); } }); |
Код перехватывает запрос и проверяет, начинается ли URL с адреса API погоды. Если да, то мы будем использовать для запроса fetch
. Как только ответ возвращается, наш код открывает кэш, клонирует ответ, сохраняет его в кэше и, наконец, возвращает ответ обратно.
Наше приложение пока еще не работает в автономном режиме. Мы реализовали кэширование и извлечение для оболочки приложения, но даже при том, что мы кэшируем данные, приложение еще не проверяет кэш, чтобы увидеть, есть ли там какие-либо данные о погоде.
Выполнение запросов
Как уже упоминалось ранее, приложение должно запускать два асинхронных запроса, один для кэша и один для сети. Для доступа к кэшу и получения последних данных приложение использует объект caches
доступный в window
. Это отличный пример прогрессивного улучшения, поскольку объект caches
может быть доступен не во всех браузерах, или если не доступен сетевой запрос, приложение все равно будет работать.
Для всего этого нам необходимо:
- Проверить, доступен ли объект
caches
в глобальном объектеwindow
. - Запросить данные из кеша.
- Если запрос сервера по-прежнему неактивен, обновить приложение с помощью кэшированных данных.
- Запросить данные с сервера.
- Сохранить данные для быстрого доступа позже.
- Обновить приложение со свежими данными от сервера.
Получение данных из кеша
Следующим шагом нам нужно проверить, существует ли объект caches
и запросить у него последние данные. Найдите комментарий TODO add cache logic here
в app.getForecas()
, и добавьте код ниже комментария.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
if ('caches' in window) { /* * Check if the service worker has already cached this city's weather * data. If the service worker has the data, then display the cached * data while the app fetches the latest data. */ caches.match(url).then(function(response) { if (response) { response.json().then(function updateFromCache(json) { var results = json.query.results; results.key = key; results.label = label; results.created = json.query.created; app.updateForecastCard(results); }); } }); } |
Теперь наше погодное приложение делает два асинхронных запроса для получения данных, один из cache
и один через XHR. Если в кеше имеются данные, они будут возвращены и отображены очень быстро (за десятки миллисекунд), но если XHR все еще выполняется. Затем, когда XHR вернет ответ, карточка с погодой будет обновлена самыми свежими данными непосредственно из API погоды.
Обратите внимание на то, как запрос кэша и запрос XHR заканчиваются вызовом для обновления карточки прогноза. Откуда приложение знает, отображает ли он последние данные? Это описано в следующем коде из app.updateForecastCard
:
1 2 3 4 5 6 7 8 9 |
var cardLastUpdatedElem = card.querySelector('.card-last-updated'); var cardLastUpdated = cardLastUpdatedElem.textContent; if (cardLastUpdated) { cardLastUpdated = new Date(cardLastUpdated); // Bail if the card has more recent data then the data if (dataLastUpdated.getTime() < cardLastUpdated.getTime()) { return; } } |
Каждый раз, при обновлении карточки, приложение сохраняет временную метку данных в скрытом атрибуте на карточке. Если временная метка, которая уже существует на карточке, новее, чем данные, переданные функции, приложение просто не обновляется.
Тестирование
Теперь приложение должно быть полностью автономным. Чтобы получить свежие данные о погоде сохраните несколько городов и нажмите в приложении кнопку обновления, затем перейдите в оффлайн режим и перезагрузите страницу.
Теперь перейдите в раздел Cache Storage
на панели Application
DevTools. Разверните секцию, и вы увидите имя вашей оболочки приложения и кеш данных, перечисленных чуть ниже слева. В данных кеша должна храниться информация по каждому городу.
8. Поддержка нативной интеграции
Никто не любит печатать длинные URL-адреса на мобильной клавиатуре. С помощью функции Add To home screen ваши пользователи могут добавить ярлык на домашний экран, как если бы они устанавливали нативное приложение из магазина, но с гораздо меньшим трением.
Web App Install Banners и Add to Homescreen для Chrome на Android
Web app install banners позволяют пользователям быстро и легко добавлять веб-приложение на свой домашний экран, что упрощает запуск и возврат в приложение. Добавить app install banners легко, и Chrome выполнит для вас большую часть тяжелой работы. Нам просто нужно включить файл манифеста веб-приложения с подробностями о приложении.
Затем Chrome будет использовать набор критериев, включая использование сервис воркера, статус SSL и частоту посещения, чтобы определить, когда показывать баннер. Кроме того, пользователь может вручную добавить его с помощью кнопки меню Add to Home Screen
в Chrome.
Объявление манифеста приложения с помощью файла
manifest.json
Манифест веб-приложения — это простой JSON файл, который дает вам, как разработчику, возможность контролировать, как ваше приложение будет выглядеть в тех областях, в которых пользователи ожидают его увидеть (например, мобильный домашний экран), указывать, что пользователь может запустить и что более важно, как он может это делать.
Используя манифест веб-приложения вы сможете:
- Улучшить отображение на домашнем экране пользователя Android
- Запускать приложение на Android в полноэкранном режиме без адресной строки
- Управлять ориентацией экрана для оптимального просмотра
- Определять «splash screen» при запуске приложения и цвет темы сайта
- Отслеживать запущено ли вы приложение с домашнего экрана или из адресной строки
Создайте файл с именем manifest.json
в рабочей папке work,
скопируйте и вставьте следующее содержимое:
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 |
{ "name": "Weather", "short_name": "Weather", "icons": [{ "src": "images/icons/icon-128x128.png", "sizes": "128x128", "type": "image/png" }, { "src": "images/icons/icon-144x144.png", "sizes": "144x144", "type": "image/png" }, { "src": "images/icons/icon-152x152.png", "sizes": "152x152", "type": "image/png" }, { "src": "images/icons/icon-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "images/icons/icon-256x256.png", "sizes": "256x256", "type": "image/png" }], "start_url": "/index.html", "display": "standalone", "background_color": "#3E4EB8", "theme_color": "#2F3BA2" } |
Манифест поддерживает массив иконок, предназначенных для различных размеров экрана. На момент написания этой статьи, Chrome и Opera Mobile — единственные браузеры, которые поддерживают манифесты веб-приложения, не использующие что-либо меньше, чем 192px.
Легкий способ отслеживать как приложение было запущено состоит в добавлении запрашиваемой строки к параметру start_url
и последующем ее анализе. При использовании этого метода не забудьте обновить список файлов, кэшируемых оболочкой приложения, чтобы убедиться, что файл с запрашиваемой строкой кэшируется.
Сообщаем браузеру о вашем manifest файле
Теперь добавьте следующую строку в нижнюю часть элемента <head>
в файле index.html
:
1 |
<link rel="manifest" href="/manifest.json"> |
Лучшие практики:
- Поместите ссылку манифеста на все страницы вашего сайта, так чтобы Chrome извлекал его, независимо от того, на какую страницу приземляется пользователь.
Short_name
является приоритетным для Chrome и будет использоваться он, если он присутствует, а не поле с именем.- Определите набор иконок для различной пиксельной плотности экранов. Chrome будет пытаться использовать иконку кратную 48dp, например, 96px на устройствах 2x или 144px для 3х устройств.
- Не забывайте использовать иконки размером, который подходит для сплеш скрина и не забудьте установить background_color.
Подробнее тут:
Использование app install banners
Add to Homescreen элемент для Safari на iOS
В конце элемента <head>
в вашем
index.html
добавьте следующее:
1 2 3 4 5 |
<!-- Add to home screen for Safari on iOS --> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-title" content="Weather PWA"> <link rel="apple-touch-icon" href="images/icons/icon-152x152.png"> |
Tile Иконка для Windows
В конце элемента <head>
в вашем index.html
добавьте следующее:
1 2 |
<meta name="msapplication-TileImage" content="images/icons/icon-144x144.png"> <meta name="msapplication-TileColor" content="#2F3BA2"> |
Тестирование
В этом разделе мы покажем вам пару способов протестировать манифест веб-приложения.
Первый способ — DevTools. Откройте раздел Manifest
на панели Application
. Если в манифесте вы все написали правильно, вы сможете увидеть, как он анализируется и отображается в удобном для пользователя формате.
В этой панели вы также можете протестировать функцию add to homescreen
. Нажмите кнопку Add to homescreen
. Вы должны увидеть сообщение add this site to your shelf
чуть ниже строки с URL адресом, как показано на скриншоте ниже.
Это десктопный вариант мобильной функции add to homescreen
. Если вы можете успешно выполнить эту всплывашку на десктопе, вы можете быть уверены, что мобильные пользователи смогут добавить ваше приложение на свои устройства.
Второй способ тестирования — через Web Server for Chrome
. При таком подходе вы открываете свой локальный сервер разработчика (на десктопе или ноутбуке) для других компьютеров, а затем просто подключаетесь к вашему прогрессивному веб-приложению с реального мобильного устройства.
Accessible on local network
и перезапустите веб-сервер.Web Server for Chrome
выберите параметр Accessible on local network
:Переключите веб-сервер в STOPPED
и вернитесь обратно в STARTED
. Вы увидите новый URL-адрес, который можно использовать для удаленного доступа к вашему приложению.
Теперь, используя новый URL, перейдите на свой сайт с мобильного устройства.
При тестировании вы увидите в консоли ошибки сервис воркера, потому что сервис воркер не обслуживается через HTTPS.
Используя Chrome в Android устройстве, попробуйте добавить приложение на рабочий стол и убедитесь, что окошко запуска отображается правильно, и используются правильные иконки.
Точно так же можно добавить приложение на свой рабочий стол в Safari и Internet Explorer.
9. Развертываем на безопасном хосте и пляшем
Последний шаг — развернуть наше погодное приложение на сервер, поддерживающий HTTPS. Если у вас его еще нет, самым простым (и бесплатным) решением является использование статического контента с Firebase. Он очень прост в использовании, обслуживает контент через HTTPS и поддерживается глобальным CDN.
Дополнительная информация: минификация и встроенный CSS
Есть еще одна вещь, которую вы должны учитывать, минимизируя ключевые стили и вставляя их непосредственно в index.html
. Page Speed Insights
рекомендует использовать не более 15k байт в первом запросе.
Посмотрите, насколько маленьким можно сделать первоначальный запрос.
Подробнее тут: PageSpeed Insight правила
Развертывание на Firebase
Если вы новичок в Firebase, то сперва вам нужно создать свою учетную запись и установить некоторые инструменты.
- Создайте учетную запись Firebase на https://firebase.google.com/console/
- Установите Firebase инструменты через npm:
npm install -g firebase-tools
Как только вы создали учетную запись и вошли в систему, вы готовы к развертыванию!
- Создайте новое приложение на странице https://firebase.google.com/console/
- Если вы не вошли в инструменты Firebase, обновите свои учетные данные:
firebase login
- Инициализируйте свое приложение и укажите каталог (например
work
), где будет ваше готовое приложение:firebase init
- Наконец, разверните приложение на Firebase:
firebase deploy
- Пляшите. Все готово! Ваше приложение будет развернуто в домене:
https://YOUR-FIREBASE-APP.firebaseapp.com
Подробнее тут: Firebase Hosting Guide
Тестирование
- Попробуйте добавить приложение на свой домашний экран, затем отключите сеть и убедитесь, что приложение работает в оффлайн режиме, как ожидалось.
Не совсем точный перевод кодлаба на codelabs.developers.google.com