
Наверное, все близкие к веб-разработке люди уже наслышаны о 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 всего за считанные часы. Всё это будет показано на примере реального сервиса — Скорочтец.
- Как сделать из сайта приложение и выложить его в Google Play за несколько часов. Часть 1/2: Progressive Web App
- Как сделать из сайта приложение и выложить его в Google Play за несколько часов. Часть 2/2: Trusted Web Activity
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
