<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Igor</title>
    <description>The latest articles on Forem by Igor (@stokaboka).</description>
    <link>https://forem.com/stokaboka</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F115805%2F3d79aff7-8ef6-46d0-abd7-9f536ffd2b01.jpeg</url>
      <title>Forem: Igor</title>
      <link>https://forem.com/stokaboka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/stokaboka"/>
    <language>en</language>
    <item>
      <title>Progressive Web Application:: Reliable. Part II</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Thu, 22 Nov 2018 05:25:08 +0000</pubDate>
      <link>https://forem.com/stokaboka/progressive-web-applicationreliable-part-ii-c3p</link>
      <guid>https://forem.com/stokaboka/progressive-web-applicationreliable-part-ii-c3p</guid>
      <description>&lt;p&gt;Привет!&lt;/p&gt;

&lt;p&gt;Мы продолжаем работу по разработке PWA и изучаем способы использования service worker (SW).&lt;/p&gt;

&lt;p&gt;В &lt;a href="https://dev.to/stokaboka/progressive-web-applicationreliable-part-i-4phc"&gt;части I&lt;/a&gt; мы:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;написали свой service worker;&lt;/li&gt;
&lt;li&gt;с помощью &lt;a href="https://developers.google.com/web/tools/workbox/" rel="noopener noreferrer"&gt;Workbox&lt;/a&gt; и &lt;a href="https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin" rel="noopener noreferrer"&gt;workbox-webpack-plugin&lt;/a&gt; включили в приложение возможность использовать наш SW.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Регистрация service worker
&lt;/h2&gt;

&lt;p&gt;В части I мы остановились на файле &lt;em&gt;app.js&lt;/em&gt; -  точке входа в приложение, проверили возможность использовать SW и выполнили метод &lt;em&gt;registerServiceWorker&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Рассмотрим файл &lt;a href="https://github.com/stokaboka/pwa01/blob/master/src/reg_sw.js" rel="noopener noreferrer"&gt;reg_sw.js&lt;/a&gt;, в котором описан этот метод.&lt;/p&gt;

&lt;p&gt;Основной метод &lt;em&gt;registerServiceWorker&lt;/em&gt; для регистрации SW :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * регистрация service worker (любого)
 * @param serviceWorker - имя фала js являющегося service worker
 * @returns {Promise&amp;lt;void&amp;gt;}
 */&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;registerServiceWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// регистрируем service worker&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceWorker&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ServiceWorker registered: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// пытаемся подписаться на PUSH уведомления&lt;/span&gt;
        &lt;span class="nf"&gt;subscribeToPushNotifications&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// что-то пошло не так&lt;/span&gt;
        &lt;span class="c1"&gt;// скорее всего отсутствует поддержка service worker&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ServiceWorker failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;При успешной регистрации SW попытаемся подписаться на PUSH уведомления  -  метод &lt;em&gt;subscribeToPushNotifications&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;запросим доступ на получение уведомлений — метод &lt;em&gt;pushStatus&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;попытаемся подписаться на уведомления
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * подписка на PUSH сообщения
 * @param registration
 * @returns {Promise&amp;lt;void&amp;gt;}
 */&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;subscribeToPushNotifications&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// проверяем возможность работать с PUSH сообщениями&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pushManager&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// опциональные параметры для подписки на Push уведомления&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;userVisibleOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;applicationServerKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;urlBase64ToUint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c1"&gt;// проверяем разрешение от пользователя&lt;/span&gt;
        &lt;span class="c1"&gt;// обрабатывать PUSH уведомления&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pushStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// разрешение на уведомления есть&lt;/span&gt;
            &lt;span class="c1"&gt;// пытаемся подписаться&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// подписка на PUSH уведомления&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pushManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Push registration registered&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Push registration failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Получение доступа к PUSH уведомлениям реализовано в методе &lt;em&gt;pushStatus&lt;/em&gt;. Управление уведомлениями производится через интерфейс &lt;em&gt;Notification из Notifications API&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pushStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// смотрим права на получение уведомлений,&lt;/span&gt;
    &lt;span class="c1"&gt;// выставленные пользователем&lt;/span&gt;
    &lt;span class="nx"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestPermission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;push-info&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;granted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// уведомления запрещены&lt;/span&gt;
            &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inactive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Push blocked&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// уведомления разрешены&lt;/span&gt;
            &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Push active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;При регистрации SW и PUSH уведомлений нужно учитывать одну важную деталь. Зарегистрировать SW возможно только в песочнице &lt;em&gt;localhost&lt;/em&gt;, например при работе с &lt;em&gt;webpack-dev-server&lt;/em&gt;. Для работы в production вэб-страница должна быть загружена по протоколу Https и иметь (само собой) сертификат вэб-сервера.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Заключение
&lt;/h2&gt;

&lt;p&gt;Мы зарегистрировали service worker. Этот подход может использоваться для любых SW, независимо от того как они получены — сформированы workbox, написаны нами или использованы готовые.&lt;/p&gt;

&lt;h3&gt;
  
  
  Продолжение следует
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/stokaboka/progressive-web-applicationreliable-part-i-4phc"&gt;Часть I&lt;/a&gt; :&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;пишем свой service worker&lt;/li&gt;
&lt;li&gt;с помощью Workbox и workbox-webpack-plugin включаем в приложение возможность использовать наш service worker&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Часть III:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;жизненный цикл service worker&lt;/li&gt;
&lt;li&gt;стратегии кеширования&lt;/li&gt;
&lt;li&gt;использование workbox-webpack-plugin модуль GenerateSW&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Исходники
&lt;/h3&gt;

&lt;p&gt;Полностью с исходным кодом проекта описанного в статье можно ознакомиться на github по ссылке: &lt;a href="https://github.com/stokaboka/pwa01" rel="noopener noreferrer"&gt;https://github.com/stokaboka/pwa01&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Спасибо за внимание!
&lt;/h3&gt;

&lt;p&gt;Буду рад вашим замечаниям и критике. Эта статья была написана в учебных целях, чтобы разобраться с технологией PWA и Service Worker.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>pwa</category>
      <category>serwiceworker</category>
      <category>workbox</category>
    </item>
    <item>
      <title>Progressive Web Application:: Reliable. Part I</title>
      <dc:creator>Igor</dc:creator>
      <pubDate>Mon, 19 Nov 2018 07:23:13 +0000</pubDate>
      <link>https://forem.com/stokaboka/progressive-web-applicationreliable-part-i-4phc</link>
      <guid>https://forem.com/stokaboka/progressive-web-applicationreliable-part-i-4phc</guid>
      <description>&lt;p&gt;Привет!&lt;/p&gt;

&lt;p&gt;Progressive Web Application (далее PWA) возможно станут новой альтернативой устанавливаемым приложениям разработанным для определенной платформы.&lt;/p&gt;

&lt;p&gt;На ресурсе &lt;a href="https://developers.google.com/web/progressive-web-apps/" rel="noopener noreferrer"&gt;developers.google.com&lt;/a&gt; приведено следующее определение PWA:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reliable &lt;/strong&gt;  Load instantly and never show the downasaur, even in uncertain network conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast- Respond&lt;/strong&gt; quickly to user interactions with silky smooth animations and no janky scrolling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Egaging- Feel&lt;/strong&gt; like a natural app on the device, with an immersive user experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Пока все выглядит довольно заманчиво:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Один код для разных платформ&lt;/li&gt;
&lt;li&gt;Обозреватель интернета в качестве виртуальной машины, который есть практически везде&lt;/li&gt;
&lt;li&gt;Богатые возможности для создания UI&lt;/li&gt;
&lt;li&gt;Возможность использовать приложение без доступа к глобальной сети&lt;/li&gt;
&lt;li&gt;На устройстве PWA “выглядит” как нативное приложение&lt;/li&gt;
&lt;li&gt;И если с браузерами и UI все понятно: фактически мы делаем одно-страничное приложение, то что делать с оффлайн режимом и установкой на устройство?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Выбрав пару свободных дней я решил заполнить это пробел в своих знаниях и взялся за разработку простейшего PWA приложения. Мне нужно было понять основы, чтобы потом использовать технологию на реальном приложении.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Основная цель - разобраться, что скрывается под понятием Reliable,  быстрая загрузка приложения и данных, возможность работать в отсутствие сети.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Поиск привел на следующие ресурсы:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/web/progressive-web-apps/" rel="noopener noreferrer"&gt;https://developers.google.com/web/progressive-web-apps/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://developers.google.com/web/tools/workbox/" rel="noopener noreferrer"&gt;https://developers.google.com/web/tools/workbox/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://blog.logrocket.com/building-a-progressive-web-app-pwa-no-react-no-angular-no-vue-aefdded3b5e" rel="noopener noreferrer"&gt;https://blog.logrocket.com/building-a-progressive-web-app-pwa-no-react-no-angular-no-vue-aefdded3b5e&lt;/a&gt;&lt;br&gt;
&lt;a href="https://vaadin.com/pwa/build/production-pwa-with-webpack-and-workbox" rel="noopener noreferrer"&gt;https://vaadin.com/pwa/build/production-pwa-with-webpack-and-workbox&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;В сети уже имеется огромное множество статей на тему PWA, каждый может найти то, что больше подходит именно ему.&lt;/p&gt;
&lt;h2&gt;
  
  
  Итак, начнем!
&lt;/h2&gt;

&lt;p&gt;Основное отличие PWA от Single Page Application (SPA) это установка на устройство, работа без доступа к сети или с частой потерей соединения и загрузка из кеша файлов приложения и данных.&lt;/p&gt;

&lt;p&gt;Это возможно при использовании ServiceWorker (далее SW)  -  это JavaScript (далее JS) код, выполняющийся в отдельном потоке от потока страницы и выполняющий роль посредника (proxy) между вэб-приложением и сетью.&lt;/p&gt;

&lt;p&gt;На данный момент SW поддерживает&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Push Notifications&lt;/li&gt;
&lt;li&gt;Background Sync&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Но на самом деле кеширование файлов в обозревателе с помощью SW вовсе не значит, что обязательно нужно делать именно PWA приложение.&lt;/p&gt;

&lt;p&gt;Мы можем использовать возможности кеширования файлов с помощью SW для локального хранения шрифтов, изображений и других компонентов web-приложения или web-страницы для ускорения загрузки.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Постановка задачи
&lt;/h3&gt;

&lt;p&gt;Разработать простейшее PWA приложение и разработать простой service worker обеспечивающий online и offline работу нашего приложения.&lt;/p&gt;
&lt;h3&gt;
  
  
  Решение
&lt;/h3&gt;

&lt;p&gt;Наше приложение будет простым, но подходящим для дальнейшего развития в более сложное решение.&lt;/p&gt;

&lt;p&gt;Для этого мы будем использовать:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/web/tools/workbox/" rel="noopener noreferrer"&gt;Google Workbox&lt;/a&gt;, JS библиотека для добавления автономной поддержки вэб-приложений&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webpack.js.org/" rel="noopener noreferrer"&gt;Webpack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin" rel="noopener noreferrer"&gt;workbox-webpack-plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Надо отметить, что использование workbox-webpack-plugin очень сильно упрощает нашу задачу. В простейшем случае, все сводится к настройке workbox-webpack-plugin.&lt;/p&gt;

&lt;p&gt;Workbox-webpack-plugin предоставляет &lt;a href="https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin" rel="noopener noreferrer"&gt;два способа&lt;/a&gt; внедрения в проект SW:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GenerateSW &lt;/strong&gt; -  генерирует файл SW на основе минимальных настроек workbox-webpack-plugin. Простая интеграция SW в приложение. Но надо отметить, что за простотой базовых настроек по умолчанию кроется мощная система конфигурации SW, но это отдельная тема&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;InjectManifest&lt;/strong&gt; -  свой или сторонний service worker, полный контроль над процессом настройками&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Мы будем использовать оба варианта: GenerateSW используем мощь Workbox servce worker через настройки workbox-webpack-plugin; InjectManifest -  напишем свой простейший SW для понимания как это работает.&lt;/p&gt;
&lt;h3&gt;
  
  
  Использование InjectManifest
&lt;/h3&gt;

&lt;p&gt;(Создадим свой SW (sw.js).)[&lt;a href="https://github.com/stokaboka/pwa01/blob/master/src/sw.js" rel="noopener noreferrer"&gt;https://github.com/stokaboka/pwa01/blob/master/src/sw.js&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;Мы будем использовать стратегию &lt;em&gt;Network First&lt;/em&gt; -  запросы отправляются в сеть, если сеть недоступна пытаемся получить данные для запроса из кеша, если в кеше нет данных -  возвращаем данные-заглушки.&lt;/p&gt;

&lt;p&gt;Создаем вспомогательные переменные:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ссылка на API для проверки сетевой запрос или нет (другими словами: что мы загружаем данные или компоненты приложения):
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://jsonplaceholder.typicode.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;массив с перечислением файлов, которые необходимо сохранять в кеше. Содержимое этих файлов сохраняется в кеше при первой загрузке приложения и будет использовано обозревателем для последующих загрузок приложения (пока не истечет срок жизни кеша) и для получения данных при отсутствии сети и пустого кеша:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./style.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./fallback/posts.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./fallback/users.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;вспомогательный метод для решения сетевой запрос (обращение к API) или нет
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isApiCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;При первой загрузке приложения происходит процесс установки и инициализация кеша. Мы перехватываем событие “install”, инициализируем кеш с тегом “files” и сохраняем нужные нам файлы в кеше для последующего использования:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;self.addEventListener('install', async e =&amp;gt; {
    // получаем ссылку на кеш
    const cache = await caches.open('files');
    // сохраняем в кеше файлы приложения и заглушки для данных
    cache.addAll(files);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Создаем основные методы для обработки запросов нашего приложения, их будет три:&lt;/p&gt;

&lt;p&gt;1) запрос в сеть, метод getFromNetwork&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function getFromNetwork(req) {
    // ссылка на кеш с тэгом "data"
    const cache = await caches.open('data');

    try {
        // выполняем запрос в сеть
        const res = await fetch(req);
        // сохраняем результат в кеш
        cache.put(req, res.clone());
        return res;
    } catch (e) {
        // упс, что-то пошло не так, сеть не работает!!!
        // извлекаем результат запроса из кеша
        const res = await cache.match(req);
        // возвращаем результат запроса если он найден в кеше
        // возвращаем данные-заглушки 
        // если в кеше нет результатов запроса
        return res || getFallback(req);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) поиск результата запроса в кеше, метод getFromCache&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function getFromCache(req) {
    // запрос в кеш
    const res = await caches.match(req);

    if (!res) {
        // в кеше нет данных для запроса
        // отправляем запрос в сеть
        // return fetch(req);
        return getFromNetwork(req)
    }

    return res;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) данные-заглушки на случай отсутствия сети и отсутствия данных в кеше, метод getFallback. Эти файлы были сохранены в кеше при первой загрузке приложения&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function getFallback(req) {
    const path = req.url.substr(apiUrl.length);

    if (path.startsWith('/posts')) {
        return caches.match('./fallback/posts.json');
    } else if (path.startsWith('/users')) {
        return caches.match('./fallback/users.json');
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;И теперь самый главный метод нашего &lt;em&gt;service worker&lt;/em&gt; -  перехват сетевых запросов (вызовы метода fetch обозревателя) из нашего web-приложения:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;self.addEventListener('fetch', async e =&amp;gt; {
    // извлекаем запрос из события
    const req = e.request;
    // запрос соответствует нашему api url - обращаемся в сеть
    // прочие запросы (html, css, js, json и любые другие файлы)
    // - пытаемся получить результаты из кеша
    // эти файлы являются частями нашего приложения и
    // сохраняются при первой загрузке
    const res = isApiCall(req) ?
        getFromNetwork(req) : getFromCache(req);
    // подсовываем событию "fetch" результат сформированный нами
    // в вызовах getFromNetwork или getFromCache
    // этот результат будет использован в нашем приложении
    await e.respondWith(res);
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Мы написали свой первый service worker для PWA приложения!!!&lt;/p&gt;

&lt;p&gt;Полный код файла можно посмотреть по ссылке: &lt;a href="https://github.com/stokaboka/pwa01/blob/master/src/sw.js" rel="noopener noreferrer"&gt;https://github.com/stokaboka/pwa01/blob/master/src/sw.js&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Настройка &lt;em&gt;workbox-webpack-plugin&lt;/em&gt; в режиме &lt;em&gt;InjectManifest&lt;/em&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Использование пользовательского или стороннего service worker.
&lt;/h3&gt;

&lt;p&gt;В нашем случае service worker  -  это наш файл sw.js&lt;/p&gt;

&lt;p&gt;Файл конфигурации webpack &lt;a href="https://github.com/stokaboka/pwa01/blob/master/build-utils/presets/webpack.manifest.js" rel="noopener noreferrer"&gt;webpack.manifest.js&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// загружаем workbox-webpack-plugin
const {InjectManifest} = require('workbox-webpack-plugin');

// настраиваем workbox-webpack-plugin в режиме InjectManifest
module.exports = () =&amp;gt; ({
    plugins: [
        new InjectManifest({
            // файл service worker написанный нами
            swSrc: './src/sw.js',
            // под именем service-worker.js наш файл попадет в сборку
            swDest: 'service-worker.js'
        })
    ]
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Файл для подключения модуля workbox-webpack-plugin и его настройке в режиме &lt;strong&gt;InjectManifest&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://github.com/stokaboka/pwa01/blob/master/build-utils/presets/webpack.manifest.js" rel="noopener noreferrer"&gt;https://github.com/stokaboka/pwa01/blob/master/build-utils/presets/webpack.manifest.js&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Этот файл  -  часть конфигурации webpack которая относится к настройке workbox-webpack-plugin и service worker.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Собственно webpack.config.js доступен по ссылке: &lt;a href="https://github.com/stokaboka/pwa01/blob/master/webpack.config.js" rel="noopener noreferrer"&gt;https://github.com/stokaboka/pwa01/blob/master/webpack.config.js&lt;/a&gt;&lt;br&gt;
Но здесь мы рассматриваем только вопросы связанные с использованием SW.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Использование SW в web-приложении
&lt;/h3&gt;

&lt;p&gt;Результатом работы &lt;em&gt;workbox-webpack-plugin&lt;/em&gt; будет два сформированных плагином файла:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;service-worker.js, копия нашего файла sw.js с импортом файла манифеста ( precache-manifest…. ) и пакета workbox-sw.js&lt;/li&gt;
&lt;li&gt;precache-manifest.########################.js, файл манифеста, где ######################## случайный набор символов&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Эти файлы копируются в папку сборки. Остается только подключить service worker в наше PWA приложение.&lt;/p&gt;

&lt;p&gt;Создаем главный файл приложения (точку входа) app.js. Здесь мы должны:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;загрузить модуль с методами для регистрации service worker;&lt;/li&gt;
&lt;li&gt;проверить возможность использовать service worker обозревателем
по событию “load” регистрируем service worker вызовом метода &lt;em&gt;registerServiceWorker&lt;/em&gt; в модуле &lt;em&gt;reg_sw.js&lt;/em&gt;;&lt;/li&gt;
&lt;li&gt;загрузить данные PWA приложения и отобразить эти данные вызовом метода &lt;em&gt;loadMoreEntries&lt;/em&gt; в модуле &lt;em&gt;api.js&lt;/em&gt; (это относится к работе приложения и подробно не рассматривается);
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// загружаем модуль с методами регистрации service worker
import { registerServiceWorker } from './reg_sw'
// методы загрузки данных
import { loadMoreEntries } from "./api";

// имя файла service worker
// этот файл сформирован на основе 
// нашего sw.js workbox-webpack-plugin - ом
// имя файла задано в файле настройке webpack.manifest.js
let serviceWorkerName = '/service-worker.js';

// проверяем возможность обозревателем использовать service worker
if ('serviceWorker' in navigator) {
    // ловим событие "load" - окончание загрузки всех компонентов
    window.addEventListener('load', async () =&amp;gt; {
        // регистрируем service worker
        await registerServiceWorker(serviceWorkerName);
        // загружаем данные для работы PWA приложения
        loadMoreEntries();
    });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Обработаем событие &lt;em&gt;beforeinstallprompt&lt;/em&gt; предложение обозревателем установить PWA:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// ловим событие "beforeinstallprompt" - предложение установить
// PWA приложение в системе (при возможности)
// установка возможна только при загрузке по HTTPS
// с сертификатом web-сервера или при работе из песочницы localhost
window.addEventListener('beforeinstallprompt', e =&amp;gt; {
    e.preventDefault();
    e.prompt();
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Заключение.
&lt;/h2&gt;

&lt;p&gt;Итак, мы разработали тестовое PWA с использованием: (Workbox)[&lt;a href="https://developers.google.com/web/tools/workbox/" rel="noopener noreferrer"&gt;https://developers.google.com/web/tools/workbox/&lt;/a&gt;], (workbox-webpack-plugin)[&lt;a href="https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin" rel="noopener noreferrer"&gt;https://developers.google.com/web/tools/workbox/modules/workbox-webpack-plugin&lt;/a&gt;]. Мы разработали свой собственный service worker с простейшей логикой, способный кешировать файлы приложения и загруженных данных. Для использования нашего &lt;em&gt;service worker&lt;/em&gt; мы использовали режим &lt;em&gt;InjectManifest&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;При отключении сети наше приложение продолжает работать, отображаются данные, которые ранее загружались при работе в сети. Для запросов данных отсутствующих в кеше применяются данные-заглушки. Это позволяет приложению продолжать работать без доступа к сети.&lt;/p&gt;

&lt;h3&gt;
  
  
  Продолжение следует:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dev.to/stokaboka/progressive-web-applicationreliable-part-ii-c3p"&gt;Часть II&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
регистрируем servece worker&lt;br&gt;
подписываемся на PUSH уведомления&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Часть III:&lt;/strong&gt;&lt;br&gt;
жизненный цикл service worker&lt;br&gt;
стратегии кеширования&lt;br&gt;
использование workbox-webpack-plugin модуль GenerateSW&lt;/p&gt;

&lt;h3&gt;
  
  
  Исходники
&lt;/h3&gt;

&lt;p&gt;Полностью с исходным кодом проекта описанного в статье можно ознакомиться на github по ссылке: &lt;a href="https://github.com/stokaboka/pwa01" rel="noopener noreferrer"&gt;https://github.com/stokaboka/pwa01&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Спасибо за внимание!&lt;/p&gt;

&lt;p&gt;Буду рад вашим замечаниям и критике. Эта статья была написана в учебных целях, чтобы разобраться с технологией PWA и Service Worker.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>pwa</category>
      <category>serwiceworker</category>
      <category>workbox</category>
    </item>
  </channel>
</rss>
