Production Progressive Web Apps With JavaScript Frameworks

96270e4c3e5e9806cf7245475c00b275?s=47 Addy Osmani
November 15, 2016

Production Progressive Web Apps With JavaScript Frameworks

Addy works through building Progressive Web Apps with React at scale. Featuring special guests, Flipkart, dive into the performance and loading patterns needed to make frameworks competitive on mobile.

Video: https://www.youtube.com/watch?v=e8XejNt5SZo

96270e4c3e5e9806cf7245475c00b275?s=128

Addy Osmani

November 15, 2016
Tweet

Transcript

  1. Production PWAs With JavaScript Frameworks Addy Osmani Eng manager, Web

    DevRel Abhinav Rastogi Tech Lead, FlipkaD (Desktop)
  2. None
  3. Frameworks can be fast if we put the work in.

  4. Fast is a product of using your framework well &

    intelligently loading via your bundler.
  5. 4.2s Time-to-interactive

  6. 4.8s Time-to-interactive

  7. bit.ly/ng2-weather Interactive in < 5s with AoT

  8. bit.ly/vue-hn Interactive in < 5s

  9. app coming soon Interactive in < 5s

  10. What do we mean by fast?

  11. None
  12. Time-to-interactive <5s on a real-device over 3G *2s on repeat-load

    aVer Service Worker registered
  13. None
  14. None
  15. bit.ly/bundling-study

  16. What JS module bundler do you use? 83% webpack

  17. Do you use code-splitting to chunk up your JS? 58%

    yes JS JS JS 10%
  18. CONFIGURING WEBPACK

  19. tree-shaking 19% Service Worker 11% HTTP/2 14% What other concepts

    are you taking advantage of?
  20. POLYMER SHOP TIME TO INTERACTIVE NEXUS 5X - THROTTLED 3G

    NEXUS 5X - 3G W/PACKET LOSS 4.3s 5.8s
  21. FLIPKART NEXUS 5X - THROTTLED 3G NEXUS 5X - 3G

    W/PACKET LOSS 4.5s 6.9s HOUSING.COM TIME TO INTERACTIVE
  22. NEXUS 5X - THROTTLED 3G NEXUS 5X - 3G W/PACKET

    LOSS TIME TO INTERACTIVE (MEDIAN) 11s 12s * high of 24s. FVL in ~17s
  23. 550ms script eval 270ms eval 524ms eval code for view

    140ms eval cookie handling ~500KB script Interactive in 13 seconds ~3s, 249KB common vendor chunk ~5s load JS for view 8s parse/eval
  24. Try not to keep the main thread busy. Larger bundles

    impact interactivity & take longer to load, parse and execute.
  25. HTTPArchive average script size per page is 408KB gzipped 1MB

    script (250KB minified) JS Parse Time On Mobile
  26. Test on real phones & real networks. There’s no substitute.

  27. None
  28. None
  29. Less code, loaded becer improves ped. There is however, nuance.

  30. None
  31. CODE-SPLITTING minimal functional code for a route

  32. System.import(‘./UserProfile’) .then(loadRoute(cb)) .catch(errorLoading); CODE-SPLITTING & ASYNC LOADING WEBPACK 1 WEBPACK

    2 bit.ly/code-splicing-webpack2 bit.ly/code-splicing // Defines a “split-point” for a separate bundle require.ensure([], () => { const profile = require(‘./UserProfile’, cb); });
  33. System.import(‘./UserProfile’) .then(loadRoute(cb)) .catch(errorLoading); CODE-SPLITTING & ASYNC LOADING WEBPACK 1 WEBPACK

    2 bit.ly/code-splicing-webpack2 bit.ly/code-splicing // Defines a “split-point” for a separate bundle require.ensure([], () => { const profile = require(‘./UserProfile’, cb); }); // Request when you require() const waitForChunk = require(‘bundle!./UserProfile.js’); waitForChunk(file => { // use file like is was require()'d }); BUNDLE-LOADER CALLS REQUIRE.ENSURE FOR YOU
  34. System.import(‘./UserProfile’) .then(loadRoute(cb)) .catch(errorLoading); CODE-SPLITTING & ASYNC LOADING WEBPACK 1 WEBPACK

    2 bit.ly/code-splicing-webpack2 bit.ly/code-splicing // Defines a “split-point” for a separate bundle require.ensure([], () => { const profile = require(‘./UserProfile’, cb); }); // Request when you require() const waitForChunk = require(‘bundle!./UserProfile.js’); waitForChunk(file => { // use file like is was require()'d }); BUNDLE-LOADER CALLS REQUIRE.ENSURE FOR YOU <Route path="user/:id" getComponent={(location, callback) => { require.ensure([], require => { callback(null, require('./UserProfile')) }) }} /> DECLARATIVE CODE-SPLITTING WITH REACT ROUTER
  35. COMMON LIBRARY CHUNKS

  36. const CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin"); module.exports = { entry: { p1:

    "./route-1", p2: "./route-2", p3: "./route-3" }, output: { filename: "[name].entry.chunk.js" }, plugins: [ new CommonsChunkPlugin("commons.chunk.js") ] } COMMON/VENDOR LIBRARY CHUNK SPLITTING
  37. THE PRPL PATTERN bit.ly/prpl-pacern

  38. require.ensure() async getComponent() React Router link rel=“preload” or HTTP/2 Push

    PRPL WITH WEBPACK bit.ly/prpl-webpack
  39. LINK REL=PRELOAD use with asset-webpack-plugin bit.ly/link-preload * For most apps

    using preload over H/2 Server Push will lead to better wins.
  40. module.exports = { entry: "./example", output: { path: path.join(__dirname, "js"),

    filename: "[chunkhash].js", chunkFilename: "[chunkhash].js" }, plugins: [ new webpack.optimize.AggressiveSplittingPlugin({ minSize: 30000, maxSize: 50000 }), // ... HTTP/2 with the AggressiveSplittingPlugin bit.ly/wepback-hcp2
  41. Code-splitting itself is not a panacea 9.8s TTI average

  42. #WhatsInYourBundle

  43. None
  44. None
  45. None
  46. None
  47. bit.ly/webpack-ped-preview

  48. 3KB alternative to React with same ES6 API Interactive in

    <5s on real devices over 3G
  49. bit.ly/source-map-explorer Before (with React)

  50. bit.ly/source-map-explorer AVer (with Preact)

  51. bit.ly/source-map-explorer { resolve: { alias: { 'react': 'preact-compat', 'react-dom': 'preact-compat'

    } } } SETUP: USE THE PREACT-COMPAT ALIAS IN WEBPACK
  52. Layer your app so the network is an enhancement.

  53. INSTANT LOADING ON REPEAT. FASTER TTI FOR SUBSEQUENT VISITS.

  54. var CACHE_NAME = 'my-pwa-cache-v1'; var urlsToCache = [ '/', '/styles/styles.css',

    '/script/webpack-bundle.js' ]; self.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { // Open a cache and cache files return cache.addAll(urlsToCache); }) ); }); SERVICE WORKER
  55. APPLICATION SHELL ARCHITECTURE

  56. plugins: [ new SWPrecacheWebpackPlugin( { cacheId: "my-app", filename: "my-service-worker.js", staticFileGlobs:

    [ "app/css/**.css", "app/**.html", "app/js/**.js", "app/images/**.*" ], verbose: true } )] SW-PRECACHE WEBPACK PLUGIN bit.ly/wepback-precache
  57. HOUSING.COM FLIPKART Oline loading states

  58. SuppoD all target users using progressive enhancement.

  59. UNIVERSAL JS RENDERING & DATA-FLOW ReactDOMServer.renderToString() ComponentWillMount() Async data-flow with

    React Router React Resolver
  60. Universal JS has issues. Makes it easy to get stuck

    in uncanny valley renderToString() is synchronous so TTFB is long Streaming server rendered React can get a better TTBH. See react-dom-stream. renderToString() can monopolize the CPU, waste it re-rendering components for each page request. Bottleneck for pages w/lots of VDOM nodes Component memoization helps. See react-ssr-optimization.
  61. HIGH-PERFORMANCE REACT PWAs bit.ly/pwa-react

  62. None
  63. Building Flipkart.com Abhinav Rastogi Tech Lead, Web Team @_abhinavrastogi

  64. Intro to Flipkart India’s largest e-commerce site and a first-class

    PWA across all form factors and browsers!
  65. None
  66. Mobile —> desktop

  67. None
  68. React Flux/Redux React-Router Webpack Express Node Fetch Promises Babel Handlebars

    PM2 Karma
  69. Architecture Both mobile and desktop sites follow a similar architecture

    at the high level.
  70. Similarities • Route-based code splitting • Smart pre-loading of chunks

    and PRPL • Partial server-rendering • Service Workers
  71. Significant differences: Mobile vs. Desktop Requirements User Behaviour Network Conditions

    Device Capabilities Browser Fragmentation
  72. Flipkart on Mobile • Build-time rendering • App shells •

    SW caches shell, offline-first • Composition of multiple SPAs
  73. Flipkart on Desktop • Partial server-side rendering • No app

    shells • Chunked response for the first request, allowing faster TTFP • SW used for caching data and resources
  74. What it looks like - Code Splitting

  75. What it looks like - PRPL

  76. Time to Interactive Search is available in the first render

    and works without JS!
  77. Major Wins • Route-based code-splitting amortizes the high cost for

    the first visit over the session • Smart preloading of chunks and PRPL makes the experience seam-less • Chunked-encoding allows us to download JS chunks while HTML is still being parsed • Based on UX requirement, solved repeat visits for mobile, first visit for desktop
  78. Impact • Business: Upto 2x conversion during sale events •

    Business: Significantly reduced bounce rate • SEO: 50% reduction in time taken by Google Search bots to crawl a page • SEO: 50% increase in number of pages indexed by Google Search • DevOps: Massive 70% reduction in desktop website tickets, lesser errors
  79. Gotchas • CORS in Webpack & route-based code-splitting • Cache-invalidation

    & the Webpack Manifest
  80. What’s Next

  81. Thank You! +Abhinav Rastogi @_abhinavrastogi @flipkart_tech

  82. None
  83. None
  84. code.nasa.gov old Angular 1 site

  85. investigated ped issues

  86. bit.ly/nasa-ped-audit

  87. Best Practices Should Be Automated PRPL

  88. PRPL with code-splitting Lazy-loading fragments Offline Caching with Service Worker

    HTTP/2 + Server Push bundling Polymer App Toolbox
  89. Open Source Software Contributed by

  90. None
  91. code.nasa.gov Implemented in <1 week 90+ score on Lighthouse Open

    Source and on GitHub now
  92. github.com/nasa/code-nasa-gov

  93. Frameworks can cross the mnish line on mobile ped *

    if we put the work in
  94. Let’s ped the web forward together.

  95. Thank You! +AddyOsmani @addyosmani