Максим Сальников
@webmaxru
Автоматизируем
сервис-воркер
с Workbox 6
Как сделать приложение быстрее и удобнее,
работая с удовольствием
Максим Сальников
- 
	Организатор Mobile-/Web-/PWA-митапов в Норвегии
- 
	Организатор конференций Mobile Era и ngVikings в Скандинавии
- 
	Спикер, тренер, автор публикаций о современном вебе
Ответственный за успех Azure-разработчиков в Microsoft


Сервис-воркер
Работа с сетью
Кеширование
- 
	Установка 
- 
	Возможности 
Под управлением сервис-воркеров
Страницы

0.9%
на мобильных устройствах
1%
на десктопах
Под управлением сервис-воркеров
Просмотры

18.2%
Удобно для пользователей
- 
                Само приложение
- 
                Потребляемые данные
- 
                Действия офлайн
- 
                Ошибки соединения
- 
                Обновления
- 
                Возможности платформы
- 
	Всегда доступно
- 
	Осмысленно сохраняются
- 
	Не пропадают
- 
	Не прерывают задачу
- 
	Явные и фоновые
- 
	Используются на всю катушку!
Сохраняя преимущества веба!
Шутка из 2019
Вольный перевод https://twitter.com/sanketsahu/status/1133223194888773632

В теории
self.addEventListener('install', event => {
    // Помещаем ресурсы в Cache Storage
})
self.addEventListener('activate', event => {
    // Управляем версиями
})
self.addEventListener('fetch', event => {
    // Извлекаем из кеша и отдаем
})handmade-service-worker.js
В руководстве
const PRECACHE = 'precache-v1';
const RUNTIME = 'runtime';
const PRECACHE_URLS = [
  'index.html',
  './',
  'styles.css',
  '../../styles/main.css',
  'demo.js'
];
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(PRECACHE)
      .then(cache => cache.addAll(PRECACHE_URLS))
      .then(self.skipWaiting())
  );
});
self.addEventListener('activate', event => {
  const currentCaches = [PRECACHE, RUNTIME];
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return cacheNames.filter(cacheName => !currentCaches.includes(cacheName));
    }).then(cachesToDelete => {
      return Promise.all(cachesToDelete.map(cacheToDelete => {
        return caches.delete(cacheToDelete);
      }));
    }).then(() => self.clients.claim())
  );
});
self.addEventListener('fetch', event => {
  if (event.request.url.startsWith(self.location.origin)) {
    if (event.request.url.indexOf('api/') != -1) {
      event.respondWith(
        caches.match(event.request.clone()).then((response) => {
          return response || fetch(event.request.clone()).then((r2) => {
            return caches.open(RUNTIME).then((cache) => {
              cache.put(event.request.url, r2.clone());
              return  r2.clone();
            });
          });
        })
      );
    } else {
      event.respondWith(
        caches.match(event.request).then(cachedResponse => {
          if (cachedResponse) {
            return cachedResponse;
          }
          return caches.open(RUNTIME).then(cache => {
            return fetch(event.request).then(response => {
              return cache.put(event.request, response.clone()).then(() => {
                return response;
              });
            });
          });
        })
      );
    }
  }
});handmade-service-worker.js
− Автоматизация при билде
− Точные настройки
− Расширяемость
− Умное кеширование
− Полный набор фолбэков
− Связь с приложением
− Отладочная информация
− ...
В продакшн
Редиректы?
Fallback?
Opaque response?
Версионность?
Инвалидация кеша?
Обновление спецификаций?
Размер локального хранилища?
Переменные имена ресурсов?
Feature detection?
Минимально необходимое обновление кеша?
Стратегии кеширования?
Роутинг?
Точные настройки стратегий?
Модульность?
Я вижу старую версию!!!
- 
	Продуманный уровень абстракций
- 
	Декларативность, где уместно
- 
	Модульность и расширяемость
- 
	Богатая функциональность «из коробки»
- 
	Мощный инструментарий
~30% сервис-воркеров — это...
Open source, активная разработка и поддержка
Настроим офлайн-доступность
import { precacheAndRoute } from "workbox-precaching";
// Закешировать и выдавать ресурсы из массива __WB_MANIFEST
precacheAndRoute(self.__WB_MANIFEST);src/service-worker.js
Осталось сделать:
- 
	Заполнить __WB_MANIFEST
- 
	Собрать (бандлинг)
- 
	Зарегистрировать в приложении
# Используем Workbox как модуль для Node
$ npm install workbox-build --save-dev}
При каждом билде
Билд-скрипт
const { injectManifest } = require("workbox-build");
let workboxConfig = {
  swSrc: "src/service-worker.js",
  swDest: "dist/sw.js",
  globPatterns: ["index.html", "*.css", "*.js"]
};
injectManifest(workboxConfig).then(() => {
  console.log(`Generated ${workboxConfig.swDest}`);
});sw-build.js
[Почти] готовый сервис-воркер
import { precacheAndRoute } from "workbox-precaching";
precacheAndRoute([
  { revision: "866bcc582589b8920dbc", url: "index.html" },
  { revision: "c2761edff7776e1e48a3", url: "styles.css" },
  { revision: "3469613435532733abd9", url: "main.js" }
]);dist/sw.js
import { precacheAndRoute } from "workbox-precaching";
precacheAndRoute(self.__WB_MANIFEST);src/service-worker.js
Бандлинг и минификация
import resolve from 'rollup-plugin-node-resolve'
import replace from 'rollup-plugin-replace'
import { terser } from 'rollup-plugin-terser'
export default {
  input: 'dist/sw.js',
  output: {
    file: 'dist/sw.js',
    format: 'iife'
  },
  plugins: [ /* Следующий слайд */ ]
}rollup.config.js
Конфигурируем плагины
plugins: [
  resolve(),
  replace({
    'process.env.NODE_ENV': JSON.stringify('production')
  }),
  terser()
]rollup.config.js
Интегрируем в билд приложения
"build-pwa":
  "npm run build-app &&
   node build-sw.js &&
   npx rollup -c"package.json / scripts

Регистрация в приложении
import { Workbox, messageSW } from 'workbox-window';
if ('serviceWorker' in navigator) {
      const wb = new Workbox('/sw.js');      
      wb.register();
    
      // Интерактивный процесс обновления с messageSW
      // См. пример кода на https://aka.ms/workbox6
}src/main.js
Доступна новая версия приложения. Загрузить

- 
	Работает офлайн (оболочка приложения) 
- 
	Управление версиями 
Динамическое кеширование
import { registerRoute } from "workbox-routing";
import {
  CacheFirst,
  NetworkFirst,
  StaleWhileRevalidate,
} from "workbox-strategies";src/service-worker.js
// Аватары можно брать всегда из кеша
registerRoute(
  new RegExp("https://www.gravatar.com/avatar/.*"),
  new CacheFirst()
);Кеширование ответов API
// Список статей должен быть всегда свежим
registerRoute(
  ({url}) => url.pathname.startsWith('/api/articles/'),
  new NetworkFirst()
);
// Статью берем из кеша и проверяем на обновление
import { BroadcastUpdatePlugin } from 'workbox-broadcast-update';
registerRoute(
  ({url}) => url.pathname.startsWith('/api/article/'),
  new StaleWhileRevalidate({
    plugins: [
      new BroadcastUpdatePlugin()
    ],
  })
);src/service-worker.js
- 
	Единовременное событие при подключении к сети
- 
	Для организации переотправки запросов, сделанных офлайн
- 
	После закрытия вкладки с приложением и/или браузера
Фоновая синхронизация
import { BackgroundSyncPlugin } from "workbox-background-sync";
const bgSyncPlugin = new BackgroundSyncPlugin("feedbackQueue", {
  maxRetentionTime: 24 * 60, // Период попыток повтора в минутах
});registerRoute(
  ({url}) => url.pathname.startsWith('/feedback'),
  new NetworkFirst({
    plugins: [bgSyncPlugin],
  }),
  "POST"
);Готовность формы к оффлайн
src/service-worker.js
Стратегии
- 
	CacheFirst
- 
	CacheOnly
- 
	NetworkFirst
- 
	NetworkOnly
- 
	StaleWhileRevalidate
- 
	...своя стратегия?
Плагины
- 
	Expiration
- 
	CacheableResponse
- 
	BroadcastUpdate
- 
	BackgroundSync
- 
	...свой плагин?
Своя стратегия?
- 
	Если нужно изменить логику запросов
- 
	Можно применять в registerRoute()
- 
	Можно использовать встроенные и собственные плагины
Свой плагин?
- 
	Если нужно настроить или расширить стратегию
- 
	Можно использовать во встроенных и собственных стратегиях
Рецепты
import {
  googleFontsCache,
  imageCache
} from "workbox-recipes";
// Кешируем Google Fonts
googleFontsCache({ cachePrefix: "wb6-gfonts" });
// Кешируем изображения
imageCache({ maxEntries: 10 });src/service-worker.js
...и еще однострочники для:
// Фолбэка для страниц и изображений
offlineFallback();
// Кеширования страниц
pageCache();
// Кеширования статических ресурсов
staticResourceCache();
// Разогрева кеша
warmStrategyCache(urls, strategy);Варианты использования WB
Гибкость
Автоматизация
Модули и их методы
Рецепты
Генерация сервис-воркера
через CLI
Собственные плагины
Собственные стратегии
- 
	Демо-приложение
- 
	Исходный код
- 
	Хостинг на Azure Static Web Apps
- 
	Русскоязычное сообщество PWA
- 
	Ресурсы на русском языке
- 
	Примеры в продакшн
Спасибо!
@webmaxru
Максим Сальников
Автоматизируем сервис-воркер с Workbox 6
By Maxim Salnikov
Автоматизируем сервис-воркер с Workbox 6
«Задеплоил сервис-воркер — нужно покупать новый домен» — известная шутка о том, как сложно писать собственную логику кеширования. С приходом шестой версии библиотеки Workbox для прогрессивных веб-приложений (PWA) больше не нужен компромисс между гибкостью и удобством автоматизации сетевых задач. Я расскажу, как начать работу с Workbox 6, реализовать типовую функциональность для офлайнового веб-приложения и пойти дальше, добавив собственную логику кеширования.
- 4,521
 
   
   
  