jquery uk february 2015: service workers on vacay
TRANSCRIPT
@thisNatasha
ServiceWorkerson vacay...
Natasha Rooney@thisNatasha
GSMA Web TechnologistW3C WebMob Co-Chairwww.w3.org/Mobile/IG/
@thisNatasha
ServiceWorker eh?
Offline Cache Worker
source: Chang W. Doh
@thisNatasha
ServiceWorker eh?
Offline Cache WorkerNavigation Controller
source: Chang W. Doh
@thisNatasha
ServiceWorker eh?
Offline Cache WorkerNavigation ControllerEvent Manager
source: Chang W. Doh
@thisNatasha
Offline Cache WorkerNavigation ControllerEvent Manager
ServiceWorker eh?
source: Chang W. Doh
@thisNatasha
ServiceWorker eh?
source: Chang W. Doh
Control request / response flowLoad a cached copy of a resource
@thisNatasha
ServiceWorker eh?
source: Chang W. Doh
Push & Background
Sync
Cache Control / Custom Response
Faster Load Time!
Control request / response flowLoad a cached copy of a resource
@thisNatasha
ServiceWorker eh?
source: Chang W. Doh
Control request / response flowLoad a cached copy of a resource
Push & Background
Sync
Cache Control / Custom Response
Faster Load Time!
@thisNatasha
Ok fine, but what is it?Worker: script
separate from the webpageDifferent
LifecycleTerminated when
not in use
@thisNatasha
Ok fine, but what is it?Worker: script
separate from the webpageDifferent
Lifecycle
“In terms of network control, it acts like a proxy server sitting on the client, you get to decide what to do on a request-by-request basis. You can use this to make stuff work faster, offline, or build new features.”Jake Archibald
Terminated when not in use
@thisNatasha
LifecycleInstalls
Activates
Waits Network fetch detection
Terminated
source: html5rocks
@thisNatasha
Two APIs one interface representing a proxy for a value not necessarily known when that thing is created… (um, Promises)
FetchGives ServiceWorkers the powers to manage network requests and return responses for resources
https://fetch.spec.whatw
g.org/
https://slightlyoff.github.io/S
erviceWorker/spec/service_w
orker/index.htm
l#cache-objects
CacheSave fetched responses, return these responses later, not part of HTTP cache
+ Promises
@thisNatasha
RegisterRegister your ServiceWorker in your main JavaScript file.
// filename: app.js
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(function(registration) { // Registration was successful console.log('ServiceWorker registration successful,
scope: ', registration.scope); }).catch(function(err) { // registration failed console.log('ServiceWorker registration failed: ', err); });}
source: html5rocks
@thisNatasha
RegisterRegister your ServiceWorker in your main JavaScript file.
// filename: app.js
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(function(registration) { // Registration was successful console.log('ServiceWorker registration successful,
scope: ', registration.scope); }).catch(function(err) { // registration failed console.log('ServiceWorker registration failed: ', err); });}
source: html5rocks
app.js
@thisNatasha
RegisterRegister your ServiceWorker in your main JavaScript file.
// filename: app.js
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(function(registration) { // Registration was successful console.log('ServiceWorker registration successful,
scope: ', registration.scope); }).catch(function(err) { // registration failed console.log('ServiceWorker registration failed: ', err); });}
source: html5rocks
SW location
@thisNatasha
RegisterRegister your ServiceWorker in your main JavaScript file.
// filename: app.js
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(function(registration) { // Registration was successful console.log('ServiceWorker registration successful,
scope: ', registration.scope); }).catch(function(err) { // registration failed console.log('ServiceWorker registration failed: ', err); });}
source: html5rocks
Browser does the rest!
@thisNatasha
Create sw.jsOr whatever you want to call your ServiceWorker javascript file...
// filename: sw.js
TIP: sw.js goes in the root of the domain
@thisNatasha
InstallGive assets you want to run offline
// filename: sw.js
// The files we want to cachevar shellcache = 'shell-v1';var urlsToCache = [ '/', '/app/css/dist/bootstrap.min.css', '/app/js/dist/angular.min.js', '/app/js/dist/jquery-1.11.2.min.js', '/app/js/dist/bootstrap.min.js'];
// INSTALLself.addEventListener('install', function(event) { // Perform install steps
event.waitUntil(caches.open(shellcache) //our cache! .then(function(cache) { console.log('Opened cache'); return cache.addAll(urlsToCache); //pass all our urls! }));
});
@thisNatasha
// filename: sw.js
// The files we want to cachevar shellcache = 'shell-v1';var urlsToCache = [ '/', '/app/css/dist/bootstrap.min.css', '/app/js/dist/angular.min.js', '/app/js/dist/jquery-1.11.2.min.js', '/app/js/dist/bootstrap.min.js'];
// INSTALLself.addEventListener('install', function(event) { // Perform install steps
event.waitUntil(caches.open(shellcache) //our cache! .then(function(cache) { console.log('Opened cache'); return cache.addAll(urlsToCache); //pass all our urls! }));
});
InstallGive assets you want to run offline
Setup and populate caches!
@thisNatasha
// filename: sw.js
// The files we want to cachevar shellcache = 'shell-v1';var urlsToCache = [ '/', '/app/css/dist/bootstrap.min.css', '/app/js/dist/angular.min.js', '/app/js/dist/jquery-1.11.2.min.js', '/app/js/dist/bootstrap.min.js'];
// INSTALLself.addEventListener('install', function(event) { // Perform install steps
event.waitUntil(caches.open(shellcache) //our cache! .then(function(cache) { console.log('Opened cache'); return cache.addAll(urlsToCache); //pass all our urls! }));
});
Install
opened cache!
Give assets you want to run offline
@thisNatasha
// filename: sw.js
// The files we want to cachevar shellcache = 'shell-v1';var urlsToCache = [ '/', '/app/css/dist/bootstrap.min.css', '/app/js/dist/angular.min.js', '/app/js/dist/jquery-1.11.2.min.js', '/app/js/dist/bootstrap.min.js'];
// INSTALLself.addEventListener('install', function(event) { // Perform install steps
event.waitUntil(caches.open(shellcache) //our cache! .then(function(cache) { console.log('Opened cache'); return cache.addAll(urlsToCache); //pass all our urls! }));
});
Install
pass in array of files
Give assets you want to run offline
@thisNatasha
// filename: sw.js
// The files we want to cachevar shellcache = 'shell-v1';var urlsToCache = [ '/', '/app/css/dist/bootstrap.min.css', '/app/js/dist/angular.min.js', '/app/js/dist/jquery-1.11.2.min.js', '/app/js/dist/bootstrap.min.js'];
// INSTALLself.addEventListener('install', function(event) { // Perform install steps
event.waitUntil(caches.open(shellcache) //our cache! .then(function(cache) { console.log('Opened cache'); return cache.addAll(urlsToCache); //pass all our urls! }));
});
Install
TIP: everything must cache at
install to be successful!
Give assets you want to run offline
@thisNatasha
FetchDetect network fetches
// detect fetchesself.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) {
…
}) );});
1st Visit: Install ServiceWorker
Later visits + navigation:
worker kicks in!
@thisNatasha
Fetch
// detect fetchesself.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) {
…
}) );});
Worker will receive “fetch”
events
Detect network fetches
@thisNatasha
Fetch
// detect fetchesself.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) {
…
}) );});
Worker will receive “fetch”
events
fetch API allows us to respond!
Detect network fetches
@thisNatasha
// detect fetchesself.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) {
…
}) );});
Fetch
Worker will receive “fetch”
events
Manual response, Network fetch, or cached resouce!
Detect network fetches
@thisNatasha
Fetch
// detect fetchesself.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) {
…
}) );});
Worker will receive “fetch”
events
gives info about request
Detect network fetches
@thisNatasha
// sw.jsself.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response; // cache hit! return response }
// IMPORTANT: Clone the request. var fetchRequest = event.request.clone();
return fetch(fetchRequest).then( function(response) { // Check if we received a valid response if(!response || response.status !== 200 || response.type !== 'basic') { return response; }
// IMPORTANT: Clone the response. var responseToCache = response.clone();
caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); });
return response; } ); }) );});
CachingDetected fetches, return match if so or get from network as fallback
pass promisecaches.match
@thisNatasha
// sw.jsself.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response; // cache hit! return response }
// IMPORTANT: Clone the request. var fetchRequest = event.request.clone();
return fetch(fetchRequest).then( function(response) { // Check if we received a valid response if(!response || response.status !== 200 || response.type !== 'basic') { return response; }
// IMPORTANT: Clone the response. var responseToCache = response.clone();
caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); });
return response; } ); }) );});
Caching
hmmm, any matches in my
caches?
Detected fetches, return match if so or get from network as fallback
@thisNatasha
Caching
// sw.jsself.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response; // cache hit! return response }
// IMPORTANT: Clone the request. var fetchRequest = event.request.clone();
return fetch(fetchRequest).then( function(response) { // Check if we received a valid response if(!response || response.status !== 200 || response.type !== 'basic') { return response; }
// IMPORTANT: Clone the response. var responseToCache = response.clone();
caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); });
return response; } ); }) );});
Got it! Awesome, so return it!
Detected fetches, return match if so or get from network as fallback
@thisNatasha
Caching
// sw.jsself.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response; // cache hit! return response }
// IMPORTANT: Clone the request. var fetchRequest = event.request.clone();
return fetch(fetchRequest).then( function(response) { // Check if we received a valid response if(!response || response.status !== 200 || response.type !== 'basic') { return response; }
// IMPORTANT: Clone the response. var responseToCache = response.clone();
caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); });
return response; } ); }) );});
Aw nothing in cache, get from
network
Detected fetches, return match if so or get from network as fallback
@thisNatasha
CachingGot it from the network? Why not add to the cache?
// sw.js return fetch(fetchRequest).then( function(response) { // Check if we received a valid response if(!response || response.status !== 200 || response.type !== 'basic') { return response; }
// IMPORTANT: Clone the response. A response is a stream // and because we want the browser to consume the response // as well as the cache consuming the response, we need // to clone it so we have 2 stream. var responseToCache = response.clone();
caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); });
return response; } ); }) );});
@thisNatasha
CachingGot it from the network? Why not add to the cache?
// sw.js return fetch(fetchRequest).then( function(response) { // Check if we received a valid response if(!response || response.status !== 200
|| response.type !== 'basic') { return response; }
// IMPORTANT: Clone the response. A response is a stream // and because we want the browser to consume the response // as well as the cache consuming the response, we need // to clone it so we have 2 stream. var responseToCache = response.clone();
caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); });
return response; } ); }) );});
check response
@thisNatasha
CachingGot it from the network? Why not add to the cache?
// sw.js return fetch(fetchRequest).then( function(response) { // Check if we received a valid response if(!response || response.status !== 200
|| response.type !== 'basic') { return response; }
// IMPORTANT: Clone the response. A response is a stream // and because we want the browser to consume the response // as well as the cache consuming the response, we need // to clone it so we have 2 stream. var responseToCache = response.clone();
caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); });
return response; } ); }) );});
clone response
@thisNatasha
CachingGot it from the network? Why not add to the cache?
// sw.js return fetch(fetchRequest).then( function(response) { // Check if we received a valid response if(!response || response.status !== 200
|| response.type !== 'basic') { return response; }
// IMPORTANT: Clone the response. A response is a stream // and because we want the browser to consume the response // as well as the cache consuming the response, we need // to clone it so we have 2 stream. var responseToCache = response.clone();
caches.open(CACHE_NAME) .then(function(cache) { cache.put(event.request, responseToCache); });
return response; } ); }) );});
add to cache
@thisNatasha
UpdateUpdating your ServiceWorker
// sw.jsself.addEventListener('activate', function(event) {
//var cacheWhitelist = ['shell-v1', 'blog-posts-cache-v1']; var cacheWhitelist = ['shell-v1'];
event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { console.log('deleting: ' + cacheName); return caches.delete(cacheName); } }) ); }) );});
@thisNatasha
UpdateUpdating your ServiceWorker
// sw.jsself.addEventListener('activate', function(event) {
//var cacheWhitelist = ['shell-v1', 'blog-posts-cache-v1']; var cacheWhitelist = ['shell-v1'];
event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { console.log('deleting: ' + cacheName); return caches.delete(cacheName); } }) ); }) );});
Edit and save me
@thisNatasha
UpdateUpdating your ServiceWorker
// sw.jsself.addEventListener('activate', function(event) {
//var cacheWhitelist = ['shell-v1', 'blog-posts-cache-v1']; var cacheWhitelist = ['shell-v1'];
event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { console.log('deleting: ' + cacheName); return caches.delete(cacheName); } }) ); }) );});
Edit and save me
TIP: completely close the
application!
@thisNatasha
Update
// sw.jsself.addEventListener('activate', function(event) {
//var cacheWhitelist = ['shell-v1', 'blog-posts-cache-v1']; var cacheWhitelist = ['shell-v1'];
event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { console.log('deleting: ' + cacheName); return caches.delete(cacheName); } }) ); }) );});
Updating your ServiceWorker
Open app and new SW should
kick in!
@thisNatasha
UpdateCheck caches need deleting or keeping
// sw.jsself.addEventListener('activate', function(event) {
//var cacheWhitelist = ['shell-v1', 'blog-posts-cache-v1']; var cacheWhitelist = ['shell-v1'];
event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { console.log('deleting: ' + cacheName); return caches.delete(cacheName); } }) ); }) );});
Let’s do some cache
management!
activate callback
source: html5rocks
@thisNatasha
UpdateCheck caches need deleting or keeping
// sw.jsself.addEventListener('activate', function(event) {
var cacheWhitelist = ['shell-v1', 'blog-posts-cache-v1'];
event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { console.log('deleting: ' + cacheName); return caches.delete(cacheName); } }) ); }) );});
Let’s do some cache
management!
defined in install step
source: html5rocks
@thisNatasha
UpdateCheck caches need deleting or keeping
// sw.jsself.addEventListener('activate', function(event) {
var cacheWhitelist = ['shell-v1', 'blog-posts-cache-v1'];
event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all( cacheNames.map(function(cacheName) { if (cacheWhitelist.indexOf(cacheName) === -1) { console.log('deleting: ' + cacheName); return caches.delete(cacheName); } }) ); }) );});
Let’s do some cache
management!
loop through and delete!
source: html5rocks
@thisNatasha
Cool Other Stuff!
What more can I do?
Push / Background Sync
Mess around with fallbacks
Pass in weird and wonderful responses!
@thisNatasha
Ok I see how this works, anything else I
should know?
TIP: Github pages are
served over HTTPS
@thisNatasha
New Questions
Super new! What else do we need to know!?
How does this scale?
Does SW improve performance?
How complex does development become?