Погружение

в Service Workers

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

  • Google Developer Expert по Angular

  • Организатор AngularJS Oslo Meetup

  • Организатор MobileEra.rocks

UI Engineer  продуктов из будущего

в ForgeRock

Google Experts это глобальная сеть опытных разработчиков, дизайнеров, маркетологов, активно поддерживающих разработчиков, стартапы и компании, которые изменяют мир своими веб- и мобильными приложениями

Google Developer

Experts

Progressive Web App

Progressive Web App (PWA) is a term used to denote a new software development methodology.

 

Unlike traditional applications, Progressive Web App can be seen as an evolving hybrid of regular web pages (or websites) and a mobile application.

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

  • Прогрессивное
  • Адаптивное
  • Независимое от соединения
  • "Как нативное" для пользователя
  • Всегда свежая версия
  • Безопасное
  • Доступное для поиска
  • Удерживающее
  • Легко устанавливаемое
  • Легко распространяемое

ServiceWorker

PWA

Работа приложения в автономном режиме

Повышение быстродействия за счет уменьшения количества обращений к сети

Удобные инструменты для обработки случаев проблем с соединением

Не только работа с сетью!

Синхронизация данных в фоновом режиме

Централизованное получение обновлений результатов сложных вычислений (геолокация, данные с гироскопа и т.д.) для использования сразу несколькими частями приложения

Компиляция и управление зависимостями CoffeeScript, less, CJS/AMD и т.д. на стороне клиента

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

Физически

*.JS

-файл

 Логически

Программируемый сетевой прокси-сервер, расположенный между приложением, запущенным в браузере и сетью

App

SW

Браузер

Это  Worker

  • Работает в фоновом режиме

  • Нет доступа к DOM

Отдельный контекст для выполнения фоновых задач, который не блокирует UI

Особенности

  • Регистрируется непосредственно из одного из скриптов приложения для определенного адреса (URL)

  • Работает только по HTTPS (или на localhost)

  • Не может использоваться для хранения состояния, но имеет доступ к Cache API и IndexedDB API

Event-driven

  • Реагирует на события (запросы в сеть со стороны клиента, push-уведомления со стороны сервера)

  • Может получать сообщения от клиента (с помощью postMessage)

Время работы

  • Запускается только при наступлении событий

  • Работает ровно столько времени, сколько требуется для обработки события

Автономная работа приложения

Регистрация

// app.js

if ('serviceWorker' in navigator) {  
    navigator.serviceWorker.register('/sw.js')
    .then(function(registration) {
        // Successful registration
        console.log("Service Worker Registered", registration);
    })
    .catch(function(err) {
        // Registration failed
        console.log("Service Worker Failed to Register", err);
    })
}

Область видимости!

Зарегистрирован

Установлен

=

Установка

// sw.js

var currentCacheName = 'pwaday-sw-v1';

var filesToCache = [
    '/index.html',
    '/css/main.css',
    '/img/logo.png',
    '/js/main.js'
];

self.addEventListener('install', function(event) {  
  event.waitUntil(
    caches.open(currentCacheName).then(function(cache) {
      return cache.addAll(filesToCache);
    })
  );
});

Установлен

Активирован

=

Активация

// sw.js

self.addEventListener('activate', function(event) {  
  event.waitUntil(
    // Get all the cache names
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        // Get all the items that are not the current one
        cacheNames.filter(function(cacheName) {
          return cacheName != currentCacheName;
        }).map(function(cacheName) {
          // Delete the items
          return caches.delete(cacheName);
        })
      ); // end Promise.all()
    }) // end caches.keys()
  ); // end event.waitUntil()
});

Активирован

Перехватывает и принимает

=

Запросы и сообщения

// sw.js

self.addEventListener('fetch', function(event) {  
  // Do stuff with fetch events
});

self.addEventListener('message', function(event) {  
  // Do stuff with postMessage() received from document
});

self.addEventListener('push', function(event) {  
  // Do stuff with push-notifications received from server
});

Жизненный цикл

Жизненный цикл

Обновление

  1. Обновляется .js-файл Service Worker 

  2. Регистрируется новый SW и вызывается событие install

  3. Старый SW все еще управляет страницами приложения, поэтому новый находится в состоянии installed / waiting

  4. Когда все страницы (закладки) приложения закрываются, старый SW переходит в состояние redundant, контроль переходит к новому

  5. Вызывается событие activate

Fast-track

self.addEventListener('install', (event) => {
  event.waitUntil(self.skipWaiting());
});


self.addEventListener('activate', (event) => {
  event.waitUntil(self.clients.claim());
});

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

// sw.js

self.addEventListener('fetch', function(event) {
  event.respondWith(
    ...
  );
});

Cache-first

// sw.js

this.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
  );
});

Вернем то, что находится в cache

Cache-first

// sw.js

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        // Cache hit - return response
        if (response) {
          return response;
        }
        return fetch(event.request);
      }
    )
  );
});

Вернем то, что находится в cache. В случае неуспеха - возьмем из сети.

Cache-first

// sw.js

var currentCacheName = 'pwaday-sw-v1';

this.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).then(function(resp) {
      return resp || fetch(event.request).then(function(response) {
        return caches.open(currentCacheName).then(function(cache) {
          cache.put(event.request, response.clone());
          return response;
        });  
      });
    })
  );
});

Вернем то, что находится в cache. В случае неуспеха - возьмем из сети и поместим в cache (на будущее)

Дальнейшие улучшения

  • Предусмотреть вариант, когда ресурса нет ни в cache, ни в сети

  • Предусмотреть обновление cache при обновлении статических ресурсов, например, используя параметр last-modified из HTTP заголовков

  • ...

Network-first

// sw.js

var currentCacheName = 'pwaday-sw-v1';

this.addEventListener('fetch', function(event) {
  event.respondWith(
    
    fetch(e.request)
      .then((response) => {
        return caches.open(currentCacheName).then((cache) => {

          cache.put(e.request.url, response.clone());
          return response.clone();

        });
      })

  );
});

Стратегии

  1. Cache-first, Network-first, Fastest, Cache-only, Network-only...

  2. Могут быть разные для разных адресов, типов файлов, методов запросов и т.д.

  3. Можно добавить логику с настраиваемым Timeout, несколькими попытками запросов и т.д.

Когда же?

Целый набор API

  • ServiceWorker API

  • Cache API

  • IndexedDB API

  • Fetch API

  • Notifications API

  • Push API

  • Promise

Инструменты

sw-toolbox

Манипуляции с cache

toolbox.precache(filesToCache);

Маршрутизация

toolbox.networkFirst, toolbox.cacheFirst, toolbox.fastest,
toolbox.cacheOnly, toolbox.networkOnly

Стратегии

toolbox.router.get('/assets/data/(.*)', toolbox.networkFirst, {
	cache: { name: currentCacheName }
});

Angular

Mobile Toolkit

Ресурсы

Спасибо!

@webmaxru

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

Made with Slides.com