PWAs: the Application Shell & the well of surprises

PWAs: the Application Shell & the well of surprises

When it comes to performance, we’ll usually take all the help we can get. The app shell model – an architecture for building PWAs – can make a huge difference… but better keep in mind it may hold a few surprises for you.

39faa5e8e938d2fbd92acc2e8ebdf65b?s=128

Stratos Pavlakis

November 30, 2018
Tweet

Transcript

  1. the Application Shell …and the well of surprises

  2. Stratos Pavlakis Software guy @Blueground th3hunt

  3. the Application Shell Why?

  4. Web Performance a short primer

  5. domContentLoaded navigationStart loadEventEnd time to first paint time to first

    paint time to interactive time to first byte frames per second page load time speed Index time to first ad first contentful paint first meaningful paint hero elements domainLookupStart
  6. critical rendering path

  7. Network Rendering

  8. Request Page GET
 /page Network Rendering

  9. Request Page Start building DOM wait GET
 /page Network Rendering

    HMTL
  10. Request Page Start building DOM wait GET
 /page Network Rendering

    GET
 /style GET
 /js HMTL
  11. Request Page Start building DOM Build CSSOM wait block GET


    /page Network Rendering GET
 /style GET
 /js CSS JS HMTL
  12. Request Page Start building DOM Build CSSOM Run JS wait

    Continue DOM block GET
 /page Network Rendering GET
 /style GET
 /js CSS JS HMTL
  13. Request Page Start building DOM Build CSSOM Run JS wait

    Continue DOM block style / layout / paint / comp GET
 /page Network Rendering GET
 /style GET
 /js CSS JS HMTL
  14. Request Page Start building DOM Build CSSOM Run JS wait

    Continue DOM block styl GET
 /page Network Rendering GET
 /style GET
 /js CSS JS HMTL
  15. Network Rendering

  16. Network Rendering CPU / GPU

  17. Network Rendering CPU / GPU Speed of Light

  18. speed of light places a hard limit
 on the network

    latency!
  19. imagine…

  20. a single byte 1 1 0 1 1 0 1

    0
  21. in New York

  22. distance light in vacuum light in fiber RTT London 5,585km

    19ms 28ms 56ms Sydney 15,993km 53ms 80ms 160ms Thess 7,695km 26ms 38ms 76ms - Browser Networking, Ilya Grigorik Getting a byte from New York
  23. 56 ms ?

  24. you wish…

  25. Client (London) Server (New York) TCP TLS

  26. Client (London) Server (New York) 0 ms SYN 28 ms

    TCP TLS
  27. Client (London) Server (New York) 0 ms SYN 28 ms

    SYN ACK ACK 56 ms TCP TLS
  28. Client (London) Server (New York) 0 ms SYN 28 ms

    SYN ACK ACK 56 ms 140 ms ChangeCipherSpec
 Finished ClientHello 84 ms ClientKeyExchange
 ChangeCipherSpec 
 Finished
 ServerHello Cert
 ServerHelloDone
 112 ms 168 ms TCP TLS
  29. Client (London) Server (New York) 0 ms SYN 28 ms

    SYN ACK ACK 56 ms Application 196 ms 140 ms ChangeCipherSpec
 Finished ClientHello 84 ms ClientKeyExchange
 ChangeCipherSpec 
 Finished
 ServerHello Cert
 ServerHelloDone
 112 ms 168 ms TCP TLS
  30. Client (London) Server (New York) 0 ms SYN 28 ms

    SYN ACK ACK 56 ms Application 196 ms 140 ms ChangeCipherSpec
 Finished ClientHello 84 ms ClientKeyExchange
 ChangeCipherSpec 
 Finished
 ServerHello Cert
 ServerHelloDone
 112 ms 168 ms Application 224 ms TCP TLS
  31. Client (London) Server (New York) 0 ms SYN 28 ms

    SYN ACK ACK 56 ms Application 196 ms 140 ms ChangeCipherSpec
 Finished ClientHello 84 ms ClientKeyExchange
 ChangeCipherSpec 
 Finished
 ServerHello Cert
 ServerHelloDone
 112 ms 168 ms Application 224 ms 11011010 TCP TLS
  32. 224 ms

  33. What if I send 1MB in 224ms?

  34. well…

  35. meet TCP Slow Start

  36. Client (London) Server (New York) TCP Slow Start

  37. Client (London) Server (New York) TCP Slow Start

  38. Client (London) Server (New York) TCP Slow Start

  39. Client (London) Server (New York) TCP Slow Start

  40. Client (London) Server (New York) TCP Slow Start

  41. 224ms + 2*56ms 3 * RTT for 64KB with init_cwnd

    = 10
  42. “Even for small payloads network places a hard limit on

    speed”
  43. Ok ok… but how bad can a few 100s of

    milliseconds be?
  44. Delay User Perception 0 - 100ms Instant 100 - 300ms

    Perceptible delay 300 - 1000ms Machine is working 1000ms+ Likely mental context switch time and user perception Browser Networking - Ilya Grigorik
  45. So… how do we deal with Mr. Network?

  46. dealing with network latency

  47. 1

  48. Edge Caching 1

  49. 1 Edge Caching

  50. 1 Edge Caching

  51. Html Css Js Img Html Css Js Img Html Css

    Js Img Html Css Js Img Html Css Js Img 1 Edge Caching Html Css Js Img
  52. Html Css Js Img Html Css Js Img Html Css

    Js Img Html Css Js Img Html Css Js Img 1 Edge Caching Html Css Js Img
  53. Html Css Js Img Html Css Js Img Html Css

    Js Img Html Css Js Img Html Css Js Img 1 Edge Caching Html Css Js Img Static / JAMStack
  54. 2

  55. HTTP2 Push 2

  56. HTTP2 Push 2

  57. HTTP2 Push 2 GET /index.html

  58. HTTP2 Push 2 GET /index.html styles.css + app.js index.html

  59. 3

  60. Service Workers 3

  61. 3 Service Workers

  62. 3 Service Workers

  63. 3 Service Workers •Proxies of our own

  64. 3 Service Workers •Proxies of our own •Live in the

    browser
  65. 3 Service Workers •Proxies of our own •Live in the

    browser •Domain & path scoped
  66. 3 Service Workers •Proxies of our own •Live in the

    browser •Domain & path scoped •Async by spec
  67. 3 Service Workers •Proxies of our own •Live in the

    browser •Domain & path scoped •Async by spec •Utilize Cache API
  68. 3 Service Workers •Proxies of our own •Live in the

    browser •Domain & path scoped •Async by spec •Utilize Cache API •Run on separate thread
  69. the app shell

  70. server browser

  71. server browser cache

  72. server browser GET /index.html cache

  73. server browser index.html GET /index.html cache

  74. service worker server browser cache

  75. service worker server browser GET /index.html cache

  76. service worker server browser GET /index.html index.html cache

  77. service worker server browser GET /index.html index.html index.html cache

  78. service worker server browser cache

  79. service worker server browser GET /app.js, /style.css app.js, style.css cache

  80. service worker server browser GET /app.js, /style.css app.js, style.css app.js

    style.js cache
  81. service worker cache server browser

  82. service worker cache server browser GET /index.html /style.css /app.js /logo.png

    /background.jpg
  83. service worker cache server browser XHR XHR

  84. Lifecycle

  85. Lifecycle

  86. Lifecycle

  87. Lifecycle

  88. Lifecycle pre-cache the shell (html, js, css, fonts)

  89. Lifecycle pre-cache the shell (html, js, css, fonts) Cache runtime

    assets as they come (e.g. posted images)
  90. None
  91. Robert, a software engineer

  92. Request Page Start building DOM Build CSSOM Run JS wait

    Continue DOM block style / layout / paint / comp GET
 /page GET
 /style GET
 /js css js html with a critical path to improve…
  93. Improve the rendering Process? (Refactor code)

  94. Improve the rendering Process? (Refactor code) That would involve…

  95. Improve the rendering Process? (Refactor code) Designers That would involve…

  96. Improve the rendering Process? (Refactor code) Developers Designers That would

    involve…
  97. Improve the rendering Process? (Refactor code) Developers Designers That would

    involve… Product Owners
  98. Better jump off a cliff

  99. What about the network?

  100. Edge Caching maybe?

  101. Edge Caching maybe? DevOps Developers Test Engineers That would involve…

    Product Owners
  102. HTTP2 Push?

  103. HTTP2 Push? DevOps Developers Test Engineers That would involve…

  104. What can service workers do?

  105. What can service workers do? Cache stuff

  106. What can service workers do? Cache stuff Even… cache the

    HTML document
  107. What can service workers do? a few front end Devs

    That would only involve… Cache stuff Even… cache the HTML document
  108. What can service workers do? a few front end Devs

    That would only involve… It would be just cross cut change that would eliminate the network Cache stuff Even… cache the HTML document
  109. What can service workers do? a few front end Devs

    That would only involve… It would be just cross cut change that would eliminate the network once and for all !! Cache stuff Even… cache the HTML document
  110. Perfecto…

  111. let’s do it

  112. the plan • Create a proof of concept • Deploy

    on staging • Work on the details while feedback is coming • Tweak a few things • Go Live!
  113. the mentality • Go fast • Perform minimum changes •

    Involve as few as possible
  114. proof of concept

  115. <!-- Google Analytics --> <!-- Facebook Pixel —> SSR <html>

    SSR <script> /index.html PoC
  116. SSR <html> SSR <script> Tag Manager /index.html PoC

  117. SSR <html> SSR <script> Tag Manager /index.html PoC

  118. Tag Manager /index.html PoC

  119. /index.html PoC

  120. /index.html but that is slower for older browsers that don’t

    support SWs PoC
  121. /index.html PoC

  122. /index.html Browser: /index.html Worker: /index-shell.html PoC

  123. more Developers involved

  124. sw.js var cacheFiles = [ “./index-shell.html”, "./js/app.js", "./css/style.css" ]; self.addEventListener("install",

    e "=> { e.waitUntil( caches.open(cacheName).then(cache "=> { return cache.addAll(cacheFiles); }) ); });
  125. "./js/app.js", "./css/style.css" ]; self.addEventListener("install", e "=> { e.waitUntil( caches.open(cacheName).then(cache "=>

    { return cache.addAll(cacheFiles); }) ); }); self.addEventListener("fetch", function(e) { if (e.request.url.match(/\/index.html/)) { e.respondWith(caches.match('/index-shell.html')); } });
  126. navigator.serviceWorker .register(“/sw.js”, { scope: "./" }) .then(() "=> { console.log(“[ServiceWorker]

    Registered"); }) .catch(err "=> { console.log(“[ServiceWorker] Failed to Register", err); }); index.html
  127. to staging!

  128. None
  129. …from staging

  130. Surprise #1 /index.html Cannot submit any form

  131. Surprise #1 /index.html

  132. Surprise #1 /index.html <meta name=“csrf-token” content=“…” />

  133. Surprise #1 /index.html Keep CSRF token in a

  134. all Developers involved

  135. to staging!

  136. None
  137. …from staging

  138. Surprise #2 /index.html CSS is not loading

  139. Surprise #2 /index.html

  140. Surprise #2 /index.html meet Opaque responses

  141. Surprise #2 /index.html meet Opaque responses

  142. Surprise #2 /index.html meet Opaque responses • Responses to requests

    made to a remote origin 
 when CORS is not enabled
  143. Surprise #2 /index.html meet Opaque responses • Responses to requests

    made to a remote origin 
 when CORS is not enabled • status == 0
  144. Surprise #2 /index.html meet Opaque responses • Responses to requests

    made to a remote origin 
 when CORS is not enabled • status == 0 • no access to headers
  145. Surprise #2 /index.html meet Opaque responses • Responses to requests

    made to a remote origin 
 when CORS is not enabled • status == 0 • no access to headers • no access to body
  146. Surprise #2 /index.html meet Opaque responses • Responses to requests

    made to a remote origin 
 when CORS is not enabled • status == 0 • no access to headers • no access to body • Using a CDN? => your assets on a remote origin
  147. Surprise #2 /index.html meet Opaque responses const request = new

    Request( 'https:"//no-cors-cdn.com/style.css', { mode: 'no-cors' } ); fetch(request).then(response "=> cache.put(request, response) );
  148. Surprise #2 /index.html meet Opaque responses Configure CORS for all

    your cached assets <link crossorigin=‘anonymous’ /> Your JS should already be so for error monitoring
  149. DevOps involved

  150. to staging!

  151. no news,
 maybe it’s time for those details…

  152. Detail #1 /index.html App has been updated. Click here to

    reload •When? •Won’t it become annoying? •Backwards compatibility?
  153. That is a BIG FAT detail, right there!

  154. Detail #1 /index.html App has been updated. Click here to

    reload Treat your web app as a mobile app
  155. Detail #1 /index.html App has been updated. Click here to

    reload •Set ShellVersion in index.html •Add ShellVersion HTTP Header 
 to all XHR requests •Bump it on every release/deploy
 based on watched files
  156. Detail #1 /index.html App has been updated. Click here to

    reload •Set minimum supported version •Return client error HTTP status 
 (e.g. 430) if below minimum
  157. Detail #1 /index.html App has been updated. Click here to

    reload Show update prompt up to 2 times/ day for + on 430s.
  158. PMs definitely involved

  159. None
  160. …from staging

  161. Surprise #3 /index.html Safari not working - at all

  162. Detail #2 /index.html How to unregister a buggy 
 service

    worker?
  163. Detail #2 /index.html Be prepared! No deployment should be required

  164. Detail #2 /index.html no big deal bug? ENV.SW_TYPE=“NOOP” self.addEventListener('install', function

    () { self.skipWaiting(); console.log('NOOP service worker installed'); }); self.addEventListener('activate', function (event) { event.waitUntil(clients.claim()); console.log('NOOP service worker activated'); });
  165. Detail #2 /index.html HUGE deal bug? ENV.SW_TYPE=“DESTROYER” self.addEventListener('install', function (e)

    { self.skipWaiting(); }); self.addEventListener('activate', function (e) { self.registration.unregister() .then(function () { return self.clients.matchAll(); }) .then(function (clients) { clients.forEach(client "=> client.navigate(client.url)); });
  166. Detail #2 /index.html /purge-caches •Caches •Storage •IDB •Service Workers Clear-Site

    HTTP Header (W3C working draft)
  167. DevOps involved. Again.

  168. to staging!

  169. None
  170. …from staging

  171. Surprise #3 /index.html it’s not thaaaat fast…

  172. What did you just say to me!??

  173. Surprise #3 /index.html We removed SSR. To go beyond user

    perceived performance and ACTUALLY load faster, you need to approach the idea of… offline first
  174. Surprise #3 /index.html Offline first (simple case) Serve cached content

    from IDB or localStorage, while fetching fresh data from the server.
  175. authenticated users == user specific content

  176. Surprise #3 /index.html 1.Sign in as Bob 2.Sign out 3.Sign

    in as Alice 4.What does Alice see? caching the user specific content, poses this problem
  177. Surprise #3 /index.html clear all caches on /signout?

  178. Surprise #3 /index.html clear all caches on /signout? won’t work

    :(
  179. Surprise #3 /index.html clear all caches on /signin?

  180. Surprise #3 /index.html clear all caches on /signin? won’t work

    :(
  181. need to create a personal service worker

  182. Surprise #3 /index.html /app_shell /index.html?uid=4w321r /signin

  183. Surprise #3 /index.html /app_shell /index.html?uid=4w321r /signin match with UID

  184. Surprise #3 /index.html /app_shell /index.html?uid=4w321r /signin + Scope IDB, Storage

    keys under this UID e.g. “/uid/42321/avatarUrl” match with UID
  185. self.addEventListener("fetch", function(e) { if (e.request.url.match(/\/index.html?uid=4w221r/)) { e.respondWith(caches.match('/index-shell.html')); } });

  186. None
  187. Offline first… would involve EVERYONE

  188. epilogue

  189. Robert case takeaways

  190. PWAs should be treated as… Apps

  191. PWAs will remind you…

  192. Cache invalidation is a hard thing :P

  193. Great performance is
 a team thing!

  194. Thank you! Questions? Stratos Pavlakis Software guy @Blueground th3hunt