$30 off During Our Annual Pro Sale. View Details »

Production Progressive Web Apps With JavaScript Frameworks

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

Addy Osmani

November 15, 2016
Tweet

More Decks by Addy Osmani

Other Decks in Programming

Transcript

  1. Production PWAs With
    JavaScript Frameworks
    Addy Osmani
    Eng manager, Web DevRel
    Abhinav Rastogi
    Tech Lead, FlipkaD (Desktop)

    View Slide

  2. View Slide

  3. Frameworks can
    be fast if we put
    the work in.

    View Slide

  4. Fast is a product
    of using your framework
    well & intelligently loading
    via your bundler.

    View Slide

  5. 4.2s
    Time-to-interactive

    View Slide

  6. 4.8s
    Time-to-interactive

    View Slide

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

    View Slide

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

    View Slide

  9. app coming soon
    Interactive in < 5s

    View Slide

  10. What do we
    mean by fast?

    View Slide

  11. View Slide

  12. Time-to-interactive
    <5s
    on a real-device
    over 3G
    *2s on repeat-load aVer Service Worker registered

    View Slide

  13. View Slide

  14. View Slide

  15. bit.ly/bundling-study

    View Slide

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

    View Slide

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

    View Slide

  18. CONFIGURING WEBPACK

    View Slide

  19. tree-shaking
    19%
    Service Worker
    11%
    HTTP/2
    14%
    What other concepts are you taking advantage of?

    View Slide

  20. POLYMER SHOP TIME TO INTERACTIVE
    NEXUS 5X - THROTTLED 3G
    NEXUS 5X - 3G W/PACKET LOSS
    4.3s
    5.8s

    View Slide

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

    View Slide

  22. NEXUS 5X - THROTTLED 3G
    NEXUS 5X - 3G W/PACKET LOSS
    TIME TO INTERACTIVE (MEDIAN)
    11s
    12s
    * high of 24s. FVL in ~17s

    View Slide

  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

    View Slide

  24. Try not to keep
    the main thread
    busy.
    Larger bundles impact interactivity & take longer to load, parse and execute.

    View Slide

  25. HTTPArchive average script size per page is 408KB gzipped
    1MB script (250KB minified)
    JS Parse Time On Mobile

    View Slide

  26. Test on real phones
    & real networks.
    There’s no substitute.

    View Slide

  27. View Slide

  28. View Slide

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

    View Slide

  30. View Slide

  31. CODE-SPLITTING
    minimal functional code for a route

    View Slide

  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);
    });

    View Slide

  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

    View Slide

  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
    path="user/:id"
    getComponent={(location, callback) => {
    require.ensure([], require => {
    callback(null, require('./UserProfile'))
    })
    }}
    />
    DECLARATIVE CODE-SPLITTING WITH REACT ROUTER

    View Slide

  35. COMMON LIBRARY CHUNKS

    View Slide

  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

    View Slide

  37. THE PRPL PATTERN
    bit.ly/prpl-pacern

    View Slide

  38. require.ensure()
    async getComponent()
    React Router
    link rel=“preload”
    or HTTP/2 Push
    PRPL WITH WEBPACK
    bit.ly/prpl-webpack

    View Slide

  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.

    View Slide

  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

    View Slide

  41. Code-splitting itself is
    not a panacea
    9.8s
    TTI average

    View Slide

  42. #WhatsInYourBundle

    View Slide

  43. View Slide

  44. View Slide

  45. View Slide

  46. View Slide

  47. bit.ly/webpack-ped-preview

    View Slide

  48. 3KB alternative to React with same ES6 API
    Interactive in <5s on real
    devices over 3G

    View Slide

  49. bit.ly/source-map-explorer
    Before (with React)

    View Slide

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

    View Slide

  51. bit.ly/source-map-explorer
    {
    resolve: {
    alias: {
    'react': 'preact-compat',
    'react-dom': 'preact-compat'
    }
    }
    }
    SETUP: USE THE PREACT-COMPAT ALIAS IN WEBPACK

    View Slide

  52. Layer your
    app so the
    network is an
    enhancement.

    View Slide

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

    View Slide

  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

    View Slide

  55. APPLICATION SHELL ARCHITECTURE

    View Slide

  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

    View Slide

  57. HOUSING.COM FLIPKART
    Oline
    loading
    states

    View Slide

  58. SuppoD all
    target users
    using progressive
    enhancement.

    View Slide

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

    View Slide

  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.

    View Slide

  61. HIGH-PERFORMANCE
    REACT PWAs
    bit.ly/pwa-react

    View Slide

  62. View Slide

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

    View Slide

  64. Intro to Flipkart
    India’s largest e-commerce site and
    a first-class PWA across all form factors
    and browsers!

    View Slide

  65. View Slide

  66. Mobile —> desktop

    View Slide

  67. View Slide

  68. React
    Flux/Redux
    React-Router
    Webpack
    Express
    Node
    Fetch
    Promises
    Babel
    Handlebars
    PM2
    Karma

    View Slide

  69. Architecture
    Both mobile and desktop sites follow
    a similar architecture at the high level.

    View Slide

  70. Similarities
    • Route-based code splitting
    • Smart pre-loading of chunks and PRPL
    • Partial server-rendering
    • Service Workers

    View Slide

  71. Significant differences: Mobile vs. Desktop
    Requirements
    User Behaviour
    Network Conditions
    Device Capabilities
    Browser Fragmentation

    View Slide

  72. Flipkart on Mobile
    • Build-time rendering
    • App shells
    • SW caches shell, offline-first
    • Composition of multiple SPAs

    View Slide

  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

    View Slide

  74. What it looks like - Code Splitting

    View Slide

  75. What it looks like - PRPL

    View Slide

  76. Time to Interactive
    Search is available in the first render
    and works without JS!

    View Slide

  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

    View Slide

  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

    View Slide

  79. Gotchas
    • CORS in Webpack & route-based code-splitting
    • Cache-invalidation & the Webpack Manifest

    View Slide

  80. What’s Next

    View Slide

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

    View Slide

  82. View Slide

  83. View Slide

  84. code.nasa.gov old Angular 1 site

    View Slide

  85. investigated ped issues

    View Slide

  86. bit.ly/nasa-ped-audit

    View Slide

  87. Best
    Practices
    Should Be
    Automated
    PRPL

    View Slide

  88. PRPL with code-splitting
    Lazy-loading fragments
    Offline Caching with Service Worker
    HTTP/2 + Server Push bundling
    Polymer App Toolbox

    View Slide

  89. Open Source Software
    Contributed by

    View Slide

  90. View Slide

  91. code.nasa.gov
    Implemented in <1 week
    90+ score on Lighthouse
    Open Source and on GitHub now

    View Slide

  92. github.com/nasa/code-nasa-gov

    View Slide

  93. Frameworks
    can cross the mnish
    line on mobile ped
    * if we put the work in

    View Slide

  94. Let’s ped the web
    forward together.

    View Slide

  95. Thank You!
    +AddyOsmani
    @addyosmani

    View Slide