company_banner

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

  • Tutorial


Наверное, все близкие к веб-разработке люди уже наслышаны о 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 всего за считанные часы. Всё это будет показано на примере реального сервиса — Скорочтец.

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

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


Mail.ru Group
1 198,23
Строим Интернет
Поделиться публикацией

Похожие публикации

Комментарии 31

    +6
    Lighthouse встроен в хром по умолчанию. Вкладка audit
      0

      Спасибо, не знал)

      0
      PWA до сих пор не работают нормально с куками/storage в ios/safari в текущей версии, в итоге контекст теряется. Пахнет яблоками и саботажем.
        +1

        А что именно не работает? Я не встречал проблем.

          0
          А именно — при переключении в другой апп и обратно — теряется localstorage, а вместе с ним и сессия/токен/вотева. iOS 12.3
            0
            Есть же IndexedDB.
              0

              Только что проверил в iOS 12.2 – ничего не теряется. Вечно эти обновления всё ломают((

                0

                В 12.2 другой прикол — external auth redirect, например OAuth2, уходит в браузер, а не в PWA

              0

              Работают ли пуш нотификейшн в айосе?

          0

          service-worker.js может быть и пустым, верно? Например, если моё приложение не может функционировать офлайн?
          И что насчёт стандартного браузерного кеша, ведь он же должен работать как обычно? Зачем тогда мы повторно реализуем кеширование в service-worker.js?

            0

            Нет, не может. Install и activate должны отработать. А вот почему стандартный кеш не используется – хороший вопрос. Скорее всего просто потому, что сервис-воркер так сделан. Извините за банальщину.

            0
            Давайте подумаем, чем с точки зрения обычного пользователя, «приложение» отличается от «сайта». Сразу в голову приходит, что приложение в телефоне, а сайт на компьютере. Но ведь есть мобильный браузер, так что сайт и в телефоне тоже.

            Посыл ясен, но приложение бывает и на компьютере тоже)

              0

              Согласен, рассуждение не строгое)

              0

              Если это туториал, а не перевод с англ. то хорошо бы ориентироваться на 'нашу' публику, а не иностранцев.
              Буду ждать продолжения.
              Реально интересует связка с гугл плеем.
              P.s. — Хотя первоисточники рулят на ангельском*…

                0

                И да, ничего не упомянуто про develop-environment по запуску локального вэб-сервера. Имхо на таких деталях кто-то посыпется… На худой конец ссылочку прикрепить по данному моменту.
                Если это туториал, то по нему должно получиться выполнение задания, иначе это просто статья.

                  0

                  Я запускал даже на статическом сервере (webpack-dev-server). Главное, файлы по нужным урлам отдать, как написано в статье.


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

                  0

                  Да, это не перевод. А почему возникло впечатление, что ориентируюсь на иностранцев? Из-за ссылок в конце?

                  +1
                  А еще Chrome поддерживает PWA для десктопов.
                    –1
                    Вот тут диссонанс возникает.
                    Язык английский, а текст русский
                    <html lang="en"
                    <title Скорочтец
                      0
                      Спасибо, поправил)
                      0

                      Я понимаю что не всем нужны пуши в приложениях, и статья про андроид, но пока в айос нет возможности отправить пуш уведомления, ценность pwa сильно под вопросом

                        0

                        Ждём)

                          0

                          Я два года ждал когда они добавят MSE. Не дождался.

                        0
                        Возможно забегаю вперед, а как работает монетизация? Будет использоваться adsense для web сайтов или мобильный?
                          0
                          Не монетизируюсь через рекламу, поэтому не подскажу.
                          0
                          Если в андроиде не стоит настройка «разрешить установку сторонних приложений» то баннер с подпиской не появляется. Я правильно понимаю, что нужно через preventdefault делать собственную кнопку? Либо приложение PWA можно как-то прописать в доверенные гугла?
                            0
                            Собственную кнопку можно сделать, как показано для iOS, например. А вот насчёт доверенных не знаю.
                            0
                            Можно ли как-то суммировать скачивания PWA напрямую с сайта к скачиваниям из Play Store при условии, что скачали с Android? Или как-нибудь перенаправить пользователя обновлять приложение уже через Play Store при условии, что изначально пользователь «установил» приложение с сайта?
                            0
                            -

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое