Maxim Salnikov
@webmaxru
Практический мастер-класс
Сделаем веб-приложение прогрессивным за один час!
Максим Сальников
- 
	Организатор Mobile / Web / PWA митапов в Осло и Лондоне
- 
	Организатор конференций Mobile Era и ngVikings в Скандинавии
- 
	Ответственный за поток о веб-разработке 404fest в Самаре
Ответственный за успех Azure-разработчиков в Microsoft



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

работают везде*
* но не все возможности доступны везде**
нативно
** применяем стратегию прогрессивного улучшения
=
+
Application shell
Web App Manifest
Быстрые, адаптивные, mobile-first
Работают по HTTPS

Логически
Физически
-файл(ы)
Вебсайт
Сервис-воркер
Браузер/ОС
Event-driven worker
Кэш
fetch
push
sync
Спроектируем App shell
My App
- 
	Определим минимальный, но достаточный набор ресурсов, помним о версионности
- 
	При первой загрузке явно поместим эти ресурсы в Cache Storage
- 
	При последующих запусках будем пробовать выдавать эти ресурсы из кэша (иначе – сеть)
- 
	В то же время проверяем, не обновилась ли версия. Если да – обновляем кэш.
Время кода!
Рабочее окружение
Инструменты:
- Git
- Node, npm
- Любой статический веб-сервер, например serve
- Редактор кода
Браузеры (последние стабильные версии):
- Chrome – обязательно
- Firefox / Edge / Edge Beta – опционально
Быстрый старт
npm install serve -g
git clone https://github.com/webmaxru/pwa-for-production
cd pwa-for-production
git checkout part-1-init
npm install
serve
Итоговое приложение (можно копировать примеры кода оттуда):
Предварительное кэширование
self.addEventListener('install', event => {
  
    // Помещаем ресурсы app shell в Cache Storage
})self.addEventListener('activate', event => {
  
    // Удаляем из Cache Storage устаревшие версии
})handmade-service-worker.js
В реальности...
- 
	Необходимо следить за актуальностью списка ресурсов
- 
	Для некоторых видов ответов (opaque, redirected) потребуется специальная обработка
- 
	Пересоздание полной версии приложения в кэше при изменении любого из ресурсов неоптимально
- 
	Размеры кэша ограничены — нужен контроль
- 
	Придется подумать о механизмах инвалидации кэша
- 
	...
Перехватываем запросы
self.addEventListener('fetch', event => {
  if (event.request.url.indexOf('/api') != -1) {
    event.respondWith(
      // Реализуем стратегию Network-First (для API?)
    )
  } else {
    event.respondWith(
      // Реализуем стратегию Cache-First (для app shell?)
    )
  }
})handmade-service-worker.js
В реальности...
- 
	Реализация стратегий — не самая простая задача
- 
	Могут потребоваться и более сложные варианты, например, Stale-While-Revalidate
- 
	Даже в рамках одной стратегии для разных групп ресурсов могут потребоваться разные настройки
- 
	В итоге придется сделать собственную реализацию роутинга
- 
	Необходимо предусмотреть все варианты фолбеков
- 
	...
- 
	Оболочка приложения
- 
	Кеширование данных (runtime)
- 
	Повторение совершенных офлайн запросов
- 
	Двусторонняя коммуникация с приложением
- 
	Офлайн-аналитика (GA)
Все это в собственном сервис-воркере
Режимы работы
- 
	Workbox CLI
- 
	Плагин Webpack
- 
	Модуль для NodeJS
# Устанавливаем модуль Workbox для NodeJS
$ npm install workbox-build --save-devBuild script
// We will use injectManifest mode
const {injectManifest} = require('workbox-build')
// Sample configuration with the basic options
var workboxConfig = {...}
// Calling the method and output the result
injectManifest(workboxConfig).then(({count, size}) => {
    console.log(`Generated ${workboxConfig.swDest},
    which will precache ${count} files, ${size} bytes.`)
})workbox-build-inject.js
Build script configuration
// Sample configuration with the basic options
var workboxConfig = {
  globDirectory: 'dist/',
  globPatterns: [
    '**/*.{txt,png,ico,html,js,json,css}'
  ],
  swSrc: 'src/workbox-service-worker.js',
  swDest: 'dist/sw.js'
}
workbox-build-inject.js
Application Shell
// Importing Workbox itself from Google CDN
importScripts('https://googleapis.com/.../workbox-sw.js');
// Precaching and setting up the routing
workbox.precaching.precacheAndRoute([])
src/workbox-service-worker.js

Caching, serving, managing versions
Precaching manifest
[
  {
    "url": "index.html",
    "revision": "34c45cdf166d266929f6b532a8e3869e"
  },
  {
    "url": "favicon.ico",
    "revision": "b9aa7c338693424aae99599bec875b5f"
  },
  ...
]Build flow integration
{
  "scripts": {
    "build-pwa": "npm run build-app &&
                  node workbox-build-inject.js"
  }
}package.json
Application Data
Intercepting requests
self.addEventListener('fetch', event => {
  if (event.request.url.indexOf('/api/breakingnews') != -1) {
    event.respondWith(
      // Network-First Strategy
    )
  } else if (event.request.url.indexOf('/api/archive') != -1 {
    event.respondWith(
      // Cache-First Strategy
    )
  }
})handmade-service-worker.js
Routes and strategies
workbox.routing.registerRoute(
  new RegExp('/api/breakingnews'),
  new workbox.strategies.NetworkFirst()
);src/workbox-service-worker.js
workbox.routing.registerRoute(
  new RegExp('/api/archive'),
  new workbox.strategies.CacheFirst({
    plugins: [...]
  })
);Strategies
- 	CacheFirst
- 	CacheOnly
- 	NetworkFirst
- 	NetworkOnly
- 	StaleWhileRevalidate
Plugins
- 
	Expiration
- 
	CacheableResponse
- 
	BroadcastUpdate
- 
	BackgroundSync
- 
	...your own plugin?
Coding time
Better UX for update flow
App version updates
v1
v2
v1
v1
v2
Deployed
Displayed
v2


A new version of the app is available. Click to refresh.
const updateChannel = new BroadcastChannel('app-shell');
updateChannel.addEventListener('message', event => {
    // Inform about the new version & prompt to reload
});Option #1: BroadcastChannel
main.js
workbox.precaching.addPlugins([
    new workbox.broadcastUpdate.Plugin('app-shell')
]);src/workbox-service-worker.js
// Feature detection
if ('serviceWorker' in navigator) {
  // Postponing the registration for better performance
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js');
  });
}Option #2: Service worker lifecycle
app.js
Requirements
- 
	Feature detection
- 
	Registration after app fully loaded and UI rendered
- 
	Hook into service worker lifecycle update event
- 
	Was the service worker updated?
- 
	Was the app itself updated?
Workbox v4: workbox-window
import { Workbox } from 'workbox-window';
if ('serviceWorker' in navigator) {
    const wb = new Workbox('service-worker.js');
    // Event listeners...
    wb.register();
}main.js
$ npm install workbox-windowAlternative
<script type="module">
import {Workbox} from 'https://...cdn.com/workbox-window.mjs';
if ('serviceWorker' in navigator) {
  const wb = new Workbox('service-worker.js');
  wb.register();
}
</script>index.html
Was service worker file updated?
wb.addEventListener('installed', event => {
  if (event.isUpdate) {
  // Show "Newer version is available. Refresh?" prompt
  } else {
  // Optionally: show "The app is offline-ready" toast
  }
});
main.js
workbox.core.skipWaiting()
workbox.core.clientsClaim()service-worker.js
Must have!
Other useful events
wb.addEventListener('activated', (event) => {
  // Service worker activated
});wb.addEventListener('waiting', (event) => {
  // Service worker was installed but can't be activated
});Same for the "external" service workers:
externalinstalled, externalwaiting, externalactivatedЧто нового?
Фоновая загрузка
- 
	Приостановка / возобновление загрузки при нестабильном подключении
- 
	Доступ к загруженным ресурсам и статусу загрузки из приложения
- 
	Продолжение загрузки после закрытия приложения
- 
	Нативные элементы интерфейса браузера / ОС для управления и получения статуса загрузки
Подробнее и с примерами



Native File System API

Badging API

Contact Picker API

Почти 100 новых API
- 
	2000+ разработчиков
- 
	Представители основных браузеров, библиотек, фреймворков
- 
	Все о PWA на русском языке
Спасибо!
@webmaxru
Максим Сальников
Есть вопрос?
@webmaxru
Максим Сальников
Thank you!
Maxim Salnikov
@webmaxru
Questions?
Maxim Salnikov
@webmaxru
Практический мастер-класс по PWA: сделаем веб-приложение прогрессивным за один час!
By Maxim Salnikov
Практический мастер-класс по PWA: сделаем веб-приложение прогрессивным за один час!
1. Определяем общую цель и конкретные задачи, что нужно, чтобы наше приложение стало PWA. 2. Устанавливаем и настраиваем наше рабочее окружение. 3. Создаем и регистрируем наш первый сервис-воркер. Изучаем возможности Dev Tools браузера касательно PWA. 4. Отправляем наше приложение в оффлайн - на практике знакомимся с библиотекой Workbox. 5. Оптимизируем работу с сетью - кешируем запросы к API, используя разные стратегии. 6. Отдыхаем от кодинга - составляем Web App Manifest, чтобы наше приложение стало устанавливаемым. 7. Проверка итоговой “прогрессивности” нашего приложения, подведение итогов. 8. Пара слов о других интересных возможностях сервис воркеров и о будущем PWA.
- 4,876
 
   
   
  