@lyzadanger | www.lyza.com
Smashing Freiburg 2016
https://github.com/lyzadanger/pragmatist-service-worker
Text
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js');
}
</script>
index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Service Workers: Offline Message</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/default.css" rel="stylesheet" title="Default Style">
</head>
<body>
<p>If you see this, you are online</p>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js');
}
</script>
</body>
</html>
self.addEventListener(
);
service-worker.js
self.addEventListener('fetch',
);
self.addEventListener('fetch', event => {
});
self.addEventListener('fetch', event => {
});
service-worker.js
if (event.request.mode === 'navigate') {
}
event.respondWith(/* ... */);
self.addEventListener('fetch', event => {
});
service-worker.js
if (event.request.mode === 'navigate') {
}
event.respondWith( ));
event.respondWith(fetch(event.request));
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
event.respondWith(fetch(event.request));
}
});
service-worker.js
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
event.respondWith(fetch(event.request));
}
});
service-worker.js
event.respondWith(fetch(event.request));
fetch(event.request)
fetch(event.request)
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
}
});
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
event.respondWith(
);
}
});
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request)
);
}
});
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch(error => {
})
);
}
});
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch(error => {
return new Response();
})
);
}
});
self.addEventListener('fetch', event => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch(error => {
return new Response('<p>Oh, dear.</p>',
{ headers: { 'Content-Type': 'text/html' } });
})
);
}
});
if (event.request.mode === 'navigate')
service-worker.js
if (event.request.mode === 'navigate' ||
(event.request.method === 'GET' &&
if (event.request.mode === 'navigate' ||
(event.request.method === 'GET' &&
event.request.headers.get('accept').includes('text/html')))
event.respondWith(
/* Promise resolving to a Response, ideally */
);
event.respondWith(
fetch(request)
);
event.respondWith(
fetch(request).catch(error => { })
);
event.respondWith(
fetch(request).catch(error => {
return new Response(...);
})
);
event.respondWith(
fetch(request).catch(error => {
return new Response('<p>Oh, dear.</p>');
})
);
event.respondWith(
fetch(request).catch(error => {
return new Response('<p>Oh, dear.</p>',
{
headers: { 'Content-Type': 'text/html' }
}
);
})
);
service-worker.js
self.addEventListener('install', event => {
});
var offlineURL = 'offline.html';
event.waitUntil(
/* Gotta stick offline page in cache */
);
self.addEventListener('install', event => {
var offlineURL = 'offline.html';
event.waitUntil(
fetch(...).then(...)
);
});
service-worker.js
self.addEventListener('install', event => {
var offlineURL = 'offline.html';
event.waitUntil(
fetch(...).then(...)
);
});
self.addEventListener('install', event => {
var offlineURL = 'offline.html';
event.waitUntil(
fetch(new Request(offlineURL)).then(...)
);
});
self.addEventListener('install', event => {
var offlineURL = 'offline.html';
event.waitUntil(
fetch(new Request(offlineURL)).then(response => {
})
);
});
self.addEventListener('install', event => {
var offlineURL = 'offline.html';
event.waitUntil(
fetch(new Request(offlineURL)).then(response => {
})
);
});
service-worker.js
return caches.open('offline').then(cache => {
});
self.addEventListener('install', event => {
var offlineURL = 'offline.html';
event.waitUntil(
fetch(new Request(offlineURL)).then(response => {
return caches.open('offline').then(cache => {
});
})
);
});
service-worker.js
return cache.put(offlineURL, response);
self.addEventListener('fetch', event => {
var request = event.request;
if (request.mode === 'navigate' ||
(request.method === 'GET' &&
request.headers.get('accept').includes('text/html'))) {
event.respondWith(
);
}
});
service-worker.js
self.addEventListener('fetch', event => {
var request = event.request;
if (request.mode === 'navigate' ||
(request.method === 'GET' &&
request.headers.get('accept').includes('text/html'))) {
event.respondWith(
fetch(request)
})
);
}
});
self.addEventListener('fetch', event => {
var request = event.request;
if (request.mode === 'navigate' ||
(request.method === 'GET' &&
request.headers.get('accept').includes('text/html'))) {
event.respondWith(
fetch(request).catch(error => {
})
);
}
});
self.addEventListener('fetch', event => {
var request = event.request;
if (request.mode === 'navigate' ||
(request.method === 'GET' &&
request.headers.get('accept').includes('text/html'))) {
event.respondWith(
fetch(request).catch(error => {
return caches.open('offline')
})
);
}
});
self.addEventListener('fetch', event => {
var request = event.request;
if (request.mode === 'navigate' ||
(request.method === 'GET' &&
request.headers.get('accept').includes('text/html'))) {
event.respondWith(
fetch(request).catch(error => {
return caches.open('offline').then(cache => {
});
})
);
}
});
self.addEventListener('fetch', event => {
var request = event.request;
if (request.mode === 'navigate' ||
(request.method === 'GET' &&
request.headers.get('accept').includes('text/html'))) {
event.respondWith(
fetch(request).catch(error => {
return caches.open('offline').then(cache => {
return cache.match('offline.html');
});
})
);
}
});
event.respondWith(
fetch(request).catch(error => {
})
);
service-worker.js
return caches.open('offline').then(cache => {
});
return caches.open('offline').then(cache => {
return cache.match('offline.html');
});
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js');
}
index.html
navigator
register
feature testing
service worker file location
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js'
);
}
index.html
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js',
{ scope : './' }
);
}
self.addEventListener('fetch', event => {
var request = event.request;
});
service-worker.js
if (isNavigateRequest(request)) {
}
// Handle request for HTML: network-first
} else if (isImageRequest(request)) {
// Handle request for images: cache-first
}
self.addEventListener('fetch', event => {
var request = event.request;
if (isNavigateRequest(request)) {
// Handle request for HTML: network-first
} else if (isImageRequest(request)) {
// Handle request for images: cache-first
}
});
function isNavigateRequest (request) {
return (request.mode === 'navigate' ||
(request.method === 'GET' &&
request.headers.get('accept').includes('text/html')));
}
function isImageRequest (request) {
return (request.headers.get('Accept').indexOf('image') !== -1);
}
if (isNavigateRequest(request)) {
}
service-worker.js
event.respondWith(
fetch(request)
);
.then(response => addToCache(request, response))
function addToCache (request, response) {
}
service-worker.js
// You do not want to cache a bad response!
if (response.ok) {
}
// For Promise chaining
return response;
// A Response can only be "used" once
const copy = response.clone();
caches.open('assets').then(cache => {
cache.put(request, copy);
});
if (isNavigateRequest(request)) {
}
service-worker.js
event.respondWith(
fetch(request)
);
.then(response => addToCache(request, response))
.catch(() => fetchFromCache(request))
function fetchFromCache (request) {
}
service-worker.js
return caches.match(request).then(response => {
});
function fetchFromCache (request) {
}
service-worker.js
return caches.match(request)
);
if (!response) {
throw Error(`${request.url} not found in cache`);
}
return response;
return caches.match(request).then(response => {
});
if (isNavigateRequest(request)) {
event.respondWith(
fetch(request)
.then(response => addToCache(request, response))
.catch(() => fetchFromCache(request))
.catch(() => offlinePage())
);
}
service-worker.js
function offlinePage () {
return caches.open('offline').then(cache => {
return cache.match('offline.html');
});
}
else if (isImageRequest(request)) {
event.respondWith(
);
}
service-worker.js
fetchFromCache(request) // Cache first
.catch(() => fetch(request) // But if not in cache, fetch
// If fetch was successful, add to cache for later
.then(response => addToCache(request, response)))
// If fetch failed...
.catch(() => console.log('unable to respond to request'))
service-worker.js
const cacheFiles = [
'',
'default.css',
'static-assets/cloud-1.jpg',
'static-assets/cloud-2.jpg',
'static-assets/cloud-3.jpg',
'static-assets/cloud-4.jpg',
'static-assets/cloud-5.jpg',
'static-assets/cloud-6.jpg',
'static-assets/cloud-7.jpg',
'static-assets/cloud-8.jpg',
'static-assets/cloud-9.jpg',
'static-assets/cloud-10.jpg'
];
service-worker.js
self.addEventListener('install', event => {
event.waitUntil(
);
});
self.addEventListener('install', event => {
event.waitUntil(
caches.open('shell').then(cache => {
})
);
});
service-worker.js
self.addEventListener('install', event => {
event.waitUntil(
caches.open('shell').then(cache => {
})
);
});
self.addEventListener('install', event => {
event.waitUntil(
caches.open('shell').then(cache => {
return cache.addAll(cacheFiles);
})
);
});
self.addEventListener('install', event => {
event.waitUntil(
caches.open('shell').then(cache => {
return cache.addAll(cacheFiles);
}).then(() => self.skipWaiting())
);
});
index.html
self.addEventListener('fetch', event => {
var request = event.request;
});
self.addEventListener('fetch', event => {
var request = event.request;
var url = new URL(request.url);
});
self.addEventListener('fetch', event => {
var request = event.request;
var url = new URL(request.url);
if (cacheFiles.indexOf(url.pathname) !== -1) {
}
});
self.addEventListener('fetch', event => {
var request = event.request;
var url = new URL(request.url);
if (cacheFiles.indexOf(url.pathname) !== -1) {
event.respondWith(
fetchFromCache(request)
);
}
});
self.addEventListener('fetch', event => {
var request = event.request;
var url = new URL(request.url);
if (cacheFiles.indexOf(url.pathname) !== -1) {
event.respondWith(
fetchFromCache(request)
.catch(() => fetch(request))
);
}
});
const cachePrefix = 'mission-05';
service-worker.js
self.addEventListener('activate', event => {
event.waitUntil(
);
});
service-worker.js
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheKeys => {
})
);
});
service-worker.js
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheKeys => {
})
);
});
var oldCacheKeys = cacheKeys.filter(key => {
return (key.indexOf(cachePrefix) !== 0);
});
var deletePromises = oldCacheKeys.map(oldKey => {
return caches.delete(oldKey);
});
return Promise.all(deletePromises);
service-worker.js
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheKeys => {
var oldCacheKeys = cacheKeys.filter(key => {
return (key.indexOf(cachePrefix) !== 0);
});
var deletePromises = oldCacheKeys.map(oldKey => {
return caches.delete(oldKey);
});
return Promise.all(deletePromises);
})
);
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheKeys => {
var oldCacheKeys = cacheKeys.filter(key => {
return (key.indexOf(cachePrefix) !== 0);
});
var deletePromises = oldCacheKeys.map(oldKey => {
return caches.delete(oldKey);
});
return Promise.all(deletePromises);
}).then(() => self.clients.claim())
);
});
@lyzadanger | lyza.com