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

@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

Цель офлайнизации №1

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

(Application Shell)

Спроектируем App shell

My App

  • Определим минимальный, но достаточный набор ресурсов, помним о версионности 

  • При первой загрузке явно поместим эти ресурсы в Cache Storage

  • При последующих запусках будем пробовать выдавать эти ресурсы из кэша (иначе – сеть)

  • В то же время проверяем, не обновилась ли версия. Если да – обновляем кэш.

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

Логически

Физически

-файл(ы)

Вебсайт

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

Браузер/ОС

Event-driven worker

Кэш

fetch
push
sync

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

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-dev

Билд-скрипт

// Используем режим injectManifest
const {injectManifest} = require('workbox-build')

// Конфигурируем (детали на следующем слайде)
var workboxConfig = {...}

// Вызываем метод и выводим результаты
injectManifest(workboxConfig).then(({count, size}) => {
    console.log(`Создан ${workboxConfig.swDest}, который
    закеширует ${count} файлов, ${size} байт.`)
})

workbox-build-inject.js

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

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

Собственно, сам сервис-воркер

// Импортируем библиотеку Workbox с Google CDN
importScripts('https://googleapis.com/.../workbox-sw.js');

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

src/workbox-service-worker.js

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

  • Перехват нужных запросов и выдача ресурсов из Cache Storage или сети

Что мы "инжектим"

[
  {
    "url": "index.html",
    "revision": "34c45cdf166d266929f6b532a8e3869e"
  },
  {
    "url": "favicon.ico",
    "revision": "b9aa7c338693424aae99599bec875b5f"
  },
  ...
]

Совместим с билдом приложения

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

package.json

Цель офлайнизации №2

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

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

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

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

Цель офлайнизации №3

Сохраняем и повторяем действия, совершенные офлайн

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

  • Откладывает действия до тех пор, пока не будет установлено подключение

  • Синхронизация будет выполнена, даже если приложение уже закрыто

navigator.serviceWorker.ready.then( swRegistration => {
  return swRegistration.sync.register('myFirstSync');
});

main.js

self.addEventListener('sync', event => {
  if (event.tag == 'myFirstSync') {
    event.waitUntil(
        // То, что было запланировано
    );
  }
});

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'
)

Цель офлайнизации №4

Управляем скачиванием и закачиванием и их результатами

Фоновая загрузка

  • Приостановка / возобновление загрузки при нестабильном подключении

  • Доступ к загруженным ресурсам и статусу загрузки из приложения

  • Продолжение загрузки после закрытия приложения

  • Нативные элементы интерфейса браузера / ОС для управления и получения статуса загрузки

const registration = await navigator.serviceWorker.ready;
await registration.backgroundFetch.fetch(
  'my-series',
  ['s01e01.mpg', 's01e02.mpg'],
  {
    title: 'Загрузка My Series',
    downloadTotal: 1000000000
   }
);

main.js

const bgFetches =
  await registration.backgroundFetch.getIds();
console.log(bgFetches);
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

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

Итого

  • Веб сегодня — полноценная платформа для приложений

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

  • Есть замечательные инструменты для автоматизации типовых задач подготовки к офлайн

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

"Progressive Web Apps State of the Union" от Dominick Ng на BlinkOn 10

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

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

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

Спасибо!

@webmaxru

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

Есть вопрос?

@webmaxru

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

#WSH?

Made with Slides.com