PWA (прогрессивные веб-приложения) — это горячая вещь для веб-разработки в 2018 году, поскольку все больше и больше платформ поддерживают базовые API, такие как Service Workers или Push-уведомления.

Если вы начинаете новый существующий проект веб-приложения, вполне вероятно, что вы создаете PWA. Но что с устаревшими приложениями? С теми, для которых не предвидится рефакторинг или бюджет (или время, или…)?

Самая популярная функция — это кеширование и возможность офлайн с помощью Service Workers.

Наиболее важные функции PWA: Кэширование/офлайн (более 80%). 66 % считают, что Добавить на главный экран или запрос на установку приложения являются обязательными в PWA, тогда как 60 % считают, что push-уведомления являются ключевыми для создания PWA.
https://medium.com/progressive-web-apps/ 2018-состояние прогрессивных веб-приложений-f7517d43ba70

Эта статья покажет вам, как реализовать эти две основные функции в реальном веб-приложении.

Трансформация

Поскольку я сейчас разрабатываю новое веб-приложение, в котором у меня есть возможность интегрировать Service Workers, Web Workers и все причудливые PWA-материалы в основную архитектуру приложения, мне было любопытно, могу ли я добавить Service Worker в один из моих существующих проектов. для быстрой победы и быстрого преобразования в PWA с незначительными настройками или даже без них.

Предпосылки

  • Знание JavaScript
  • представление о том, что такое PWA и особенно Service Workers
  • и устаревшее веб-приложение;)

HTTPS

Прежде чем мы сможем начать, позвольте мне добавить еще одно предварительное условие для разработки PWA. Для некоторых веб-разработчиков барьер для входа в квест Service Worker может быть высоким, потому что запросы к серверу должны доставляться через HTTPS. Это означает, что вам нужны SSL-сертификаты и для вашей локальной среды разработки.

Я использую Nginx в Ubuntu с самоподписанными сертификатами (https://ram.k0a1a.net/self-signed_https_cert_after_chrome_58) в качестве платформы для разработки.
Поскольку политики браузера время от времени становятся более строгими, будет сложно поддерживать рабочую настройку.
Если у вас есть рабочие примеры для других платформ, поделитесь ими в комментариях ниже.

Совет. Если это применимо к вашей конфигурации, Service Workers можно обслуживать через HTTP с localhost в качестве имени домена.

Манифест

У меня есть еще один. Если на вашем сайте его еще нет, я рекомендую вам создать файл веб-манифеста. Вместе с Service Workers вы получаете бесплатную функцию Добавить на главный экран.
Хорошей отправной точкой является PWABuilder (https://www.pwabuilder.com/) где вы можете создать файл веб-манифеста, а также код JavaScript для различных сценариев Service Worker. Он также проверяет требования для различных целевых платформ, например требуемые размеры значков.

Действие

А теперь приступим!

Инициализировать

Первым шагом является инициализация Service Worker в основном файле JavaScript.
В качестве альтернативы инициализация может также выполняться в HTML-разметке на целевой странице в разделе ‹script›‹/script›.

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('//' + window.location.hostname + '/servicewoker.js', { scope: '/' }).catch(function (error) { console.log('service worker registration failed: ' + error); });
    navigator.serviceWorker.ready.then(function () { return console.log('service worker is ready'); });
}

Ваш файл serviceworker.js должен находиться в корневом каталоге вашего домена, если вы хотите охватить весь сайт (он же область действия).

сервисворкер.js

Я буду использовать стандартную нотацию ECMAScript (также известную как JavaScript). Если вы используете, например, TypeScript, ваша IDE будет рада преобразовать код для вас.

Мы начнем с инициализации строки кэша для управления версиями и возможности удаления старого кэшированного контента.

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

var CACHE = '20180531';
var cacheFiles = [
    '/',
    '/js/main.js',
    '/css/main.css',
    '/image/logo.png',
    '/offline.html',
    '/error.html'
].map(function (path) { return "" + path; });

Жизненный цикл

Жизненный цикл Service Worker включает в себя события «установка», «активация» и «выборка». Мы реализуем прослушиватели событий для этих трех.

Установить

Первые два события не очень интересны. Событие «установить» кэширует данные активы.

self.addEventListener('install', function (event) {
    event.waitUntil(caches.open("" + CACHE).then(function (cache) {
        return cache.addAll(cacheFiles);
    }).then(function () { return self.skipWaiting(); }));
});

Активировать

Немного домашнего хозяйства. В событии активировать существующее старое содержимое кеша удаляется. Остерегайтесь квоты (https://medium.com/dev-channel/offline-storage-for-progressive-web-apps-70d52695513c)!

self.addEventListener('activate', function (event) {
    event.waitUntil(caches.keys().then(function (cacheKeys) {
        var oldCacheKeys = cacheKeys.filter(function (key) {
            return (key.indexOf(CACHE) !== 0);
        });
        var deletePromises = oldCacheKeys.map(function (oldKey) {
            return caches.delete(oldKey);
        });
        return Promise.all(deletePromises);
    }).then(function () { return self.clients.claim(); }));
});

Стратегия кэширования

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

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

Я стремился всегда обслуживать статический контент из кеша, определенные запросы всегда из сети с резервным кешем, а другие из кеша или сети (если они еще не кэшированы).

Принести

Магия начинается здесь. Слушатель событий «fetch» ​​направляет каждый сетевой запрос Service Worker.

Часть I
Сначала определяются запросы (или их части), которые всегда следует пытаться получить из сети (например, запрос к URL-адресу https://example.com/live/request/get/new/emails ).

Поскольку, как уже упоминалось, каждый сетевой запрос будет обрабатываться этим прослушивателем событий, также все внешние запросы к CDN или рекламным сетям потенциально будут кэшироваться. Чтобы предотвратить это, я указал только домены в массиве, который обслуживает приложение.

self.addEventListener('fetch', function(event) {
    var exclude = false;
    var excludes = ['/live/request', '/important/request'];
    var domain = false;
    var domains = ['https://example.com', 'https://example.org'];

    excludes.forEach(function (item) {
        if (event.request.url.indexOf(item) > -1) {
            exclude = true;
        }
    });
    domains.forEach(function (item) {
        if (event.request.url.indexOf(item) === 0) {
            domain = true;
        }
    });

Часть II
Если правило исключения не применяется и запись в кэше существует, будет возвращен кешированный ответ.
В случае запроса по сети ответ будет возвращен и кэшируется только в том случае, если домен совпадает с определенным, статус ответа HTTP соответствует «200» (ОК) и метод запроса относится к типу «GET».
Если возникает ошибка, будет возвращена страница ошибки (или необработанный ответ от других доменов, например, отсутствующий ресурс изображения из рекламной сети).

event.respondWith(
    caches.match(event.request).then(function(cache) {
        if (!exclude && cache) {
            return cache;
        }
        return fetch(event.request).then(function(response) {
            if (domain && response.status >= 400) {
                return caches.match('/error.html');
            }
            return caches.open("" + CACHE).then(function (cache) {
                if (domain && response.status === 200 &&
                    event.request.method === 'GET') {
                    cache.put(event.request, response.clone());
                }
                return response;
            });
        });

Часть III
В последней части прослушивателя 'fetch' будет перехвачена возникшая ошибка (скорее всего, если устройство находится в автономном режиме).
Пользователю может быть представлен оффлайн сообщение. Вместо этого может быть возвращен ответ JSON, если он лучше соответствует потребностям веб-приложения.

}).catch(function(error) {
          return caches.match(event.request).then(function (value) {
                if (domain && value === undefined) {
                    return caches.match('/offline.html');
                }
                return value;
            });
        })
    );
});

(serviceworker.js из этого примера: https://gist.github.com/timog/e287d417da995932da7271f6a3fa24f1)

Вот и все! На сцену выходит (простой) новый PWA.

Возможность работы в автономном режиме

После начальной загрузки страницы большинство ресурсов теперь обслуживаются Service Worker без сетевого запроса. Это обеспечивает автономные возможности веб-приложения и обеспечивает гораздо лучший пользовательский интерфейс, чем «офлайн-страница ошибок».

Подводные камни

На момент написания отладка могла быть грубой по краям. Мне пришлось несколько раз очистить кеш и перезапустить браузер (Chrome), потому что DevTools не делал того, что я хотел.

Признаюсь, кое-что мне пришлось изменить. Но это было не совсем изменение кода. Статические ресурсы, такие как файлы .js и .css, обслуживались из дополнительного поддомена (например, https://static.example.org/css/main.css). Поэтому было невозможно предварительно кэшировать эти активы.
Но поскольку мне нравится менять архитектуру своего приложения после некоторых колебаний, я избавился от поддомена.

Совместимость

В настоящее время все основные браузеры поддерживают Service Workers.
https://caniuse.com/#feat=serviceworkers

Также настольные операционные системы, такие как Chrome OS, macOS и Windows 10, будут поддерживать PWA.

Если используется неподдерживаемая платформа, устаревшее веб-приложение будет работать точно так же, как и раньше.

TODO

Есть много других функций, которые могут быть реализованы с этого момента, например, улучшенная работа в автономном режиме (кеширование отправленных запросов и их отправка при повторном подключении к сети), push-уведомления и т. д. Вам нужно решить, стоит ли погружаться глубже в океан PWA для ваш проект.

И, очевидно, улучшить качество кода.

Заключительные слова

Я надеюсь, что смогу направить того или другого на правильный путь, чтобы превратить устаревшее веб-приложение в PWA.

Спасибо за чтение и не забудьте проверить связанные ресурсы в этой статье, а также ниже.

рекомендуется дальнейшее чтение

Использование сервис-воркеров
https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers

Кэширование файлов с помощью Service Worker
https://developers.google.com/web/ilt/pwa/caching-files-with-service-worker

Поваренная книга сервис-воркера
https://serviceworke.rs/

/* продолжайте любопытствовать */