Progressive Web Apps - что это такое и для чего оно нужно

Для тех, кто не понял с первого раза

Denis Grushkin
Mad Devs — блог об IT

--

Progressive Web Apps.

Говорят, чтобы статья была максимально полезной, она должна быть понятна даже твоему деду, который до сих пор не понимает чем вы там занимаетесь в своих “интырнетах”. Исходя из этого, я решил довольно простым языком расписать “Что такое PWA и нахрен оно вообще нужно?”. Поэтому, если тебе не удалось с первого раза разобраться с этой неведомой штукой, в этой статье будет всё, чтобы решить это раз и навсегда.

Цель статьи:

Я не буду подробно разжевывать тебе о PWA, так как на эту тему написано уже много статей.

Моя цель — простым языком дать основную информацию, которой тебе хватит, чтобы понимать что такое PWA. Потом показать реальный пример, которым забетонируем всё, что ты усвоил.

После прочтения ты будешь понимать следующее:

  1. Возможности и преимущества.
  2. Основные понятия.
  3. Настройка PWA.
  4. Как обновлять кэш.

А ну и еще, я настоятельно рекомендую взять сейчас ноутбук и повторять всё, что я буду делать. Иначе, информация вылетит из головы.

На этом вводная часть закончена, переходим к основной части.

Возможности и преимущества

Я подготовил несколько логически связанных пунктов, которыми попробую описать всю картину.

Технология PWA была анонсирована компанией Google в 2015 году. Она позиционирует себя как дополнительная надстройка, которая позволяет сделать из сайта подобие мобильного приложения.

При этом внутренности никакие не меняются, не трансформируются, сайт остаётся тем же, трансформируется только браузер.

Какие возможности предоставялет PWA?

  • Отправка уведомлений, кэширование контента и установка ярлыка на рабочий стол;
  • Отправка уведомлений в виде всплывающего окна, в котором можно оповещать пользователя о чем-либо;
  • Работа в автономном режиме, то есть в отсутствие интернет-соединения, благодаря кэшированию контента.

Пару слов о преимуществах

  • Простота в установке. Не нужно идти ни в какие магазины приложений, что-то скачивать и танцевать с бубном. Просто открываешь сайт по ссылке, выскакивает окошко “установить ярлык на рабочий стол”, устанавливаешь и готово.
  • Конечно это будет работать на всех более-менее современных устройствах, нужен только браузер.
  • Сайт становится доступнее, из-за ярлыка на рабочем столе. Разблокировал телефон, тыкнул на ярлык и сайт открылся. Круто же.
  • Занимает мало памяти, менее одного мегабайта.
  • Повышенная безопасность, все ресурсы передаются только по https протоколу.
  • Стабильная работа. Если будут неполадки с интернетом, контент будет компенсирован из кэша, тем самым сайт будет всегда доступен.
  • Настройка PWA менее трудоемкий процесс, нежели создание мобильного приложения. Не нужно писать две одинаковые прилы на Android и IOS. Поэтому, для бизнеса это будет намного дешевле. Более подробно можно узнать тут:

Не приятные моменты

  • Есть заблуждение, по поводу того, что PWA помогает улучшить SEO показатели. Я с этим не согласен!
    И первая проблема с которой ты сталкнешься — это SPA, где html разметку на страницу рендерит javascript. Пока скрипты не загрузятся(сколько им там нужно) разметка не появится, а будет лишь div с айдишником “app”. Вот как раз в тот момент, когда всё раздупляется, происходит SEO анализ, но страница, как ты понял, пустая. И хоть ты добавишь +100500 PWA на сайт, они никак не ускорят рендер html кода.
    И чтобы быть менее голословным, давай убедимся на реальном примере. Возьмём сайт https://madops.io, который и является single page application’ом. Если посмотреть на его внутренности view-source:https://madops.io, то ты и увидишь всё то, что я описал выше.
    В остальных же случаях, когда сервер разом рендерит всю html разметку, проблем не возникает, как, например, тут view-source:https://maddevs.io
  • Ограниченные возможности. Такие функции как управление камерой, отправка смс, доступ к датчикам и многое другое не будет доступно для PWA, по соображениям безопасности.
  • Не поддерживается некоторыми браузерами. Например, на IOS в safary пока не поддерживаются push-уведомления.

Думаю, этого вполне достаточно, чтобы уже представить что такое PWA. Ну а если мало, можно почитать тут

Вот список сайтов, которые используют PWA — https://pwa.rocks

Основные понятия

Service Worker — Это по сути файл со скриптами, который отвечает за всю эту магию. Все запросы браузера идут через него, что даёт кучу возможностей, например, если отсутствует соединение с интернетом возвращает контент из кэша(если он конечно там есть).
В нем мы обрабатываем различные события, запись, удаление файлов из кэша и многое другое.
Скрипты работают в фоновом режиме, параллельно с приложением.

Manifest.json — Файл настроек. Там мы указывает какие иконки использовать, какой текст отображать в ярлыке, в каком формате открывать окно браузера и тд. Более подробно поговорим о нём чуть ниже.

Application Shell — Так называют оболочку для PWA. А если конкретнее, это браузер, который слегка трансформируется, чтобы дать больше возможностей разработчику.

HTTPS — Одно из главных требования PWA — передача данных по https протоколу, что более безопасно.
При разработке можно использовать localhost.

Push Notifications — технология для отправки push-уведомлений.

Настройка PWA

А настроить её на самом деле очень просто, и чтобы это доказать, давай сразу перейдем к написанию кода, так будет проще. Погнали!

А нет, стой.

Вот ссылка на уже готовый код https://github.com/denisoed/PWA-example, от туда можешь скачать картинки, которые потребуются дальше, ну и за одно ознакомишься с тем, что получилось.

Итак, начнем с создания папки для проекта, назовем её PWA. Далее в эту папку добавим index.html, в котором будет вот такой код

<!doctype html><html lang="en">

<head>
<meta charset="utf-8">
<title>PWA</title>
<meta name="description" content="Progressive Web Apps">
</head>
<body class="fullscreen">
<div class="container">
<a href="https://maddevs.io" target="_blank">
<img src="./images/logo.svg" alt="Mad Devs">
</a>
<h1>PWA</h1>
<p>Progressive Web Apps</p>
</div>
</body>
</html>

Верстку я уже подготовил, но пока без стилей это выглядит плохо, поэтому добавим и их. Создаём папку css, в которую добавляем файл styles.css и вставляем в него код ниже

body {
font-family: sans-serif;
}
/* Make content area fill the entire browser window */
html,
.fullscreen {
display: flex;
height: 100%;
margin: 0;
padding: 0;
width: 100%;
background-color: #000;
}
/* Center the content in the browser window */
.container
{
margin: auto;
text-align: center;
}
.container img {
width: 50px;
height: auto;
}
.container h1 {
color: #fff;
font-size: 12rem;
font-weight: bold;
margin: 30px 0 -20px;
}
.container p {
color: #fff;
font-size: 3rem;
margin: 0;
}

Конечно же, подключаем этот файл в index.html, в тэг <head>…</head>

<link rel="stylesheet" href="css/styles.css">

Давай сразу подключим нужные картинки, которые можно скачать тут. Перейдешь по ссылке, там будет кнопка Clone or download, зеленая такая, жмешь её, потом кликаешь Download ZIP. Скачается архив, в нем и будут картинки в папке images. Фух, думаю довольно доступно объяснил

Настройка PWA.

Открываешь проект, создаешь там директорию images, куда вставляешь все картинки. Далее открываешь index.html и вставляешь в тэг <head>…</head> мета информацию. Что это и зачем, можешь почитать тут.

<link rel="icon" href="images/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="images/mstile-150x150.png">
<meta name="theme-color" content="black" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="PWA">
<meta name="msapplication-TileImage" content="images/mstile-144x144.png">
<meta name="msapplication-TileColor" content="#FFFFFF">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

В итоге, в файле index.html должна быть вот такая структура

<!doctype html><html lang="en">

<head>
<meta charset="utf-8">
<title>PWA</title>
<meta name="description" content="Progressive Web Apps">
<link rel="icon" href="images/favicon.ico" type="image/x-icon" />
<link rel="apple-touch-icon" href="images/mstile-150x150.png">
<meta name="theme-color" content="black" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="PWA">
<meta name="msapplication-TileImage" content="images/mstile-144x144.png">
<meta name="msapplication-TileColor" content="#000">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/styles.css">
</head>
<body class="fullscreen">
<div class="container">
<a href="https://maddevs.io" target="_blank">
<img src="./images/logo.svg" alt="Mad Devs">
</a>
<h1>PWA</h1>
<p>Progressive Web Apps</p>
</div>
</body>
</html>

Теперь остаётся запустить и посмотреть, что получилось. Я тут нашел очень удобный экстеншн Web Server for Chrome, который запускает локальный сервер, его нужно установить, он нам понадобится далее. Там ничего сложно, просто укажи папку с проектом, где лежит index.html, он сам всё сделает. Копируешь ссылку и вставляешь в браузер.

Настройка и запуск веб сервера.
Настройка и запуск веб сервера

И вот что у нас получилось. Я бы не сказал, что это шЭдЭвр, но как по мне нормально!

PWA.
шЭдЭвр

Ну слушай, самое сложное, считай, мы сделали, давай-ка теперь посмотрим, что думает google валидация о нашей работе. Для этого нажми f12 и перейди во вкладку Lighthouse (раньше было Audits), там будет синяя кнопка Generate report, тыкай.

Проверка PWA google.

После того, как пройдет процесс валидации, мы увидим следующую картину: пункт отвечающий за PWA будет серым. Это говорит о том, что у нас отсутствуют настройки.

Результат проверки PWA.

И если проскроллить ниже, можно увидеть рекомендации, которые нужно выполнить, чтобы PWA работало как часы.

Вкладка Lighthouse поможет тебе отслеживать все ошибки при настройки PWA.

Выявленные ошибки.

Ну вот, наконец-то мы дошли до самого интересного.

Для начала нужно создать файл manifest.json в корне проекта. В него мы добавляем следующие метаданные:

  • name — Полное название. Используется в ярлыке приложения
  • short_name — Сокращенное название, будет задействовано там, где полное имя не будет помещаться
  • icons — Список иконок, которые будут отображаться в ярлыке установленного приложения.
  • lang — Язык по умолчанию
  • start_url — Обязательный параметр. Он говорит приложению с какого файла нужно стартовать. При открытии приложения, браузер будет всегда открывать эту страницу
  • display — Говорит о том, в каком формате открывать окно браузера.
  • background_color — Это свойство используется на экране заставки при первом запуске приложения на мобильном устройстве.
  • theme_color — Задает цвет панели инструментов и может быть отражен в предварительном просмотре приложения в переключателях задач. theme_color должен соответствовать цвету мета-темы, указанному в заголовке документа. В Нашем случае так <meta name=”theme-color” content=”black” />
{
"name": "Progressive Web Apps",
"short_name": "PWA",
"icons": [
{
"src": "images/mstile-70x70.png",
"sizes": "70x70",
"type": "image/png"
},
{
"src": "images/mstile-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "images/mstile-150x150.png",
"sizes": "150x150",
"type": "image/png"
},
{
"src": "images/mstile-192x192.png",
"sizes": "310x150",
"type": "image/png"
},
{
"src": "images/mstile-310x150.png",
"sizes": "310x150",
"type": "image/png"
},
{
"src": "images/mstile-310x310.png",
"sizes": "310x310",
"type": "image/png"
},
{
"src": "images/mstile-512x512.png",
"sizes": "310x310",
"type": "image/png"
}
],
"lang": "en-US",
"start_url": "/index.html",
"display": "standalone",
"background_color": "black",
"theme_color": "black"
}

Этого пока достаточно. Вот тут есть описание всех свойств этого файла, как будет время, почитай обязательно.

Подключаем manifest.json в index.html в тэг <head>…</head>

<link rel="manifest" href="/manifest.json">

Приступаем к написанию скриптов. Создаем папку c названием js, в которую добавляем файл main.js с вот таким кодом:

window.addEventListener('load', () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(reg => {
console.log('SW registered!');
}).catch(err => console.log('SW registration FAIL:', err));
}
});

Пару слов о том, что там происходит.

  • Как только страница загрузилась, проверяем, поддерживает ли браузер serviceWorker, и если успешно, то идём дальше.
  • Далее регистрируем наш файл sw.js(который еще предстоит настроить). Ничего необычного.

Подключаем cкрипты в index.html, но уже не в тэг <head>…</head>, а перед закрывающим тэгом </body>.

<script src="js/main.js"></script>

Ну теперь давай создадим сам файл sw.js. В нем будет храниться вся логика для Service Worker. Создаем его в корне проекта, и первой строкой добавим туда название кэша.

const cacheName = 'pwa_v1';

Следующей строкой добавим переменную includeToCache. В ней мы указываем файлы, которые нужно закэшировать. Да, я понимаю, что не удобно, приходится руками прописывать все файлы, но имеем то, что имеем. Зато мы всегда будем уверены, что ничего лишнего не кэшируется. Экономия трафика и стабильность.

const includeToCache = [
'/',
'/index.html',
'/images/favicon.ico',
'/images/logo.svg',
'/images/logo-black.svg',
'/css/styles.css',
'/js/main.js'
];

Следующими идут события. Service Worker имеет несколько событий под капотом, их ещё называют жизненные циклы. И первое из них - это install. Оно срабатывает только один раз при записи кэша.

/* Start the service worker and cache all of the app's content */
self.addEventListener('install', e => {
e.waitUntil(
caches.open(cacheName).then(cache => {
return cache.addAll(includeToCache);
})
);
});

Событие fetch. Это событие просматривает все запросы, и если что-то совпадает с тем, что лежит в кэше, отдает совпадение из кэша. Иначе, отдает то, что приходит с сервера.
Метод respondWith, который дословно переводится “Ответь с помощью…”, как раз и отвечает за выдачу данных из кэша или тех, что вернул сервер. А если сервер ничего не вернул, то берем из кэша.

/* Serve cached content when offline */
self.addEventListener(‘fetch’, e => {
e.respondWith(
caches.match(e.request).then(response => {
return response || fetch(e.request);
})
);
});

Этого кода пока достаточно. Давай теперь убедимся что файл sw.js зарегистрирован и кэш записан. Перейди в консоль разработчика, открой вкладку Application и там перейди в настройки Service Workers. Здесь мы видим, что файл sw.js успешно зарегистрирован, это подтверждает зелёная лампочка.

Файл sw.js успешно зарегистрирован.

Продолжаем движение по боковой навигационной панели, находим выпадающий список с названием Cache Storage, где собственно и хранится наш кэш. Если на него нажать, можно увидеть какие файлы и контент были закэшированны.

Файлы и контент закэшированны.

Теперь, если отключить интернет и перезагрузить страницу, сайт будет работать.

Подведём итоги. Для того, чтобы заставить сайт работать при отсутствии интернета, не нужно устанавливать никакие фреймворки, добавлять библиотеки и прочее. Достаточно нескольких строк кода и общего понимания этой технологии.

Как обновлять кэш?

Первая проблема, с которая я столкнулся, разбираясь с PWA, была связана с обновлением старого кэша. Но, как оказалось, это решается очень просто.

Давай поменяем парочку стилей, чтобы было видно что, что-то изменилось. Обновим страницу, убедимся что стили поменялись на странице. Обрубим соединение с интернетом, ещё раз перезагрузим страницу, но почему-то кэш не обновился, и мы видим старую версию сайта.

Решение выглядит следующим образом. В файл sw.js добавляем событие activate, при вызове которого, будем проверять имя старого и нового кэша, и если имена отличаются, то удаляем старый и добавляем новый. Да, чтобы кэш обновлялся, нам нужно менять его название при каждом обновлении кода.

В начале я не зря в имени кэша указал постфикс *_v1, это и будет его версией. На самом деле не важно, как называть, главное, чтобы имена отличались.

self.addEventListener(‘activate’, e => {
// delete any caches that aren’t in cacheName
// which will get rid of version
e.waitUntil(
caches.keys().then(keys => Promise.all(
keys.map(key => {
if (cacheName !== key) {
return caches.delete(key);
}
})
)).then(() => {
console.log(cacheName + ‘ now ready to handle fetches!’);
})
);
});

Если почитать код, можно увидеть условие, где сравниваются имена кэшей, и если они не совпадают, старый кэш удаляется.

Пару слов по поводу события activate. Это событие срабатывает после того, как воркер был зарегистрирован и готов к работе. Но чтобы он был готов, нужно дождаться пока старый кэш перестанет использоваться сайтом, a на это потребуется какое-то время. И чтобы избавится от этого ожидания, можно добавить метод ниже.

self.skipWaiting();

Теперь кэш будет обновляться сразу после регистрации нового воркера. Добавляем его в событие install.

/* Start the service worker and cache all of the app's content */
self.addEventListener('install', e => {
self.skipWaiting(); e.waitUntil(
caches.open(cacheName).then(cache => {
return cache.addAll(includeToCache);
})
);
});

И в файле main.js добавим функцию update, которая при каждой перезагрузке страницы будет запускать обновление кэша.

reg.update();

Добавляем над методом console.log(). На самом деле не важно, главное чтобы в коллбэке .then()

window.addEventListener('load', () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').then(reg => {

reg.update();

console.log('SW registered!');
}).catch(err => console.log('SW registration FAIL:', err));
}
});

Вот и всё, перезагружаем страницу. Снова открываем инструменты разработчика, ставим галочку offline в табе Service Workers на боковой панели, ещё раз перезагружаем страницу и наблюдаем за вкладкой Cache Storage. Там можно будет увидеть, как старый кэш сменится новым.

Хочу предупредить, может понадобиться несколько раз перезагрузить страницу, так как на установку нового воркера тоже требуется время (около 2 мин обычно).

Установка нового воркера.

И после очередной перезагрузки страницы, мы видим наши новые стили и обновлённый кэш, ура!

Новые стили и обновленный кэш.

Заключение

Благодаря данной статье ты, мой дорогой друг, обучился базовым принципам работы с технологией PWA на реальном примере. Не стесняйся оставлять комментарии к статье и делистся собственными примерами о настройке PWA. Всех благ!

Узнать больше о Mad Devs.

--

--