Хабрахабр

Как сделать из сайта приложение и выложить его в Google Play за несколько часов. Часть 1/2: Progressive Web App

Ещё бы! Наверное, все близкие к веб-разработке люди уже наслышаны о Progressive Web App. Эта технология практически уравняла веб и мобильную разработку с точки зрения распространения продуктов и вовлечённости пользователей.

Но вот только скачивается это приложение в браузер и запускается из него. Да, современный фронтенд, написанный, например, на React, работает как приложение. Давайте подумаем, чем с точки зрения обычного пользователя, «приложение» отличается от «сайта». В этом и заключается огромный гандикап, который всегда имела мобильная разработка. Но ведь есть мобильный браузер, так что сайт и в телефоне тоже. Сразу в голову приходит, что приложение в телефоне, а сайт на компьютере. Тогда остаётся 3 существенных отличия:

  1. Иконка приложения есть на главном экране смартфона.
  2. Приложение открывается в отдельном окне.
  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 в Google Chrome на своём рабочем компьютере. Это инструмент для анализа сайтов в целом и для проверки соответствия стандарту Progressive Web App в частности.

Далее открываем наш сайт, боевой или запущенный локально, и генерируем отчёт для него при помощи Lighthouse:

В разделе Progressive Web App отчёта вы должны увидеть примерно следующее:

Во-первых, если вы запускаете сайт локально, а вам придётся это делать во время разработки и тестирования, то нужно использовать домен localhost и никакой другой. Обратите внимание на раздел Installable. Благодаря этому будет удовлетворено требование «Use HTTPS», а точнее Lighthouse просто закроет глаза на него, и вы сможете полноценно тестировать свой PWA.

Давайте сделаем это. Кроме требования HTTPS, чтобы наше приложение превратилось в PWA и стало устанавливаемым, нужно подключить к сайту service worker и Web app manifest.

Технология 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 => })) ) );
}); // '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 и искать в кэше запрашиваемые ресурсы, прежде чем идти за ними на сервер.

Так как Скорочтец является одностраничным приложением (SPA), то у него один единственный html, который после добавления указанного скрипта выглядит вот так: Чтобы всё это заработало, нужно добавить скрипт для регистрации сервис-воркера в html-файлы.

index.html

<!DOCTYPE html>
<html lang="en">
<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:

Информация о стартовой странице и не только указывается в Web App Manifest, давайте добавим его! Если сравнивать с предыдущим отчётом, то теперь у нас удовлетворён второй пункт и сайт отвечает 200 даже offline, а также в 5-м пункте мы видим, что сервис-воркер обнаружен, но вот стартовой страницы не хватает.

Манифест предоставляет информацию о нашем приложении: короткое и длинное имя, иконки всех размеров, стартовая страница, цвета и ориентация.

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="en">
<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.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть