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

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)
  2. Fast is a product of using your framework well &

    intelligently loading via your bundler.
  3. POLYMER SHOP TIME TO INTERACTIVE NEXUS 5X - THROTTLED 3G

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

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

    LOSS TIME TO INTERACTIVE (MEDIAN) 11s 12s * high of 24s. FVL in ~17s
  6. 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
  7. Try not to keep the main thread busy. Larger bundles

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

    script (250KB minified) JS Parse Time On Mobile
  9. 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); });
  10. 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
  11. 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
  12. 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
  13. 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.
  14. 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
  15. 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
  16. 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
  17. 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.
  18. Similarities • Route-based code splitting • Smart pre-loading of chunks

    and PRPL • Partial server-rendering • Service Workers
  19. Flipkart on Mobile • Build-time rendering • App shells •

    SW caches shell, offline-first • Composition of multiple SPAs
  20. 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
  21. 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
  22. 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