service workers for performance

Post on 07-Aug-2015

2.803 Views

Category:

Technology

3 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Service Workers

The Practical Bits

@patmeenan

Patrick Meenan

Slides

Slideshare: http://www.slideshare.net/patrickmeenan

Twitter: @patmeenan (and to #velocityconf)

Velocity Site, attached to session information

Video: https://www.youtube.com/user/patmeenan

Office Hours:  Friday, May 29 from 12:30pm-1:00pm@patmeenan

Chrome Team @Google (performance)

WebPageTest

pmeenan@webpagetest.org

@patmeenan

The Real Experts

Alex Russell@slightlylate

Jake Archibald@jaffathecake

Service Workers

DOM

Resource Fetching in Chrome

DOM

Resource Fetching in Chrome

In-memory Resource

Cache

DOM

Resource Fetching in Chrome

In-memory Resource

Cache

Resource Fetcher

DOM

Resource Fetching in Chrome

Resource Fetcher

Net Stack

DOM

Resource Fetching in Chrome

In-memory Resource

Cache

Resource Fetcher

Net Stack

Disk Cache

DOM

Resource Fetching in Chrome

In-memory Resource

Cache

Resource Fetcher

Net Stack Net

Disk Cache

DOM

Resource Fetching in Chrome

In-memory Resource

Cache

Resource Fetcher

Net Stack Net

Disk Cache

Service Worker

SW Added and Removed

Here!

CapabilitiesSees every request for your document

- Including cross-origin

- And headersCan synthesize responsesSupports fetchHas a programmable cache and Indexed DB

LimitationsHTTPS documents onlyNot active for first viewiFrames are separate documentsNon-CORS Cross-origin responses are opaqueNo global state

- or concept of a “page”No Sync API’s (localstorage, XHR, etc)

http://memegenerator.net/instance/62336209

Registering

if ('serviceWorker' in navigator) {

navigator.serviceWorker.register('/my-app/sw.js', {

scope: '/my-app/'

});

}

https://github.com/slightlyoff/ServiceWorker/blob/master/explainer.md

Registering

if ('serviceWorker' in navigator) {

navigator.serviceWorker.register('/my-app/sw.js', {

scope: '/my-app/'

});

}

https://github.com/slightlyoff/ServiceWorker/blob/master/explainer.md

Registering

if ('serviceWorker' in navigator) {

navigator.serviceWorker.register('/my-app/sw.js', {

scope: '/my-app/'

});

}

https://github.com/slightlyoff/ServiceWorker/blob/master/explainer.md

Registering

if ('serviceWorker' in navigator) {

navigator.serviceWorker.register('/my-app/sw.js', {

scope: '/my-app/'

});

}

https://github.com/slightlyoff/ServiceWorker/blob/master/explainer.md

Install / activateself.addEventListener('install', function(event) {

event.waitUntil(

fetchStuffAndInitDatabases()

);

});

self.addEventListener('activate', function(event) {

// You're good to go!

});

https://github.com/slightlyoff/ServiceWorker/blob/master/explainer.md

Network Intercepting

self.addEventListener('fetch', function(event) {

event.respondWith(new Response("Hello world!"));

});

https://github.com/slightlyoff/ServiceWorker/blob/master/explainer.md

Enough Theory – Let’s Use It

Offline Content

Most benefit for Single-Page Apps where app/data are separated

Service Workers become progressive enhancement for offline support

Offline Content

Pre-cache offline content

Pass live requests through when online

- Caching the responses

Serve cached responses when offline (if available)

Serve offline-specific versions otherwise

Offline Content – Pre-caching

self.addEventListener( ‘install’, function(event) {

event.waitUntil(

caches.open(‘my-offline-cache-v1’).then( function(cache) {

return cache.addAll([

‘/site.js’,

‘/images/offline.png’,

‘/offline.html’]);

}));});

Offline Content – Pre-caching

self.addEventListener( ‘install’, function(event) {

event.waitUntil(

caches.open(‘my-offline-cache-v1’).then( function(cache) {

return cache.addAll([

‘/site.js’,

‘/images/offline.png’,

‘/offline.html’]);

}));});

Offline Content – Pre-caching

self.addEventListener( ‘install’, function(event) {

event.waitUntil(

caches.open(‘my-offline-cache-v1’).then( function(cache) {

return cache.addAll([

‘/site.js’,

‘/images/offline.png’,

‘/offline.html’]);

}));});

Offline Content – Pre-caching

self.addEventListener( ‘install’, function(event) {

event.waitUntil(

caches.open(‘my-offline-cache-v1’).then( function(cache) {

return cache.addAll([

‘/site.js’,

‘/images/offline.png’,

‘/offline.html’]);

}));});

Offline Content – Pre-caching

self.addEventListener( ‘install’, function(event) {

event.waitUntil(

caches.open(‘my-offline-cache-v1’).then( function(cache) {

return cache.addAll([

‘/site.js’,

‘/images/offline.png’,

‘/offline.html’]);

}));});

Offline Content – Fetch Processing

self.addEventListener( 'fetch', function(event) {

if (navigator.online) {

event.respondWith( onlineRequest(event.request) );

} else {

event.respondWith( offlineRequest(event.request) );

}

});

Offline Content – Fetch Processing

self.addEventListener( 'fetch', function(event) {

if (navigator.online) {

event.respondWith( onlineRequest(event.request) );

} else {

event.respondWith( offlineRequest(event.request) );

}

});

Offline Content – Fetch Processing

self.addEventListener( 'fetch', function(event) {

if (navigator.online) {

event.respondWith( onlineRequest(event.request) );

} else {

event.respondWith( offlineRequest(event.request) );

}

});

Offline Content – Fetch Processing

self.addEventListener( 'fetch', function(event) {

if (navigator.online) {

event.respondWith( onlineRequest(event.request) );

} else {

event.respondWith( offlineRequest(event.request) );

}

});

Offline Content – Fetch Processing

self.addEventListener( 'fetch', function(event) {

if (navigator.online) {

event.respondWith( onlineRequest(event.request) );

} else {

event.respondWith( offlineRequest(event.request) );

}

});

Offline Content – Online Response

function onlineRequest(request) {

return caches.match(request)

.then(function(response) {

}

);

}

Offline Content – Online Response

function onlineRequest(request) {

return caches.match(request).then(function(response) {

if (response) {

return response;

} else {

return fetch(request);

}

});

}

Offline Content – Online Response

function onlineRequest(request) {

return caches.match(request).then(function(response) {

if (response) {

return response;

} else {

return fetch(request);

}

});

}

Offline Content – Online Response…

var fetchRequest = request.clone();

return fetch(fetchRequest).then( function(response) {

var responseToCache = response.clone();

caches.open(CACHE_NAME).then( function(cache) {

cache.put(request, responseToCache);

});

return response;

});

Offline Content – Online Response…

var fetchRequest = request.clone();

return fetch(fetchRequest).then( function(response) {

var responseToCache = response.clone();

caches.open(CACHE_NAME).then( function(cache) {

cache.put(request, responseToCache);

});

return response;

});

Offline Content – Online Response…

var fetchRequest = request.clone();

return fetch(fetchRequest).then( function(response) {

var responseToCache = response.clone();

caches.open(CACHE_NAME).then( function(cache) {

cache.put(request, responseToCache);

});

return response;

});

Offline Content – Online Response…

var fetchRequest = request.clone();

return fetch(fetchRequest).then( function(response) {

var responseToCache = response.clone();

caches.open(CACHE_NAME).then( function(cache) {

cache.put(request, responseToCache);

});

return response;

});

Offline Content – Online Response…

var fetchRequest = request.clone();

return fetch(fetchRequest).then( function(response) {

var responseToCache = response.clone();

caches.open(CACHE_NAME).then( function(cache) {

cache.put(request, responseToCache);

});

return response;

});

Offline Content – Online Response…

var fetchRequest = request.clone();

return fetch(fetchRequest).then( function(response) {

var responseToCache = response.clone();

caches.open(CACHE_NAME).then( function(cache) {

cache.put(request, responseToCache);

});

return response;

});

Offline Content – Offline Response

function offlineRequest(request) {

if (request.url.match(/\.png$/)) {

return caches.match(‘/images/offline.png’);

} else if (request.url.match(/\.html$/)) {

return caches.match(‘/offline.html’);

}

}

Offline Content – Offline Response

function offlineRequest(request) {

if (request.url.match(/\.png$/)) {

return caches.match(‘/images/offline.png’);

} else if (request.url.match(/\.html$/)) {

return caches.match(‘/offline.html’);

}

}

Other Uses

Not limited to applications designed for offline/SPALegacy apps + fetch intercept = Awesome possibilities

Custom Error Pages

Fetch Request NormallyFor non-200 responses serve a local error pageNot just server errors:

- DNS failures

- CDN/Proxy Errors

- Intermediaries

SPOF Prevention

Identify requests of interest

- 3rd-party javascript

- FontsSet a timerPass fetch requests through If timer expires before fetch completes generate error response

CDN/Origin Failover

Identify CDN requests by URLSet a timerPass fetch request through If timer expires, create fetch request to originRespond with first fetch request to complete

Multi-Origin/CDN

Identify CDN requests by URLReplace CDN domain with alternate CDN (or origin)Keep track of performance by originPrefer faster origin (keep measuring)

Could also race the CDNs in parallel

- Be careful about increased data

Stale-While-Revalidate

Respond with local cached resource

- Ignoring Expires, max-age, etc.Pass fetch request through in parallelUpdate cache with new response

Works best for known resources (analytics/ads JS, etc)

Prefetch

Custom response headers with prefetch resourcesWhen idle, prefetch suggested resources

Resource Prioritization/Scheduling

Track in-flight requestsMake scheduling decisions as new requests come inCustom application-aware scheduling logic

- Delay JS if it is known to be at the end

- Delay footer images

- etc

Delta CompressionDelta compression for build->build updates

- Include version in URL scheme

- Get latest version number from cache

- Request delta from server

- Apply patch

- Cache new version

New compression algorithms

Custom CompressionNew compression algorithms

- Brotli

- Fractal image compression

- JSON-specific dictionaries

- Application-specificProve-out new algorithms before standardizing…as long as it can be implemented in JS and code size is

reasonable

Incremental Progressive Images

Identify JPEG image requests from known originSynthesize responseRange request (or smarter) for first few scansStream initial range into synthesized responseRange request for remaining image (some point later)Append remaining data into synthesized response

Drawing Images Locally

930 x 11,362 px WebPageTest waterfall

351KB compressed PNG

42 MB in-memory (server) to generate

< 20KB compressed JSON data to describe

Prefer actual images for WPT waterfalls for easier embedding

- Otherwise SVG, Canvas or DOM would work

Drawing Images Locally*

Identify appropriate requests (i.e. waterfall.png?...)Fetch data necessary to draw imageDraw to canvasExtract as PNGSynthesize PNG response

* Work in progress, coming soon

Metrics/Debugging

Pass all requests throughPassively observe timings and successReport metrics backGives visibility into failures and requests that are in flight

- Unlike resource timing

And there’s more…

Required for…Push Notifications

- https://gauntface.com/blog/2014/12/15/push-notifications-service-worker

Background Sync- https://github.com/slightlyoff/BackgroundSync/blob/master/explainer.md

GeoFencing- https://github.com/slightlyoff/Geofencing

AvailabilityChrome Stable (40+)Opera (27+)Firefox (Nightly)

Composable

Service workers can “importScripts”Begging for a framework to handle pluggable pipeline

- With compatible plugins for processing

- Delta compression + scheduling + Stale While Revalidate…

- Just a matter of importing the libs

Thank You!

@patmeenan

pmeenan@webpagetest.org

top related