Как сделать из сайта приложение и выложить его в 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


На входе у нас есть веб-сайт с мобильной вёрсткой:


Первым делом нужно установить расширение 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="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:



Если сравнивать с предыдущим отчётом, то теперь у нас удовлетворён второй пункт и сайт отвечает 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="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.

Полезные ссылки


  • Introduction to Progressive Web Apps (Offline First) — Part 1 auth0.com/blog/introduction-to-progressive-apps-part-one
  • Introduction to Progressive Web Apps (Instant Loading) — Part 2 auth0.com/blog/introduction-to-progressive-web-apps-instant-loading-part-2
  • Introduction to Progressive Web Apps (Push Notifications) — Part 3 auth0.com/blog/introduction-to-progressive-web-apps-push-notifications-part-3
  • Разработка вашего первого Progressive Web App c React tuhub.ru/posts/progressive-web-app-with-react
  • 9 amazing PWA secrets www.creativebloq.com/features/9-amazing-pwa-secrets
  • Progressive Web Apps on iOS are here medium.com/@firt/progressive-web-apps-on-ios-are-here-d00430dee3a7
  • Progressive Web Apps with React.js: Part I — Introduction medium.com/@addyosmani/progressive-web-apps-with-react-js-part-i-introduction-50679aef2b12
  • Progressive Web Apps with React.js: Part 2 — Page Load Performance medium.com/@addyosmani/progressive-web-apps-with-react-js-part-2-page-load-performance-33b932d97cf2
  • Progressive Web Apps with React.js: Part 3 — Offline support and network resilience medium.com/@addyosmani/progressive-web-apps-with-react-js-part-3-offline-support-and-network-resilience-c84db889162c?source=user_profile---------11----------------
  • Progressive Web Apps with React.js: Part 4 — Progressive Enhancement medium.com/@addyosmani/progressive-web-apps-with-react-js-part-4-site-is-progressively-enhanced-b5ad7cf7a447
  • A React And Preact Progressive Web App Performance Case Study: Treebo medium.com/dev-channel/treebo-a-react-and-preact-progressive-web-app-performance-case-study-5e4f450d5299
Источник: https://habr.com/ru/company/mailru/blog/450504/


Интересные статьи

Интересные статьи

В прошлый раз мы осваивали создание нового проекта при помощи STM CubeMX первую часть можно найти здесь. Для тех, кому лень перечитывать — закончилось все тем, что пустой проект успешно собрался...
В прошлом месяце мы уже писали о том, что нового в железе и программном обеспечении камер iPhone 11 и 11 Pro. И вы могли заметить, что изменения, произошедшие в оборудовании, были довольно ск...
Компании переполнили рынок товаров и услуг предложениями. Разнообразие наблюдается не только в офлайне, но и в интернете. Достаточно вбить в поисковик любой запрос, чтобы получить подтверждение насыще...
Посвящается Андрею А., котрому я так и не принес до сих пор извинения за испорченную силиконом вещь... Продолжаем неспешно наши рассказы о рыбалке опусы про клеи. Сегодняшний наш герой не може...
Сегодня мы продолжим разговор об использовании форм в React. В прошлый раз мы рассматривали особенности взаимодействия компонентов и текстовых полей. Здесь же мы обсудим работу с другими элемента...