Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JS@PayPal 2015 - Insanely fast rendering w/ Service Workers and Early Flushing

Mark Stuart
December 14, 2015

JS@PayPal 2015 - Insanely fast rendering w/ Service Workers and Early Flushing

Insanely fast rendering w/ Service Workers and Early Flushing

Mark Stuart

December 14, 2015
Tweet

More Decks by Mark Stuart

Other Decks in Programming

Transcript

  1. 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!
  2. 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();
  3. Try it out! Just a few headers. Took a lot

    of work to pull this off. Ready for production after moratorium.
  4. With early flushing, we render at 695ms. But, that’s still

    not fast enough. I’d like to introduce you to Service Workers!
  5. Apps that use Service Workers today - Facebook - Push

    notifications - Pinterest - Push notifications - Medium - Caching - Google’s Inbox app - Caching & Push notifications
  6. Browser Support - Chrome 40+ - Firefox 44+ - Opera

    24+ - Android 46 - Safari and IE to come later.
  7. - 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
  8. Features - Able to intercept and handle network requests -

    Complete control of the browser’s cache - Push notifications - Background sync - Geofencing
  9. 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; }); }); }); ); }); ! ! !
  10. 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); }); }
  11. 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)); });
  12. Supporting WebP images ! ! ! <img src="icon-sprite.png" /> 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)); });
  13. Responsive images ! ! ! <img src="icon-sprite.png" /> 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 “[email protected]” 3. Request that from the network
  14. 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.
  15. 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
  16. 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.
  17. 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
  18. 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