Максим Сальников

@webmaxru

Сервис-воркеры:

используем накопленные знания для светлого будущего PWA

Что мы узнали за 3 года использования PWA?

И как жить дальше

Максим Сальников

@webmaxru

  • Google Dev Expert, Microsoft MVP

  • Организатор Mobile / Web / PWA митапов в Осло и Лондоне

  • Организатор конференций Mobile Era и ngVikings

Full-stack разработчик "приложений из будущего" в ForgeRock

#YearOfPWA

Что новенького?

Еще одно определение PWA

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

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

Кросс-платформенные?

Браузеры

Настольные

Мобильные

Магазины?

Для разработчиков

Онлайн больше не обязателен

Масса других возможностей

Получение уведомлений

Service Worker API

Web App Manifest

Новые преимущества?

Полноценное приложение

}

Сервис-воркер

Двигатель PWA

Логически

Физически

JS

-файл

Приложение

Сервис-воркер

Инструментарий

  • Service Worker API

  • Cache API

  • IndexedDB

  • Fetch

  • Clients API

  • Broadcast Channel API

  • Push API

  • Notifications API

  • Local Storage

  • Session Storage

  • XMLHttpRequest

  • DOM

Непростой жизненный цикл

'install'

Загрузка

Установка

Активация

Удален

'activate'

Ожидание

Активен

Прогрессивные веб-приложения

стремящийся к прогрессу, проникнутый передовыми идеями

постепенно возрастающий, увеличивающийся

Прогрессивное улучшение

Определяйте наличие функциональности

Совет #1

Регистрация

if ('serviceWorker' in navigator) {

    // Регистрируем сервис-воркер

}

Фоновая синхронизация

if ('SyncManager' in window) {

    // Реализуем функциональность для оффлайн-режима

}

Подписка на push

if (!('PushManager' in window)) {

    // Прячем интерфейс подписки на push-уведомления

}

Действия в уведомлениях

if ('actions' in Notification.prototype) {

  // Можем использовать кнопки с разными действиями

}

Правильный момент для регистрации

Чем позже, тем лучше

Совет #2

if ('serviceWorker' in navigator) {

        navigator.serviceWorker.register('/sw-workbox.js')
            .then(...);

}
if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/sw-workbox.js')
            .then(...);
    )};
}
platformBrowserDynamic()
  .bootstrapModule(AppModule)
  .then(() => {

    // Регистрация сервис-воркера

  });

main.ts

Если что-то пошло не так

Нужен kill switch

Совет #3

  • Разрегистрировать сервис-воркер?

  • Разместить исправленный сервис-воркер (или    no-op)

  • Удостовериться, что сервис-воркер не берется из кеша HTTP

План спасения

No-op

self.addEventListener('install', () => {
  self.skipWaiting();
});
self.addEventListener('activate', () => {
  self.clients.matchAll({type: 'window'}).then(tabs => {
    tabs.forEach(tab => {
      tab.navigate(tab.url);
    });
  });
});

Крайняя мера для UX

Обновление и обход кеша HTTP

Cache-Control: no-cache

Ресурсы, импортированные через importScripts() 

Сервис-воркер

Спецификация обновлена

Побайтное сравнение, нужно использовать версионность

navigator.serviceWorker.register(`/sw.js?v=${VERSION}`);

Контент не проверяется, нужно менять название ресурса

updateViaCache

index.html

navigator.serviceWorker.register('/sw.js', {
  updateViaCache: 'none'
})

Значения: "imports", "all" или "none"

Предварительное кеширование

Знайте ваши ресурсы и не оставляйте мусор

Совет #4

const appShellFilesToCache = [
  ...
  './non-existing.html'
]

sw-handmade.js

self.addEventListener('install', (event) => {

  event.waitUntil(
    caches.open(cacheName).then((cache) => {
      return cache.addAll(appShellFilesToCache)
    })
  )

})
  • Ошибки HTTP

  • Время работы сервис-воркера

  • Ошибки хранилищ

if ('storage' in navigator && 'estimate' in navigator.storage) {
  navigator.storage.estimate().then(({usage, quota}) => {
    console.log(`Using ${usage} out of ${quota} bytes.`);
  });
}
Chrome <6% своб. пространства
Firefox <10% своб. пространства
Safari <50MB
IE10 <250MB
Edge Зависит от размера диска

Объем хранилищ ограничен

Кеширование ресурсов с других адресов

Приготовьтесь

к непрозрачности

Совет #5

2 варианта

  • Добавить заголовки CORS на удаленной стороне

  • Обрабатывать opaque ответы

Ограничения opaque ответов

  • Свойство status всегда равно нулю и не зависит от того, успешен запрос или нет

  • Методы Cache API add()/addAll() сработают аварийно, если статус хотя бы одного из ответов не находится в диапазоне 2XX

const appShellFilesToCache = [
  ...
  'https://workboxjs.org/offline-ga.min.svg'
]

sw-handmade.js

self.addEventListener('install', (event) => {

  event.waitUntil(
    caches.open(cacheName).then((cache) => {
      return cache.addAll(appShellFilesToCache)
    })
  )

})

Решение для no-cors

const noCorsRequest =
    new Request('https://workboxjs.org/offline-ga.svg', {
        mode: 'no-cors'
    });

fetch(noCorsRequest)
    .then(response => cache.put(noCorsRequest, response));

Правильные инструменты

Доверяй И проверяй

Совет #6

Инструменты помогают

  • Реализовывать сложные алгоритмы

  • Перенимать лучшие практики

  • Фокусироваться на ВАШЕЙ задаче

  • Следить за обновлениями спецификаций

  • Обрабатывать пограничные случаи

Фреймворки

  • sw-precache / sw-toolbox

  • Workbox

  • offline-plugin для Webpack

  • PWABuilder.com

  • create-react-app

  • preact-cli

  • polymer-cli

  • vue-cli

  • angular-cli

Генераторы

  • Lighthouse

  • Sonarwhal

Аудит

App shell

Динамическое кеширование

Оффлайн GA

Повторение неуспешных запросов

Уведомления об обновлениях

Build-интеграции

Возможность расширять функциональность собственного сервис-воркера

Генерация и валидация Web App Manifest

Генерация сервис-воркера

Генерация графики (иконки)

Генерация нативных проектов для размещения в магазины приложений

npm install -g sonarwhal

sonarwhal --init

sonarwhal https://airhorner.com
npm install -g lighthouse

lighthouse https://airhorner.com

Push-уведомления

Реинкарнации "всплывающих окон" - НЕТ!

Совет #7

  • Только после явного запроса от пользователя

  • Очевидная и простая отписка 

Подписка

Уведомления

  • Рассмотреть все альтернативные варианты. Уведомление – крайний случай.

  • Не для массовых, но для индивидуальных сообщений

MyAirline

Открыта регистрация на ваш рейс

MyAirline

myairline.com

Ваш рейс

MY 1228

Отправление

21.09 13:45

SVO -> KUF

Нажмите здесь, чтобы зарегистрироваться

myairline.com

Содержимое

  • Не повторяйтесь. Места и так мало.

  • Отправляйте непосредственно информацию, а не факт ее поступления

  • Продумайте призыв к действию

Не только кеширование

Используйте весь потенциал сервис-воркера

Совет #8

Балансировщик нагрузки

  • Перехватываем запросы и  выбираем определенный сервер

  • Например, наименее нагруженный или для A/B тестирования

Загрузки из браузера

service-worker.js

self.addEventListener('fetch', function(event) {

  if(event.request.url.indexOf("download-file") !== -1) {
    event.respondWith(event.request.formData().then( formdata => {

      var response = new Response(formdata.get("filebody"));
      response.headers.append('Content-Disposition',
        'attachment; filename="' + formdata.get("filename") + '"');
      return response;

    }));
  }

});

Поддержка WebP (с WASM)

service-worker.js

event.respondWith(async function() {

  const response = await fetch(event.request);
  const buffer = await response.arrayBuffer();

  const WebPDecoder = await fetchWebPDecoder();
  const decoder = new WebPDecoder(buffer);
  const blob = await decoder.decodeToBMP();

  return new Response(blob, { headers: { "content-type": "image/bmp",
    "status": 200 } });

}());
  • 1400+ разработчиков

  • Представители основных браузеров, библиотек, фреймворков

Совет #9

  • Все о PWA на русском языке

Спасибо!

@webmaxru

Максим Сальников

Новые функции

Еще в разработке

Совет #9

Периодическая синхронизация

  • Ограничение временным интервалом, состоянием батареи, режимом подключения к сети

  • Требует явного разрешения со стороны пользователя

  • Не требуется поддержки со стороны сервера

  • Финальное решение по запуску – за браузером

navigator.serviceWorker.ready.then((registration) => {
  registration.periodicSync.register({
    tag: 'get-latest-news',         // default: ''
    minPeriod: 12 * 60 * 60 * 1000, // default: 0
    powerState: 'avoid-draining',   // default: 'auto'
    networkState: 'avoid-cellular'  // default: 'online'
  }).then((periodicSyncReg) => {

    // Задача успешно зарегистрирована

  })
});

index.html

self.addEventListener('periodicsync', function(event) {
  if (event.registration.tag == 'get-latest-news') {
    event.waitUntil(fetchAndCacheLatestNews());
  }
  else {
    // Неизвестная задача, лучше отменить регистрацию
    event.registration.unregister();
  }
});

sw-handmade.js

  • 1400+ разработчиков

  • Представители основных браузеров, библиотек, фреймворков

Совет #10

  • Все о PWA на русском языке

Спасибо!

@webmaxru

Максим Сальников

Есть вопрос?

Сервис-воркеры: используем накопленные знания для светлого будущего PWA

By Maxim Salnikov

Сервис-воркеры: используем накопленные знания для светлого будущего PWA

Несомненно, 2018 - это год, когда Progressive Web Apps получат действительно широкое признание всех вовлеченных сторон: разработчиков браузеров, разработчиков веб-приложений, пользователей. Скорость и гладкость этого процесса в значительной степени зависят от того, насколько правильно мы, разработчики, используем возможности новых API. Основа всей идеи PWA - это Service Worker API, который отвечает за всю магию работы оффлайн, оптимизацию сетевых запросов, push-уведомления и массу других интересных и полезных вещей. В моей сессии, основанной на накопленном опыте разработки и поддержки PWA, мы пройдемся по списку: лучшие практики с примерами кода как избежать множества подводных камней последние новости с фронта поддержки PWA браузерами известные ограничения и как с ними жить масса полезной и практической информации о PWA

  • 2,202