Slide 1

Slide 1 text

Insanely Fast Rendering! w/ Early Flushing and Service Workers JS@PayPal Winter 2015 - 12/14

Slide 2

Slide 2 text

go/serviceworkers

Slide 3

Slide 3 text

Hi, I’m Mark! @mark_stuart @marstuart

Slide 4

Slide 4 text

Let's talk about pushing pixels to the browser as fast as possible.

Slide 5

Slide 5 text

Early Flushing

Slide 6

Slide 6 text

By default, HTTP responses are buffered.

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Browsers can’t render anything until that 1st HTML response is received. :(

Slide 10

Slide 10 text

OK, so what can we do about it?

Slide 11

Slide 11 text

What if we chunked up that HTML response into multiple parts?

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

Browsers can start downloading and parsing critical JS and CSS before the HTML response is finished.

Slide 14

Slide 14 text

No Early Flush DOMLoaded- 3.5s Load - 3.8s First paint - 3.4s Early Flush DOMLoaded- 3.4s Load - 3.5s First paint - 695ms Saved 2.8s!

Slide 15

Slide 15 text

Good news! You can use this too!

Slide 16

Slide 16 text

Add a few headers… Flush out your scripts, styles, etc. Call your services, resolve locale, whatever else, then flush again. ! ! ! 1 res.setHeader('Transfer-Encoding', 'chunked'); 2 3 // For Slingshot... 4 res.setHeader('X-SLR-EARLY-FLUSH', '1'); 5 6 // For Akamai... 7 res.setHeader('X-Akamai-Stream', 'True'); res.write(renderTemplate('first-flush.html', context)); res.write(renderTemplate('second-flush.html', context)); res.end();

Slide 17

Slide 17 text

Try it out! Just a few headers. Took a lot of work to pull this off. Ready for production after moratorium.

Slide 18

Slide 18 text

With early flushing, we render at 695ms. But, that’s still not fast enough. I’d like to introduce you to Service Workers!

Slide 19

Slide 19 text

Flush early!

Slide 20

Slide 20 text

Service Workers

Slide 21

Slide 21 text

Apps that use Service Workers today - Facebook - Push notifications - Pinterest - Push notifications - Medium - Caching - Google’s Inbox app - Caching & Push notifications

Slide 22

Slide 22 text

Browser Support - Chrome 40+ - Firefox 44+ - Opera 24+ - Android 46 - Safari and IE to come later.

Slide 23

Slide 23 text

What’s a Service Worker?

Slide 24

Slide 24 text

- Similar to a Web Worker - Can’t directly access the DOM - Communicates w/ parent by postMessage - Can import scripts via importScripts - Promise-based APIs - Easy debugging

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

Features - Able to intercept and handle network requests - Complete control of the browser’s cache - Push notifications - Background sync - Geofencing

Slide 27

Slide 27 text

Most exciting feature? Being able to intercept and handle network requests!

Slide 28

Slide 28 text

A few interesting use cases

Slide 29

Slide 29 text

Serving assets from cache self.addEventListener('fetch', function (event) { event.respondWith( caches.open('my-cool-cache').then(function (cache) { return cache.match(event.request).then(function (response) { if (response) { return response; } return fetch(event.request.clone()).then(function (response) { if (response.status < 400) { cache.put(event.request, response.clone()); } return response; }); }); }); ); }); ! ! !

Slide 30

Slide 30 text

Enforce SLA on 3rd party scripts ! ! ! self.addEventListener('fetch', function (event) { // If it's a 3rd party script, enforce a 2s SLA, then timeout if (thirdPartyScripts.includes(event.request.url)) { return event.respondWith(Promise.race([ timeout(2000), fetch(event.request.url) ])); } event.respondWith(fetch(event.request.url)); }); // FPTI and Fraudnet scripts var thirdPartyScripts = [ 'https://www.paypalobjects.com/pa/js/pa.js', 'https://www.paypalobjects.com/webstatic/r/fb/fb-all-prod.pp.min.js' ]; function timeout(delay) { return new Promise(function (resolve) { setTimeout(function () { resolve(new Response('', { status: 408, statusText: 'Request timed out' })); }, delay); }); }

Slide 31

Slide 31 text

Enforce SLA on 3rd party scripts ! ! ! // FPTI and Fraudnet scripts var thirdPartyScripts = [ 'https://www.paypalobjects.com/pa/js/pa.js', 'https://www.paypalobjects.com/webstatic/r/fb/fb-all-prod.pp.min.js' ]; function timeout(delay) { return new Promise(function (resolve) { setTimeout(function () { resolve(new Response('', { status: 408, statusText: 'Request timed out' })); }, delay); }); } self.addEventListener('fetch', function (event) { // If it's a 3rd party script, enforce a 2s SLA, then timeout if (thirdPartyScripts.includes(event.request.url)) { return event.respondWith(Promise.race([ timeout(2000), fetch(event.request.url) ])); } event.respondWith(fetch(event.request.url)); });

Slide 32

Slide 32 text

Supporting WebP images ! ! ! self.addEventListener('fetch', function (event) { var request = event.request; var url = request.url; var isImage = /\.jpg$|.png$/.test(request.url); var supportsWebp = request.headers.get('accept').includes('webp'); if (isImage && supportsWebp) { url = request.url.substr(0, request.url.lastIndexOf('.')) + '.webp'; } return event.respondWith(fetch(url)); });

Slide 33

Slide 33 text

Responsive images ! ! ! Use images like you normally would… 1. Intercept that image request 2. If “retina” support (or device-pixel-ratio >= 2), re-write the URL as “icon-sprite@2x.png” 3. Request that from the network

Slide 34

Slide 34 text

Defer requests while offline ! ! ! If a network request fails, or user goes offline… You can cache requests and re-play them when the user goes back online.

Slide 35

Slide 35 text

Mock Server? lol ! ! ! Might be a really crazy idea, but it’s possible! Need to run in mocks mode? Use a Service Worker! Intercept API responses and send back mock responses No need to run a server

Slide 36

Slide 36 text

Service Workers are great for PayPal use cases - Offline support - Send/Request money (P2P) - Rendering shells - Dashboard apps like Hawk and 8Ball all have this “shell” on the ourside that could be instantly rendered while the remaining bits are loaded afterwards.

Slide 37

Slide 37 text

Application Shell Architecture - Coined by Addy Osmani, from the Chrome team - With Service Workers, you can instantly load your page on repeat visits. - Render the Shell or Chrome of your page immediately. - Lazy load in the rest of your page

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

Our Caching Strategy (Starting Q1) - Always read static/framework assets from cache (jQuery, Angular, etc.) - Always immediately render "shell" from cache - Fetch from network, by default. If success, cache response. - If any network issues or fallbacks, read from cache. - Add a 2 second SLA on 3rd party scripts

Slide 40

Slide 40 text

Questions? #serviceworkers on Node.js Slack (nodejs.slack.com)