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

@webmaxru

Погружение в глубокий офлайн –

веб способен на это!

Что значит готовность веб-приложения к офлайн

И как еe достичь прямо сегодня

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

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

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

  • Ответственный за поток о веб-разработке 404fest в Самаре

Ответственный за успех Azure-разработчиков в Microsoft

  1. The World Wide Web is the New Software Platform

  2. The Web Browser is the New Operating System

  3. JavaScript is the de facto Programming Language of the Web 

Январь, 2008

Веб – отличная платформа для приложений

Браузеры

  • Почти на каждом устройстве с UI

  • Автообновляемые

  • API для доступа к аппаратным ресурсам

JavaScript

Браузеры

  • Разносторонний язык

  • Мощный инструментарий

  • Поступательно развивается в правильном направлении

Веб – отличная платформа для приложений

JavaScript

Движки JS

Браузеры

  • Курс на производительность

  • Возможности встраивания

Веб – отличная платформа для приложений

JavaScript

Движки JS

Интерфейсы

Браузеры

  • Удобные инструменты для создания адаптивных интерфейсов

  • Повышенное внимание доступности

  • Разнообразие высококачественных библиотек компонентов

Веб – отличная платформа для приложений

Сообщество

JavaScript

Движки JS

Интерфейсы

Браузеры

69.7%

Веб – отличная платформа для приложений

Сообщество

JavaScript

Движки JS

Интерфейсы

Браузеры

Проблемы?

Завязан на состояние подключения к сети

(по историческим причинам)

Веб – отличная платформа для приложений

Решения

Кэширование

Установка

  • HTTP Cache

  • AppCache

  • Save page as... (complete)

  • Chrome Apps

  • Electron

  • NativeScript, React Native

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

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

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

работают везде*

* но не все возможности доступны везде**

нативно

** применяем стратегию прогрессивного улучшения

Офлайн-готовность 

<

>

=

=

+

Application shell

Web App Manifest

Быстрые, адаптивные, mobile-first

Работают по HTTPS

Установка приложения "как нативного"

Офлайн-готовность приложения

  • Само приложение

  • Потребляемые данные

  • Действия офлайн

  • Ошибки соединения

  • Обновления

  • Возможности платформы

  • Всегда доступно

  • Осмысленно сохраняются

  • Тщательно записываются

  • Не прерывают задачу

  • Явные и неявные

  • Использутся на всю катушку!

Сохраняя при этом природу веба!

Оболочка приложения

Давайте спроектируем App Shell

My App

  • Определить ресурсы

  • Закэшировать

  • Выдать из кэша

  • Управлять версиями

}

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

Логически

Физически

-файл(ы)

Вебсайт

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

Браузер/ОС

Event-driven worker

Кэш

fetch
push
sync

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

self.addEventListener('install', event => {
  
    // Помещаем ресурсы app shell в Cache Storage

})
self.addEventListener('activate', event => {
  
    // Удаляем из Cache Storage устаревшие версии

})

handmade-service-worker.js

Перехватываем запросы

self.addEventListener('fetch', event => {
  
  event.respondWith(
    caches.match(event.request).then(response => {
      return response || fetch(event.request)
    })   
  )

})

handmade-service-worker.js

Внезапно...

В реальности...

Редиректы?

Fallback?

Opaque response?

Версионность?

Инвалидация кэша?

Обновление спецификаций?

Размер локального хранилища?

Переменные имена ресурсов?

Feature detection?

Минимально необходимое обновление кэша?

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

Роутинг?

Точные настройки стратегий?

Модульность?

Я вижу старую версию!!!

  • Определить ресурсы

  • Закэшировать

  • Выдать из кэша

  • Управлять версиями

}

Есть ли помощник?

# Будем использовать как модуль Node
$ npm install workbox-build --save-dev

И множество других модулей

Конфигурация

var workboxConfig = {
  globDirectory: 'dist/',
  globPatterns: [
    '**/*.{txt,png,ico,html,js,json,css}'
  ],
  swSrc: 'src/workbox-service-worker.js',
  swDest: 'dist/sw.js'
}

workbox-build.js

Исходник сервис-воркера

// Загрузим с Google CDN (но лучше иметь локально)
importScripts('https://googleapis.com/.../workbox-sw.js')

// Все остальное
workbox.precaching.precacheAndRoute([])

src/workbox-service-worker.js

Кэширование, выдача, управление версиями

Билд-скрипт

const {injectManifest} = require('workbox-build')

var workboxConfig = {...}

// Наполняем сервис-воркер информацией о ресурсах
injectManifest(workboxConfig).then(({count, size}) => {
    console.log(`Generated ${workboxConfig.swDest},
    which will precache ${count} files, ${size} bytes.`)
})

workbox-build.js

Интеграция с билдом приложения

{
  "scripts": {
    "build-pwa": "npm run build-app &&
                  node workbox-build.js"
  }
}

package.json

Данные приложения

Перехват запросов

self.addEventListener('fetch', event => {

  if (event.request.url.indexOf('/api/breakingnews') != -1) {
    event.respondWith(
      // Стратегия Network-First
    )
  } else if (event.request.url.indexOf('/api/archive') != -1 {
    event.respondWith(
      // Стратегия Cache-First
    )
  }
})

handmade-service-worker.js

Пути и стратегии

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: [...]
  })
)

Стратегии

  • CacheFirst

  • CacheOnly

  • NetworkFirst

  • NetworkOnly

  • StaleWhileRevalidate

Плагины

  • Expiration

  • CacheableResponse

  • BroadcastUpdate

  • BackgroundSync

  • ...ваш собственный?

Сохранение офлайн-действий

Background sync

  • Одноразовое событие офлайн->онлайн

  • Работает при закрытом приложении

const swReg = await navigator.serviceWorker.ready
if ('sync' in swReg) {
  // Сохраняем данные в IndexedDB и...
  swReg.sync.register('postTweet')
}

main.js

self.addEventListener('sync', event => {
  if (event.tag == 'postTweet') {
    event.waitUntil(
        // Извлекаем из IndexedDB и отправляем на сервер
    )
  }
})

handmade-service-worker.js

const postTweetPlugin =
    new workbox.backgroundSync.Plugin('tweetsQueue', {
        maxRetentionTime: 24*60 // Продолжительность попыток
    })

src/workbox-service-worker.js

workbox.routing.registerRoute(
  /(http[s]?:\/\/)?([^\/\s]+\/)post-tweet/,
  new workbox.strategies.NetworkOnly({
    plugins: [postTweetPlugin]
  }),
  'POST'
)

Загрузки под контролем

Background fetch

  • Авто-остановка/продолжение загрузок (down/up)

  • Отслеживание состояния и прогресса в коде

  • Работает при закрытом приложении

  • Пользователь управляет и следит за процессом

const swReg = await navigator.serviceWorker.ready

await swReg.backgroundFetch.fetch(
  'my-series',
  ['s01e01.mpg', 's01e02.mpg'],
  {
    title: 'Первый сезон',
    downloadTotal: 600 * 1024 * 1024
   }
)

main.js

self.addEventListener('backgroundfetchsuccess', event => {
  event.waitUntil(
    (async function() {
      try {
        // Помещаем ресурс в Cache Storage
        ...
        await event.updateUI({ title: `Загружено!` })
      } catch (err) {
        await event.updateUI({ title: `Ошибка: ${err}` })
      }
    })()
  )
})

src/service-worker.js

Подробнее и с примерами

Планирование обновлений

Periodic background sync

  • Cron в браузере

  • Не зависит от сети

  • Периодичность задается разработчиком

  • Другой API для этого

  • Работает только онлайн

  • Браузер решает

const swReg = await navigator.serviceWorker.ready

if ('periodicSync' in swReg) {

  swReg.periodicSync.register('refreshTweets', {
    // Минимальный период - 24 часа
    minInterval: 24 * 60 * 60 * 1000,
  })

}

main.js

self.addEventListener('periodicsync', event => {
  if (event.tag === 'refreshTweets') { 
    event.waitUntil(
    	// [Может быть] Выполняем полезные действия
    )
  }
})

src/service-worker.js

  • Тип сетевого соединения?

  • Режим экономии трафика?

  • Доступное место на устройстве?

chrome://flags/#enable-devtools-experim

Отладка фоновых сервисов

А что насчет "другого API"

  • Notification Triggers

  • Scheduled Task

Еще инструментов?

Native File System API

Badging API

Contact Picker API

Готовы к взлету

Почти 100 новых API

  • Полноценная платформа для приложений

  • Механизмы офлайн-готовности

  • Отличный инструментарий

  • Главное — пользовательский опыт

Всё только начинается!

Веб сегодня

  • 2000+ участников

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

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

Спасибо!

@webmaxru

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

Есть вопрос?

@webmaxru

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

Погружение в глубокий офлайн - веб способен на это!

By Maxim Salnikov

Погружение в глубокий офлайн - веб способен на это!

Прогрессивные веб-приложения уже получили действительно широкую известность и признание всеми вовлеченными сторонами: разработчиками браузеров (наконец, всеми!), разработчиками, пользователями. Идея приложений, не зависящих от подключения к сети, доказала свою жизнеспособность, и мы видим все больше и больше проектов, идущих по этому пути, что делает возможность работы в офлайне не только лучшей практикой, но просто и хорошей манерой в вебе. В моем докладе, основанном на глубоком исследовании возможностей Service Worker API (с использованием Cache Storage, Background Fetch, Background Sync) и собранных UX-находках, мы рассмотрим историю офлайн веба, важность рассмотрения подключения как привилегии, текущие проблемы (и их решения) и правильные инструменты. В течение докдада мы спроектируем приложение, готовое к работе офлайн, применяя лучшие технологии и UX-практики и добавляя возможности одна за одной: оболочка приложения, кеширование ресурсов и данных, синхронизация при подключении к сети. Все ради наших пользователей, которые требуют нового уровня отказоустойчивости и скорости работы наших приложений.

  • 3,520