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

Instant Loading: Making the web competitive on mobile

Instant Loading: Making the web competitive on mobile

Tech Talk at PayPal, Oct 10th, 2017.


Addy Osmani

October 10, 2017

More Decks by Addy Osmani

Other Decks in Technology


  1. Instant Loading Making the web competitive on mobile PayPal, Oct

    17’ @addyosmani
  2. My desk

  3. Improving performance is a journey. Lots of small changes can

    lead to big gains.
  4. 1. JavaScript Frameworks 2. Large JavaScript Bundles 3. PRPL Pattern

    4. <link rel=dns-prefetch> 5. <link rel=preload> 6. Reduction in HTTP Requests 7. Service Workers 8. Image Compression TOPICS
  5. loading is a user journey with many disparate expectations you’ve

  6. None
  7. USERS LOOK FOR VISUAL FEEDBACK TO reassure them everything is

    working as expected.
  8. first Interactive consistently Interactive

  9. Time to Interactive <5s on an average mobile device over

    3G *2s on repeat-load a:er Service Worker registered GOAL
  10. 19s 16s 420KB JavaScript Startup Performance, Double-Click Mobile Speed Matters

    report & the HTTP Archive The average web page on mobile in 2017 UNTIL INTERACTIVE FULLY LOADED JAVASCRIPT
  11. None
  12. None
  13. None
  14. PERFORMANCE JavaScript Start-up Start-up o o V8 Runtime Call Stats

  15. 1MB script (250KB minified) JS Parse Time On Mobile

  16. None
  17. about:inspect in Chrome DevTools

  18. None
  19. None
  20. None
  21. Apple A11 Arm64 SOC in iPhone8 benchmarks faster than x86

    in Macbook Pro
  22. webpagetest.org/easy Moto G4 + 3G

  23. A closer look at PayPal.com

  24. None
  25. Opportunities to dns-prefetch more?

  26. <link rel=prefetch> resources for the next navigation

  27. Authenticated PayPal.com

  28. Ideally, test on real (average) mobile hardware. CPU, GPU, cores,

    network packet loss can all introduce variance.
  29. 16 seconds in before First Paint

  30. 20 seconds before we see First Meaningful Paint

  31. 30 seconds before the page is Continuously Interactive

  32. Network bound for 3+ seconds

  33. 14s just fetching resources

  34. 11 seconds in idle time (time when the browser is

    waiting on the CPU or GPU to do some processing)
  35. ~6 seconds spent just in parsing & compiling JavaScript Party

    on the main thread
  36. None
  37. By default, React includes many helpful warnings. These warnings are

    very useful in development. However, they make React larger and slower so you should make sure to use the production version when you deploy the app.
  38. None
  39. 50% of script immediately loaded is unused

  40. Use babel-preset-env to only transpile code for browsers that need

    it { "presets": [ ["env", { "targets": { "browsers": ["last 2 versions"] } }] ] } Only transpile what you need with
  41. Many sites optimize for the Lowest Common Denominator Most users

    end up being deployed ES2015 polyfills
  42. Deploying ES2015+ JavaScript in 2017 babel-preset-env + <script type=module>

  43. Deploying ES2015+ JavaScript in 2017 main-legacy.js main.js

  44. Switching to MozJPEG could save 60% on bytes

  45. None
  46. Continuous Performance Tracking

  47. None
  48. Code-splitting // Defines a “split-point” for a separate bundle require.ensure([],

    () => { const profile = require('./UserProfile', cb); }); import('./UserProfile') .then(loadRoute(cb)) .catch(errorLoading) Webpack 2+ Webpack 1 Also see Splittable, Closure Compiler or Browserify
  49. Minify _everything_ Babelified ES5 w/Uglify ES2015+ with Babili css-loader +

    minimize:true Code-splitting Dynamic import() Route-based chunking Tree-shaking Webpack 2+ with Uglify RollUp DCE w/ Closure Compiler Optimize “Vendor” libs NODE_ENV=production CommonsChunk + HashedModuleIdsPlugin() Transpile less code babel-preset-env + modules:false Browserlist useBuiltIns: true Scope Hoisting: Webpack 3 RollUp Strip unused Lodash modules lodash-webpack-plugin babel-plugin-lodash Fewer Moment.js locales ContextReplacementPlugin()
  50. workflow

  51. None
  52. None
  53. None
  54. None
  55. None
  56. None
  57. None
  58. None
  59. None
  60. None
  61. None
  62. None
  63. None
  64. None
  65. Webpack support for Pure Modules

  66. None
  67. None
  68. Plenty of lightweight options for mobile Lower total cost on

    size + parse times from the get-go
  69. Preact on Lyft

  70. Preact on Uber

  71. None
  72. None
  73. None
  74. None
  75. Byte savings @

  76. Brotli Display Ads from Google now served using Brotli compression!

    40% Data-savings up to 15% in aggregate over gzip https://developers.googleblog.com/
  77. Brotli Improved load time by 7% in India & 4%

    U.S bit.ly/linkedin-brotli Decreased the size of static assets by 20% bit.ly/dropbox-brotli 17% improvement for largest JS bundles bit.ly/certsimple-brotli 1.5 petabytes (million gigs) saved a day bit.ly/playstore-brotli
  78. WebP 30% smaller than JPEG 25% smaller than PNG

  79. WebP bit.ly/webp-format Serving over 43B image requests a day 25-30%

    savings for WebP on average (26% lossless) Data Saver + Web Store
  80. WebP Conversion XNConvert Windows/Mac/Linux Can convert in batch Supports most

    formats Alternatively: imagemin Pixelmator ImageMagick GIMP Leptonica
  81. WebP Serving <picture> <!-- Chrome: WebP --> <source srcset="photo.webp" type="image/webp">

    <!-- Edge: JPEG-XR --> <source srcset="photo.jxr" type="image/vnd.ms-photo"> <!-- Safari: JPEG 2000 --> <source srcset="photo.jp2" type="image/jp2"> <!-- Firefox: Fallback --> <img srcset="photo.jpg"> </picture> Or use the Accept header + .htaccess to serve WebP if a browser supports it and it exists on disk.
  82. Service Workers Inbox by Gmail 10% improvement in Time-to-Interactive

  83. None
  84. HTTP Caching Checklist Use consistent URLs and minimize resource churn

    Provide a validation token (ETag) to avoid transferring unchanged bytes Identify resources that can be cached by intermediaries (like CDNs) Determine the optimal cache lifetime of resources (max-age) Consider a Service Worker for more control over your repeat visit caching 1. 2. 3. 4. 5. bit.ly/caching-checklist
  85. None
  86. Let’s hack

  87. ResourceLoadPriorityVeryHigh

  88. None
  89. None
  90. ResourceLoadPriorityVeryHigh

  91. PayPal-inium

  92. Original Everything is high priority Preloaded JS Preloaded CSS +

  93. Original Everything is high priority JS + CSS is high

    priority CSS + fonts are high prio
  94. None
  95. None
  96. None
  97. None
  98. None
  99. <head> <link rel="preload" as="script" href="1.js"> <link rel="preload" as="script" href="2.js"> <link

    rel="preload" as="script" href="3.js"> .. Link: 1.js; rel="preload"; as="script" <link rel=“preload”>
  100. None
  101. None
  102. None
  103. None
  104. bit.ly/prpl-paFern PRPL bit.ly/prpl-paFern

  105. HTTP/2 with 3G

  106. HTTP/2 + preload with 3G

  107. None
  108. None
  109. Express + HTTP/2 Push Headers const express = require('express'), let

    app = express(); app .use('/js', express.static('js')) .get('/', function (req, res) { res.set('Link', ` </style.css>; rel=preload; as='style', </js/vendor.bundle.js>; rel=preload; as='script', </js/app.bundle.js>; rel=preload; as='script'`)
  110. None
  111. None
  112. None
  113. Alternatively: Track cache content using cookies if (supports_http2() && !http_cached('/app.js'))

    { header('link:</app.js>; rel=preload; as=script’); setcookie('/app.js', 'is-cached', 0, '/'); }
  114. Alternatively: Track cache content using cookies function http_cached($filename) { if

    ('is-cached' === $_COOKIE[$filename]) { return true; } else { return false; } } Try CASPer
  115. None
  116. Repeat visit with Service Worker

  117. Next: Differential Serving based on browser compatibility? HTTP/1 works better

    when resources are concatenated (bundled) HTTP/2 works better when resources are more granular (unbundled) Serve an unbundled build for server/browser combinations supporting HTTP/2. Trigger delivery with <link rel="preload"> or HTTP/2 Push Serve a bundled build to minimize round-trips to get the app running on server/browser combinations that don't support HTTP/2 Push
  118. Debugging: HTTP/2 Server Push in DevTools

  119. Debugging: HTTP/2 Server Push in DevTools

  120. HTTP/2 Server Push Rules Of Thumb Push just enough resources

    to fill idle network time, and no more. Push resources in evaluation-dependence order. Consider using strategies to track the client-side cache. Use the right cookies when pushing resources. Use server push to fill the initial cwnd. Consider preload links to reveal remaining critical resources. 1. 2. 3. 4. 5. bit.ly/h2push PUSH
  121. None
  122. PRPL In-A-Box Polymer App Toolbox PREACT CLI

  123. None
  124. None
  125. None
  126. With Service Workers

  127. With HTTP/2 Server Push

  128. babel-preset-env + per-browser bundles

  129. babel-preset-env + per-browser bundles

  130. Twitter Lite

  131. None
  132. Interactive in <5s on 3G

  133. Can we get fast 3G numbers across the board or

    regular 3G?
  134. PRPL Push / Preload

  135. 18% improvement <link rel=dns-prefetch>

  136. 36% improvement <link rel=preload>

  137. Render

  138. HTML Streaming reduced TTFB by 30% (200ms), increasing time user’s

    spent in the app. Nicolas Gallagher, Technical lead for Twitter Lite
  139. None
  140. 4x improvement to render perf by using requestIdleCallback() to defer

    JS loading of images. Nicolas Gallagher, Technical lead for Twitter Lite
  141. Heavy image decode Lower image decode

  142. None
  143. Adapt intelligently H E I G H T Size appropriately

    WIDTH IMAGE DECODE Compress carefully Take care with tools Prioritize critical images HIGH LOW Lazy-load the rest Choose the right format Image Optimisation
  144. images.guide

  145. Data Saver Mode introduced up to 70% savings for Twitter

    Lite Do this with the browser using the Save-Data client hint
  146. Precache

  147. This is a headline Followed by a subhead This is

    body copy and it goes a little like this and Lorem ipsum dolor sit amet, consectetur adipiscing elit. This is body copy and it goes a little like this and Lorem ipsum dolor sit amet, consectetur adipiscing elit. Application Shell A skeleton representing the user interface that can be offline cached & instantly rendered on repeat visits.
  148. Before Service Worker After Service Worker

  149. Lazy-load

  150. None
  151. Before code-splitting

  152. webpack-web.config.js const plugins = [ // extract vendor and webpack's

    module manifest new webpack.optimize.CommonsChunkPlugin({ names: [ 'vendor', 'manifest' ], minChunks: Infinity }), // extract common modules from all the chunks (requires no 'name' property) new webpack.optimize.CommonsChunkPlugin({ async: true, children: true, minChunks: 4 }) ];
  153. After code-splitting bit.ly/twitterlite-perf bit.ly/twitter-casestudy

  154. None
  155. None
  156. https://meowni.ca/font-style-matcher/

  157. https://www.zachleat.com/web/comprehensive-webfonts/ “Comprehensive Web Fonts”

  158. None
  159. None
  160. <link rel="preload" as="font" href="font.woff" type="font/woff"> Link: <font.woff>; rel=preload; as=font; type='font/woff'

  161. Heaviest use of rel=preload is for Web Fonts HTTPArchive

  162. Preloading Web Fonts = 50% (1.2s) improvement in time-to-text-paint

  163. Control font performance with font-display auto: uses whatever font display

    strategy the user-agent uses block: draws "invisible" text at first if the font is not loaded, but swaps the font face in as soon as it loads swap: draws text immediately with a fallback if the font face isn’t loaded, but swaps the font face in as soon as it loads fallback: font face is rendered with a fallback at first if it’s not loaded, but the font is swapped as soon as it loads optional: if the font face can’t be loaded quickly, just use the fallback Chrome 60
  164. None
  165. None
  166. bit.ly/font-subsetting

  167. https://fonts.googleapis.com/css?family=Inconsolata https://fonts.googleapis.com/css?family=Inconsolata&text=Hello Web Font Subsetting ~3KB ~880 bytes Supported by

    Google Fonts
  168. /* Small subset, normal weight */ @font-face { font-family: whatever;

    src: url('reg-subset.woff') format('woff'); unicode-range: U+0-A0; font-weight: normal; } The browser can also handle subsetting! https://jakearchibald.com/2014/minimising-font-downloads/ /* Large subset, normal weight */ @font-face { font-family: whatever; src: url('reg-extended.woff') format('woff'); unicode-range: U+A0-FFFF; font-weight: normal; } Chrome 36 Firefox 44
  169. CSS Font Loading API https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/webfont-optimization const font = new FontFace("Awesome

    Font", "url(/fonts/awesome.woff2)", { style: 'normal', unicodeRange: 'U+000-5FF', weight: '400' }); // don't wait for the render tree, initiate an immediate fetch! font.load().then(function() { // apply the font (which may re-render text and cause a page reflow) // after the font has finished downloading document.fonts.add(font); document.body.style.fontFamily = "Awesome Font, serif"; // OR... apply your own render strategy here... }); Chrome 35 Firefox 41
  170. Use System Fonts when you can The fastest font is

    one that doesn’t need to load. Try font-display: optional; If a Web Font can’t load fast, load a fallback instead. If the Web Font is cached, it’ll get used the next time the user loads the page. Try <link rel=preload as=font> to request Web Fonts with a higher priority If Web Fonts are a critical to your UX, preload them to minimize FOIT. Try subsetting to limit the range of Web Font characters needed Subsetting removes characters & Open-Type features from fonts, reducing file size. Google Fonts, TypeKit & Font Squirrel support it. Be careful with use. Try the CSS Font Loading API if you need more control Track font download progress & apply once fetched, manipulate font faces and override default lazy load behavior. S Have a Web Font Loading Strategy @addyosmani
  171. None
  172. Streams API bit.ly/streams-ftw Progressive Loading: HTML

  173. <link> in body bit.ly/progressive-css Progressive Loading: CSS

  174. <link> in body bit.ly/progressive-css Progressive Loading: CSS <body> <!-- HTTP/2

    push this resource, or inline it, whichever's faster --> <link rel="stylesheet" href="/site-header.css"> <header>…</header> <link rel="stylesheet" href="/article.css"> <main>…</main> <link rel="stylesheet" href="/comment.css"> <section class="comments">…</section> <link rel="stylesheet" href="/about-me.css"> <section class="about-me">…</section> </body>
  175. medium.com/reloading

  176. None
  177. None
  178. None
  179. None
  180. None
  181. None
  182. None
  183. None
  184. None
  185. None
  186. None
  187. None
  188. None
  189. None
  190. None
  191. None
  192. None
  193. None
  194. None
  195. None
  196. https://medium.com/dev-channel/treebo-a-react-and-preact-progressive-web-app- performance-case-study-5e4f450d5299 next do webpack bundle analyzer

  197. None
  198. None