PWA — это просто. Hello Habr

от автора

Продолжаем знакомство с Progressive Web Applications. После теоретической прошлой части самое время перейти к практике.

Сегодня мы построим простое, но полноценное PWA «Hello Habr».

Приложение доступно по адресу https://altrusl.github.io/habr-pwa/hello-habr/. При открытии в браузере на мобильном устройстве возможно добавление ярлыка на домашний экран и запуск в полноэкранном режиме.

Если кто хочет попробовать рассматриваемый пример на своем компьютере, то Chrome позволяет работать локально с простыми PWA приложениями без установки сторонних веб серверов с SSL сертификатами.

Инструкция для запуска «Hello Habr» локально

Необходимо установить из интернет-магазина Chrome вот это или аналогичное расширение, являющееся локальным веб-сервером. Без поддержки PHP, естественно.

Файлы «Hello Habr» можно взять с GitHub-a — https://github.com/altrusl/habr-pwa/tree/master/hello-habr

Поместите всё в одну директорию и укажите ее веб серверу.

«Hello Habr» состоит из одной страницы. Он показывает на ней картинку (лого) и анимированную надпись.

«Hello Habr» code

index.html

<html>     <head>         <title>Hello Habr</title>         <script src="hh.js"></script>         <link rel="stylesheet" href="hh.css" />         <script type="text/javascript">             if ('serviceWorker' in navigator) {                 navigator.serviceWorker.register('/sw.js')                 .then(function(registration) {                     console.log('Registration successful, scope is:', registration.scope);                 })                 .catch(function(error) {                     console.log('Service worker registration failed, error:', error);                 });             }         </script>     </head>     <body>         <div class="center">             <p id="text"></p>         </div>         <div id="logo"></div>     </body> </html>

hh.css

@font-face {     font-family: Zaplyv-Heavy;     src: url(Zaplyv-Heavy.otf);    }     body {     display: flex;     align-items: center;     align-content: center;      justify-content: center;      overflow: auto;    }  .center {     font-family: Zaplyv-Heavy;     font-size: 8vmax; }  #logo {     background-image: url(logo.jpg);     background-size: 100%;     width: 100px;     height: 100px;     position: absolute;     top: 0;     right: 0;     margin: 10px; }

hh.js

window.onload = function() {     fetch("hh.txt?mode=nocache").then(data => data.text()).then(data => {         animateText(data)       }); }  function animateText(data) {     var ele = document.getElementById("text"),         txt = data.split("");     var interval = setInterval(function(){     if(!txt[0]){         return clearInterval(interval);     };     ele.innerHTML += txt.shift();     }, 150); }

hh.txt

Hello Hubr

Также присутствует кастомный шрифт. Итого — минимальный полный набор ресурсов среднего веб сайта. Если открыть index.html в браузере, отобразится картинка и надпись. Надпись загружается javascript-ом через fetch из файла hh.txt — простейшая модель общего PWA приложения.

Если открывать без sw.js, то это будет обычный веб сайт. Добавим к нашим файлам Service Worker.

sw.js

// Caches var CURRENT_CACHES = {     font: 'font-cache-v1',     css:'css-cache-v1',     js:'js-cache-v1',     site: 'site-cache-v1',     image: 'image-cache-v1' };  self.addEventListener('install', (event) => {   self.skipWaiting();     console.log('Service Worker has been installed'); });  self.addEventListener('activate', (event) => {     var expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {         return CURRENT_CACHES[key];     });        // Delete out of date caches     event.waitUntil(         caches.keys().then(function(cacheNames) {             return Promise.all(                 cacheNames.map(function(cacheName) {                     if (expectedCacheNames.indexOf(cacheName) == -1) {                         console.log('Deleting out of date cache:', cacheName);                         return caches.delete(cacheName);                     }                 })             );         })     );      console.log('Service Worker has been activated');   });  self.addEventListener('fetch', function(event) {   console.log('Fetching:', event.request.url);     event.respondWith(async function() {     const cachedResponse = await caches.match(event.request);     if (cachedResponse) {       console.log("\tCached version found: " + event.request.url);       return cachedResponse;     } else {               console.log("\tGetting from the Internet:" + event.request.url);       return await fetchAndCache(event.request);     }   }());  });  function fetchAndCache(request) {    return fetch(request)   .then(function(response) {     // Check if we received a valid response     if (!response.ok) {       return response;       // throw Error(response.statusText);     }          var url = new URL(request.url);     if (response.status < 400 &&       response.type === 'basic' &&       url.search.indexOf("mode=nocache") == -1        ) {       var cur_cache;       if (response.headers.get('content-type') &&          response.headers.get('content-type').indexOf("application/javascript") >= 0) {         cur_cache = CURRENT_CACHES.js;       } else if (response.headers.get('content-type') &&              response.headers.get('content-type').indexOf("text/css") >= 0) {         cur_cache = CURRENT_CACHES.css;       } else if (response.headers.get('content-type') &&              response.headers.get('content-type').indexOf("font") >= 0) {         cur_cache = CURRENT_CACHES.font;       } else if (response.headers.get('content-type') &&              response.headers.get('content-type').indexOf("image") >= 0) {         cur_cache = CURRENT_CACHES.image;       } else if (response.headers.get('content-type') &&              response.headers.get('content-type').indexOf("text") >= 0) {         cur_cache = CURRENT_CACHES.site;       }       if (cur_cache) {         console.log('\tCaching the response to', request.url);         return caches.open(cur_cache).then(function(cache) {           cache.put(request, response.clone());           return response;         });       }     }     return response;   })   .catch(function(error) {     console.log('Request failed for: ' + request.url, error);     throw error;   }); }

Как видно, мы создаем пять кэшей для каждого вида ресурсов. Кэш site — для html файлов. Кэшируются все ресурсы, за исключением тех, у кого в GET query стоит «mode=nocache» — а это у нас запрос к файлу hh.txt со строкой для надписи.

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

.htaccess

# Cache-Control Headers <ifModule mod_headers.c>   <FilesMatch (\.css|\.js|sprites\.png)$> 	Header unset ETag 	Header unset Expires 	Header set Cache-Control "no-cache"   </FilesMatch> </IfModule>

Логика работы sw.js простая — «Cache falling back to the network». Сперва запрашиваемый ресурс проверяется в кэше, если он там есть, то берется и возвращается браузеру оттуда. Если нет — получается из сети, возвращается браузеру, а копия ресурса помещается в кэш.

После первого открытия страницы index.html в консоли Chrom-a видны записи об установке и активации Service Worker-а. После второго открытия в хранилище создаются наши кэши и в них помещаются наши ресурсы. Также видно, что при последующих открытиях на веб сервер уходят только запросы к hh.txt, все остальные ресурсы берутся из Service Worker-a.

Скриншот

Хранящиеся локально index.html, hh.css, hh.js, hh.otf, logo.jpg — это и есть тот самый application shell, оболочка статичных ресурсов и данных, выполняющая роль оболочки программы на клиенте. Вся динамическая информация, необходимая для работы сайта, получается javascript запросами на сервер и отображением полученных данных в app shell-e. В нашем случае это запрос к text.txt.

Для того, чтобы называться функционально полноценным PWA, «Hello Habr» не хватает одного — иконки на домашнем экране смартфонов и запуска в полноэкранном режиме.

Для этого необходимо в index.html подключить манифест приложения:

manifest.json

{   "short_name": "Hello Habr",   "name": "Hello Habr - PWA example",   "icons": [     {       "src": "logo3.jpg",       "type": "image/jpg",       "sizes": "192x192"     },     {       "src": "logo2.jpg",       "type": "image/jpg",       "sizes": "512x512"     }   ],   "start_url": "index.html",   "background_color": "#3367D6",   "display": "standalone",   "scope": "/habr-pwa/hello-habr/",   "theme_color": "#3367D6" }

Подключается он в index.html:

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

После этого мобильные браузеры (каждый по-своему) предложат создать ярлык для приложения на домашнем экране. При запуске по ярлыку приложение будет открываться в standalone режиме — без браузерных элементов управления. Более подробней об опциях манифеста — на Google Developers.

Приложение «Hello Habr» в минимальной мере обладает всеми свойствами PWA и является им по сути. Как видно, для того, перевести простой сайт в PWA нужно просто подключить манифест и файл Service Worker-a. Используемый sw.js достаточно универсальный.

В следующий раз переведем в PWA готовый сайт на CMS Joomla (сайт «из коробки» с изначальными демо-данными). Причем, sw.js останется практически тем же.


ссылка на оригинал статьи https://habr.com/post/419585/


Комментарии

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

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