Наверное, все близкие к веб-разработке люди уже наслышаны о Progressive Web App. Ещё бы! Эта технология практически уравняла веб и мобильную разработку с точки зрения распространения продуктов и вовлечённости пользователей.
Да, современный фронтенд, написанный, например, на React, работает как приложение. Но вот только скачивается это приложение в браузер и запускается из него. В этом и заключается огромный гандикап, который всегда имела мобильная разработка. Давайте подумаем, чем с точки зрения обычного пользователя, «приложение» отличается от «сайта». Сразу в голову приходит, что приложение в телефоне, а сайт на компьютере. Но ведь есть мобильный браузер, так что сайт и в телефоне тоже. Тогда остаётся 3 существенных отличия:
- Иконка приложения есть на главном экране смартфона.
- Приложение открывается в отдельном окне.
- Приложение отправляет push-уведомления.
Все 3 пункта снимаются благодаря Progressive Web App или PWA. Теперь, заходя на сайт из мобильного браузера, мы можем «скачать» его, после чего увидим иконку на главном экране. Кроме того, при запуске появляется заставка, как у мобильных приложений, и при желании можно настроить отправку push-уведомлений.
И казалось бы, всё прекрасно! Но увы, за 10 с лишним лет мобильной эпохи пользователи слишком сильно привыкли искать приложения в Google Play и App Store. Ломать привычки пользователей — дело неблагодарное, и потому ребята из Google (кстати, Google является разработчиком PWA) решили, что если гора не идёт к Магомеду, то… В общем, совсем недавно, 6 февраля 2019 года, они обеспечили использование Trusted Web Activities для выкладки веб-приложений в Google Play.
В статье из двух частей будет рассказано, как пройти полный путь от обычного веб-сайта до приложения в Google Play всего за считанные часы. Всё это будет показано на примере реального сервиса — Скорочтец.
Lighthouse
На входе у нас есть веб-сайт с мобильной вёрсткой:
Первым делом нужно установить расширение Lighthouse в Google Chrome на своём рабочем компьютере. Это инструмент для анализа сайтов в целом и для проверки соответствия стандарту Progressive Web App в частности.
Далее открываем наш сайт, боевой или запущенный локально, и генерируем отчёт для него при помощи Lighthouse:
В разделе Progressive Web App отчёта вы должны увидеть примерно следующее:
Обратите внимание на раздел Installable. Во-первых, если вы запускаете сайт локально, а вам придётся это делать во время разработки и тестирования, то нужно использовать домен localhost и никакой другой. Благодаря этому будет удовлетворено требование «Use HTTPS», а точнее Lighthouse просто закроет глаза на него, и вы сможете полноценно тестировать свой PWA.
Кроме требования HTTPS, чтобы наше приложение превратилось в PWA и стало устанавливаемым, нужно подключить к сайту service worker и Web app manifest. Давайте сделаем это.
Service worker
Технология service workers позволяет вашему сайту быть online даже тогда, когда сервер недоступен. Это такой посредник между клиентом и сервером, который перехватывает каждый запрос и в случае чего подсовывает данные из кэша в качестве ответа.
Для работы PWA достаточно базовой реализации service worker, которая выглядит следующим образом:
service-worker.js
// Должно быть true в production
var doCache = true;
// Имя кэша
var CACHE_NAME = 'my-pwa-cache-v2';
// Очищает старый кэш
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys()
.then(keyList =>
Promise.all(keyList.map(key => {
if (!cacheWhitelist.includes(key)) {
console.log('Deleting cache: ' + key)
return caches.delete(key);
}
}))
)
);
});
// 'install' вызывается, как только пользователь впервые открывает PWA
self.addEventListener('install', function(event) {
if (doCache) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
// Получаем данные из манифеста (они кэшируются)
fetch('/static/reader/manifest.json')
.then(response => {
response.json()
})
.then(assets => {
// Открываем и кэшируем нужные страницы и файлы
const urlsToCache = [
'/app/',
........
'/static/core/logo.svg*',
]
cache.addAll(urlsToCache)
console.log('cached');
})
})
);
}
});
// Когда приложение запущено, сервис-воркер перехватывает запросы и отвечает на них данными из кэша, если они есть
self.addEventListener('fetch', function(event) {
if (doCache) {
event.respondWith(
caches.match(event.request).then(function(response) {
return response || fetch(event.request);
})
);
}
});
Здесь реализованы обработчики для трёх событий:
install
, activate
и fetch
. Как только пользователь откроет сайт, на котором есть service worker, вызовется событие install
. Это процедура установки сервис-воркера в браузер пользователя. В её обработчике в массиве urlsToCache
вы можете указать страницы сайта, которые будут кэшироваться, включая статику. Затем вызывается activate
, которое очищает ресурсы, использованные в предыдущей версии скрипта сервис-воркера. И теперь, когда сервис-воркер успешно установлен, он будет перехватывать каждое событие fetch
и искать в кэше запрашиваемые ресурсы, прежде чем идти за ними на сервер.Чтобы всё это заработало, нужно добавить скрипт для регистрации сервис-воркера в html-файлы. Так как Скорочтец является одностраничным приложением (SPA), то у него один единственный html, который после добавления указанного скрипта выглядит вот так:
index.html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Скорочтец</title>
</head>
<body>
<div id="root"></div>
<script src="/static/build/app.js"></script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
}).catch(function(err) {
console.log(err)
});
});
} else {
console.log('service worker is not supported');
}
</script>
</body>
</html>
Функция
navigator.serviceWorker.register('/service-worker.js')
принимает в качестве аргумента URL, по которому расположен файл сервис-воркера. Здесь не важно, как именно называется файл, но важно, чтобы он был расположен в корне домена. Тогда областью видимости сервис-воркера станет весь домен, и он будет получать события fetch
из любой страницы.Таким образом, расположив файл сервис-воркера по адресу skorochtec.ru/service-worker.js и добавив нужный скрипт в html, мы получаем следующую картину в отчёте Lighthouse:
Если сравнивать с предыдущим отчётом, то теперь у нас удовлетворён второй пункт и сайт отвечает 200 даже offline, а также в 5-м пункте мы видим, что сервис-воркер обнаружен, но вот стартовой страницы не хватает. Информация о стартовой странице и не только указывается в Web App Manifest, давайте добавим его!
Web App Manifest
Манифест предоставляет информацию о нашем приложении: короткое и длинное имя, иконки всех размеров, стартовая страница, цвета и ориентация.
manifest.json
{
"short_name": "Скорочтец",
"name": "Скорочтец",
"icons": [
{
"src":"/static/core/manifest/logo-pwa-16.png",
"sizes": "16x16",
"type": "image/png"
},
{
"src":"/static/core/manifest/logo-pwa-32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src":"/static/core/manifest/logo-pwa-48.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src":"/static/core/manifest/logo-pwa-72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src":"/static/core/manifest/logo-pwa-96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src":"/static/core/manifest/logo-pwa-144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src":"/static/core/manifest/logo-pwa-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src":"/static/core/manifest/logo-pwa-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "/app/",
"background_color": "#7ACCE5",
"theme_color": "#7ACCE5",
"orientation": "any",
"display": "standalone"
}
Последняя переменная указывает, что это будет отдельное приложение. Файл манифеста необходимо расположить на сайте (не обязательно в корне) и подключить его в html:
index.html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Скорочтец</title>
<!-- Add manifest -->
<link rel="manifest" href="{% static "core/manifest/manifest.json" %}">
<!-- Tell the browser it's a PWA -->
</head>
<body>
<div id="root"></div>
<script src="/static/build/app.js"></script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
}).catch(function(err) {
console.log(err)
});
});
} else {
console.log('service worker is not supported');
}
</script>
</body>
</html>
Давайте снова проанализируем сайт Lighthouse-ом:
Ура! Теперь у нас не просто сайт, а Progressive Web App! Возможно, вы заметили, что скорость загрузки резко подросла. Это никак не связано с тем, что мы делали, просто я заменил development-сборку React-приложения на production, чтобы отчёт выглядел максимально красиво.
Ну что ж, заходим на сайт из мобильного Chrome и что же мы видим?
Да! Можно открывать шампанское! Добавляем приложение на главный экран:
Бонусом получаем заставку при запуске, которая собирается из указанных в манифесте name, background_color и иконки 512x512 в массиве icons:
К сожалению, цвет текста подбирается автоматически, что в случае Скорочтеца немного ломает стиль.
Ну и само приложение:
Ограничения
На данный момент PWA поддерживается только в Chrome и Safari (начиная с iOS версии 11.3). Причём, Safari поддерживает эту технологию «по-тихому». Пользователь может добавить приложение на главный экран, но только никакого сообщения об этом нет, в отличие от Chrome.
Полезные советы и трюки
1. Предложение об установке на Safari
Поскольку в Apple этого не сделали (надеемся, что пока не сделали), то приходится реализовывать «руками». Получается вот такое:
Реализуется следующим JavaScript-кодом:
const isIos = () => {
const userAgent = window.navigator.userAgent.toLowerCase();
return /iphone|ipad|ipod/.test( userAgent );
};
// Проверяем, открыто ли приложение отдельно или в браузере
const isInStandaloneMode = () => ('standalone' in window.navigator) && (window.navigator.standalone);
// Если приложение открыто на iOS и в браузере, то предлагаем установить
if (isIos() && !isInStandaloneMode()) {
this.setState({ isShown: true }); // На примере React
}
2. Отслеживание установок
Это работает только в Google Chrome. Нужно добавить в html скрипт, отлавливающий событие appinstalled и, например, отправлять на свой сервер сообщение об этом:
<script>
window.addEventListener('appinstalled', (evt) => {
fetch(<your_url>, {
method: 'GET',
credentials: 'include',
});
});
</script>
3. Правильный выбор start_url
Обязательно нужно позаботиться о том, что url всех страниц приложения являются продолжением
start_url
, указанного в манифесте. Потому что, если вы укажете "start_url": "/app/"
, а затем пользователь перейдёт на страницу, скажем, "/books/", то тут же покажет себя адресная строка браузера и весь пользовательский опыт сломается. Кроме того, человек почувствует себя обманутым: думал, что использует приложение, а это замаскированный браузер. И даже theme_color
из манифеста, который окрасит интерфейс браузера в ваш фирменный цвет, не спасёт.В случае Скорочтеца, все страницы, относящиеся к приложению, начинаются с /app/, поэтому таких казусов не возникает.
Что дальше?
Ну что ж, теперь вы знаете, как забраться к пользователю на главный экран смартфона через ваш сайт. Но это только одна из дверей, и скорее всего не парадная. Во второй части будет рассказано, как войти через парадную дверь: вы узнаете, как выложить ваше прогрессивное веб-приложение в Google Play.
Полезные ссылки
- Introduction to Progressive Web Apps (Offline First) — Part 1
- Introduction to Progressive Web Apps (Instant Loading) — Part 2
- Introduction to Progressive Web Apps (Push Notifications) — Part 3
- Разработка вашего первого Progressive Web App c React
- 9 amazing PWA secrets
- Progressive Web Apps on iOS are here
- Progressive Web Apps with React.js: Part I?—?Introduction
- Progressive Web Apps with React.js: Part 2?—?Page Load Performance
- Progressive Web Apps with React.js: Part 3?—?Offline support and network resilience
- Progressive Web Apps with React.js: Part 4?—?Progressive Enhancement
- A React And Preact Progressive Web App Performance Case Study: Treebo
Комментарии (24)
frankmasonus
07.05.2019 15:45-1PWA до сих пор не работают нормально с куками/storage в ios/safari в текущей версии, в итоге контекст теряется. Пахнет яблоками и саботажем.
macket Автор
07.05.2019 17:14+1А что именно не работает? Я не встречал проблем.
frankmasonus
07.05.2019 23:51А именно — при переключении в другой апп и обратно — теряется localstorage, а вместе с ним и сессия/токен/вотева. iOS 12.3
macket Автор
08.05.2019 14:04Только что проверил в iOS 12.2 – ничего не теряется. Вечно эти обновления всё ломают((
frankmasonus
08.05.2019 14:39В 12.2 другой прикол — external auth redirect, например OAuth2, уходит в браузер, а не в PWA
catanfa
07.05.2019 16:12service-worker.js
может быть и пустым, верно? Например, если моё приложение не может функционировать офлайн?
И что насчёт стандартного браузерного кеша, ведь он же должен работать как обычно? Зачем тогда мы повторно реализуем кеширование вservice-worker.js
?macket Автор
07.05.2019 17:44Нет, не может. Install и activate должны отработать. А вот почему стандартный кеш не используется – хороший вопрос. Скорее всего просто потому, что сервис-воркер так сделан. Извините за банальщину.
vyo
07.05.2019 17:24Давайте подумаем, чем с точки зрения обычного пользователя, «приложение» отличается от «сайта». Сразу в голову приходит, что приложение в телефоне, а сайт на компьютере. Но ведь есть мобильный браузер, так что сайт и в телефоне тоже.
Посыл ясен, но приложение бывает и на компьютере тоже)
KeyJoo
07.05.2019 22:25Если это туториал, а не перевод с англ. то хорошо бы ориентироваться на 'нашу' публику, а не иностранцев.
Буду ждать продолжения.
Реально интересует связка с гугл плеем.
P.s. — Хотя первоисточники рулят на ангельском*…KeyJoo
07.05.2019 22:32И да, ничего не упомянуто про develop-environment по запуску локального вэб-сервера. Имхо на таких деталях кто-то посыпется… На худой конец ссылочку прикрепить по данному моменту.
Если это туториал, то по нему должно получиться выполнение задания, иначе это просто статья.macket Автор
07.05.2019 23:00Я запускал даже на статическом сервере (webpack-dev-server). Главное, файлы по нужным урлам отдать, как написано в статье.
Ну и есть одна тонкость, её тоже указал:
"Во-первых, если вы запускаете сайт локально, а вам придётся это делать во время разработки и тестирования, то нужно использовать домен localhost и никакой другой. Благодаря этому будет удовлетворено требование «Use HTTPS», а точнее Lighthouse просто закроет глаза на него, и вы сможете полноценно тестировать свой PWA."
macket Автор
07.05.2019 22:56Да, это не перевод. А почему возникло впечатление, что ориентируюсь на иностранцев? Из-за ссылок в конце?
Alexufo
Lighthouse встроен в хром по умолчанию. Вкладка audit
macket Автор
Спасибо, не знал)