Slide 1

Slide 1 text

LOADING BEST PRACTICES Modern @addyosmani

Slide 2

Slide 2 text

MOBILE CHANGES everything.

Slide 3

Slide 3 text

THE NETWORK THERMAL THROTTLING PARSING JAVASCRIPT THIRD-PARTY CODE PARSER BLOCKING PATTERNS DISK I/O CACHE EVICTION IPC JANK DIFFERENCES IN L2/L3 CACHING RTTS IMAGES WHAT IMPACTS LOADING?

Slide 4

Slide 4 text

2017 Best Practices For Loading Compress diligently! (GZip, Brotli) Cache effectively (HTTP, Service Workers) ⚡ Minify & optimize *.* Preresolve DNS for critical origins Preload critical resources Respect data plans Stream HTML responses Make fewer HTTP requests Have a Font loading strategy ✂ Send less JavaScript (code-splitting) Lazy-load non-critical resources Route-based chunking Library sharding PRPL pattern Tree-shaking (Webpack, RollUp) Serve modern browsers ES2015 (babel-preset-env) 1 Scope hoisting (Webpack) Don’t ship DEV code to PROD

Slide 5

Slide 5 text

We want happy users.

Slide 6

Slide 6 text

RESPONSE ANIMATION IDLE LOAD RAIL Evolving

Slide 7

Slide 7 text

First Paint First Meaningful Paint Time To Interactive User happiness metrics First Contentful Paint

Slide 8

Slide 8 text

“Networks, CPUs and disks all hate you. On the client, you pay for what you send in ways you can't easily see” - Alex Russell, Chrome

Slide 9

Slide 9 text

JavaScript has a cost. Fast = Fast at Parse Eval Download On mobile devices

Slide 10

Slide 10 text

2017 JavaScript Parse Costs Average Phone ~1MB JavaScript (uncompressed)

Slide 11

Slide 11 text

JavaScript Parse Cost On Mobile - CNN ~9s difference to the A11 With thanks to Pat Meenan

Slide 12

Slide 12 text

PRPL Pattern USED BY SITES LIKE SUPPORTED BY CLIs

Slide 13

Slide 13 text

Tools with a baseline that is fast by default provide the best chance of success.

Slide 14

Slide 14 text

Where do mobile sites spend their time loading? With thanks to Camillo and Mathias @ V8 Average Housing Forbes Treebo Twitter Trivago Lancome Tech Today OLACabs Wego Konga

Slide 15

Slide 15 text

Caching In most cases, when a web page needs a resource, Chrome starts by looking it up in the Memory cache. If the Memory cache doesn’t have it, Chrome will then ask the network stack to handle the request. The network stack will eventually process the request and will start by looking for the resource in the HTTP cache. If the HTTP cache doesn’t have it, the network stack will then issue an actual network request.

Slide 16

Slide 16 text

* Chrome has 4+ caches. The above reflects the main two - the HTTP and memory caches Chrome’s Cache Hit Rates

Slide 17

Slide 17 text

EVERYONE IS RESPONSIBLE FOR performance.

Slide 18

Slide 18 text

5s

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

“File-size isn't just about the download. Byte-for-byte, JavaScript is more expensive for the browser to process than the equivalently sized image or Web Font.” - Tom Dale, Glimmer & Ember

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

https://twitter.com/kristoferbaxter/status/908144931125858304

Slide 24

Slide 24 text

GOOD OPTIONS FOR MOBILE WEB POLYMER PREACT VUE.JS GLIMMER SVELTE REACT *WITH CODE-SPLITTING AND A PERF BUDGET AND OTHERS, LIKE STENCILJS.

Slide 25

Slide 25 text

Recipe for building good web sites

Slide 26

Slide 26 text

Performance Budget Tools CALIBRE BUNDLESIZE SPEEDCURVE

Slide 27

Slide 27 text

bit.ly/perf-budgets REAL-WORLD WEB PERF BUDGETS

Slide 28

Slide 28 text

HEALTH OF THE WEB

Slide 29

Slide 29 text

Chrome DevTools Lighthouse WebPageTest Web Performance Tooling Synthetic lab conditions Real-world RUM

Slide 30

Slide 30 text

EXPLORE The health of the web as a whole with HTTP Archive

Slide 31

Slide 31 text

BETA beta.httparchive.org AVAILABLE TODAY

Slide 32

Slide 32 text

✅ RESPONSE BODIES ✅ LIGHTHOUSE REPORTS ✅ BLINK FEATURE COUNTERS ✅ NEW PERFORMANCE METRICS ..AND IT’S ALL QUERYABLE!

Slide 33

Slide 33 text

SITES ARE SENDING USERS… http://beta.httparchive.org Using Dev Tools mobile emulation, Moto G4 calibrated CPU, Cable (5/1mbps, 28ms) STATE OF JAVASCRIPT ON MOBILE P90: ~1MB P75: 0.6MB OF JS SPENDING ~4s ON PARSE/COMPILE

Slide 34

Slide 34 text

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()

Slide 35

Slide 35 text

Is all of that ~1MB used upfront?

Slide 36

Slide 36 text

40% SITES MAY USE ONLY OF THE JAVASCRIPT THEY LOAD UPFRONT. With thanks to fmeawad@chromium.org JS CODE COVERAGE OF TOP 50 SITES

Slide 37

Slide 37 text

Removing unused code can reduce network transmission times, CPU-intensive code parsing, and memory overhead

Slide 38

Slide 38 text

SITES ARE SENDING USERS… http://beta.httparchive.org Using Dev Tools mobile emulation, Moto G4 calibrated CPU, Cable (5/1mbps, 28ms) STATE OF THE WEB ON MOBILE P50: 344KB, P75: 614KB, P90: 970KB P90: 5.4MB P75: 2.9MB P90 3.8MB (70%) of this is images P90 1MB (18%) of this is JS

Slide 39

Slide 39 text

70% OF THIS IS IMAGES. OPTIMIZE THEM. https://images.guide

Slide 40

Slide 40 text

SITES ARE INTERACTIVE IN.. http://beta.httparchive.org Using Dev Tools mobile emulation, Moto G4 calibrated CPU, Cable (5/1mbps, 28ms) WEB SPEED METRICS ON MOBILE P50: 344KB, P75: 614KB, P90: 970KB P90: 35s P75: 22s P90: 11s before First Meaningful Paint

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

Queryable RUM for the web? Ilya Grigorik @igrigorik Bryan McQuade @bryanmcquade

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

GIVING DEVELOPERS MORE control. @addyosmani

Slide 69

Slide 69 text

@font-face {
 font-family: 'Roboto';
 font-display: optional;
 src: url(Roboto.woff) format('woff'),
 url(Roboto.eot) format('eot');
 font-weight: 400;
 font-style: normal;
 } If my Web Fonts can’t load quickly, don’t load them at all. Chrome 60 Safari WIP Firefox WIP

Slide 70

Slide 70 text

// Network type that browser uses
 navigator.connection.type > 'wifi' 
 // New: Effective connection type // using rtt and downlink values
 navigator.connection.effectiveType > '2G' I want to adapt serving based on estimated network quality BEFORE AFTER For more on navigator.connection.* See ‘Building a modern media experience’ Chrome 62

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

Chrome 50 Safari 11 Firefox WIP

Slide 73

Slide 73 text


 
 (async () => {
 try {
 const response = await fetch(new Request("movies.json", {credentials: "include"}));
 const data = await response.json();
 console.log(data);
 } catch (exception) {
 console.log("Booo");
 }
 })();
 I have critical resources I want to load earlier than discovery. Chrome 62

Slide 74

Slide 74 text

addEventListener('activate', event => { event.waitUntil(async function() { // Feature-detect if (self.registration.navigationPreload) { // Enable navigation preloads! await self.registration.navigationPreload.enable(); } }()); }); I want to start network requests while the Service Worker is still booting up. Saves 1 RTT Early numbers suggest a 20% improvement to page load time at PC95. Chrome 59

Slide 75

Slide 75 text

Many sites optimize for the Lowest Common Denominator Most users end up being deployed ES2015 polyfills

Slide 76

Slide 76 text

Deploying ES2015+ JavaScript in 2017 babel-preset-env +

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

FUTURE? BETTER PERF. Modules. Service Workers. Navigation Architecture. OFF-MAIN THREAD FETCH SCRIPT STREAMING TODAY, STILL NEED TO BUNDLE FOR PRODUCTION PlzNavigate

Slide 79

Slide 79 text

PROGRESSIVE WEB APPS ARE THE NEW normal. but…we have some new ones to share!

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

Old Mobile Site - 1st load First Paint: 4.2s First Meaningful Paint: 6.2s Time To Interactive: 23s

Slide 84

Slide 84 text

New Mobile Site - 1st load First Paint: 1.8s First Meaningful Paint: 5.1s Time To Interactive: 5.6s JS Bundles: 620KB ➡ 150KB CSS Bundles: 150KB ➡ 6KB inline P90 for Pin pages: 20s ➡ 6.5s

Slide 85

Slide 85 text

New Mobile Site - Repeat Loads First Paint: 0.6s First Meaningful Paint: 3.5s Time To Interactive: 3.9s

Slide 86

Slide 86 text

0 15 30 45 60 Android iOS PWA 0.15MB 56MB 9.6MB Size: Comparing the PWA to the native apps 150KB home feed load

Slide 87

Slide 87 text

Comparing old mobile web to new mobile web +60% +44% +50% +40% Time Spent > 5 minutes User-generated Ad $ Ad Clickthroughs Core Engagements Comparing across web/native +2-3% +2% +0% +5% Time Spent > 5 minutes User-generated Ad $ Ad Clickthroughs Core Engagements

Slide 88

Slide 88 text

JavaScript Bundle Splitting Strategy How did Pinterest handle code-splitting? Vendor Entry Async React, Redux, React Router etc (73KB) Main shell, Core logic, Redux store (59KB) Async route chunks (13-18KB)

Slide 89

Slide 89 text

const chunkPlugins = [
 new webpack.optimize.CommonsChunkPlugin({
 name: 'vendor-mweb',
 minChunks: Infinity,
 chunks: ['entryChunk-mobile']
 }),
 new webpack.optimize.CommonsChunkPlugin({
 name: 'entryChunk-webpack',
 minChunks: Infinity,
 chunks: ['vendor-mweb']
 }),
 ]; const bundles = {
 'vendor-mweb': [
 'app/mobile/polyfills.js',
 'intl',
 'normalizr',
 'react-dom',
 'react-redux',
 'react-router-dom',
 'react',
 'redux'
 ],
 'entryChunk-webpack': 'app/mobile/runtime.js',
 'entryChunk-mobile': 'app/mobile/index.js'
 }; Webpack Config

Slide 90

Slide 90 text

// Create a loader
 const Closeup = () => import(/* webpackChunkName: "CloseupPage" */ 'app/mobile/routes/CloseupPage');
 
 // Register it to the route
 route('/pin/:pinId', routes.Closeup, { name: 'Closeup' }),
 
 // Render a react-router-v4 Route with the route bundle loader
 
 }
 /> React Router V4

Slide 91

Slide 91 text

// Async load the route bundle
 class PageRoute extends PureComponent {
 render() {
 const { bundleLoader, ...props } = this.props;
 return ;
 }
 }
 
 // Load it and render
 class Loader extends PureComponent {
 componentWillMount() {
 this.props.loader().then(module => {
 this.setState({ LoadedComponent: module.default });
 });
 }
 } React Router V4

Slide 92

Slide 92 text

Webpack Bundle Analyzer: Before splitting out common async route code

Slide 93

Slide 93 text

Webpack Bundle Analysis Tackling dupes across lazily loaded routes - Moved common code into the Entry chunk - 20% increase in size of Entry (59KB -> 71KB) - 60-90% decrease in size of async route chunks - e.g: Homefeed (13.9KB -> 1KB), Closeup (18KB -> 7KB)

Slide 94

Slide 94 text

Webpack Bundle Analyzer: After moving out common code from async chunks into entryChunk 60-90% decrease in size of async route chunks (e.g 13.9KB ➡ 1KB) 20% increase in size of entry (59KB ➡ 71KB)

Slide 95

Slide 95 text

Service Workers Caching runtime & static assets offline Start - Runtime caching async JS chunks (for V8 bytecode cache) - Precaching vendor & entry chunks - Precaching most used routes (e.g Pins) - Generating a SW for each locale bundle Today - Cache all JS/CSS cache-first - Cache the Application Shell - Precache bundle loaded by the shell - Webpack runtime, vendor, entry - Named chunks to cache async routes

Slide 96

Slide 96 text

/* global $VERSION, $Cache, importScripts, WorkboxSW */
 importScripts('https://unpkg.com/workbox-sw@1.1.0/build/importScripts/workbox-sw.prod.v1.1.0.js');
 
 // Add app shell to the webpack-generated precache list
 $Cache.precache.push({ url: 'sw-shell.html', revision: $VERSION });
 
 // Register precache list with Workbox
 const workbox = new WorkboxSW({ handleFetch: true, skipWaiting: true, clientClaim: true });
 workbox.precache($Cache.precache);
 
 // Runtime cache all js
 workbox.router.registerRoute(/webapp\/js\/.*\.js/, workbox.strategies.cacheFirst());
 
 // Prefer app-shell for full-page loads
 workbox.router.registerNavigationRoute('sw-shell.html', {
 blacklist: [
 // bunch of non-app routes
 ],
 }); SW Caching

Slide 97

Slide 97 text

Future - Web Push notifications - Fixing slow API responses (home-feed takes 1s on Fast 3G) - Optimize server latency and response sizes - Adding for preloading bundles

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

0 7.5 15 22.5 30 Android PWA 2.8MB 30MB Size: Comparing the PWA to the native apps

Slide 100

Slide 100 text

Before After

Slide 101

Slide 101 text

Performance Budgets Budgets Tinder tries not to exceed Vendor Async Other 155KB 55KB 35KB CSS 20KB

Slide 102

Slide 102 text

import A from '../A';
 import B from '../B'; 
 const route = [
 {
 route: '/',
 regions: {
 side: A,
 main: B
 }
 }
 ]; JavaScript Route-based code-splitting Before main.js A B

Slide 103

Slide 103 text

import Loadable from ‘react-loadable'; 
 const A = Loadable({
 loader: () => import('../A' /* webpackChunkName: "pc-r-A" */),
 loading: () => null
 }); 
 const B = Loadable({
 loader: () => import('../B' /* webpackChunkName: "pc-r-B" */),
 loading: () => null
 });
 
 const route = [
 {
 route: '/',
 regions: {
 side: A,
 main: B
 },
 preload: [ /* next page chunk to preload*/ ]
 } React Router React Loadable CommonsChunkPlugin After A.js B.js

Slide 104

Slide 104 text

const LoadableComponent = Loadable({...});
 
 LoadableComponent.preload(); JavaScript Route-based code-splitting Preloading more page chunks with React Loadable

Slide 105

Slide 105 text

JavaScript Route-based code-splitting Before Main bundle size: 166kb DOMContentLoad: 5.46s load: 11.91s After Main bundle size: 101kb DOMContentLoad: 4.69s load: 4.69s

Slide 106

Slide 106 text

before after Reduce first paint by 500ms, load time by 1 second

Slide 107

Slide 107 text

new BundleAnalyzerPlugin({
 analyzerMode: 'server',
 analyzerPort: 8888,
 reportFilename: 'report.html',
 openAnalyzer: true,
 generateStatsFile: false,
 statsFilename: 'stats.json',
 statsOptions: null
 }) Webpack Bundle Analysis ✅ core-js + babel-preset-env to drop unused polyfills ✅ Use lodash-webpack-plugin to reduce bundle size ✅ Replaced localForage with IndexedDB ✅ Split non-critical components not used for First Paint ✅ Removed critical CSS from bundle (SSRs already)

Slide 108

Slide 108 text

new LodashModuleReplacementPlugin({
 caching: true,
 collections: true,
 paths: true,
 shorthands: true
 }), Lodash Savings

Slide 109

Slide 109 text

CSS Loading Strategy before after

Slide 110

Slide 110 text

new webpack.optimize.ModuleConcatenationPlugin(),
 new webpack.DefinePlugin({
 'process.env': {
 // Env stuff...
 }, Update to Webpack 3 & React 16 Webpack 2 -> 3 reduced JS parsing time by 8% (250ms -> 230ms) React 15.x -> React 16: reduced vendor chunk size by ~6.7%

Slide 111

Slide 111 text

new workboxWebpackPlugin({
 injectManifest: true,
 swSrc: paths.serviceWorkerSrcPath,
 swDest: paths.serviceWorkerDestPath,
 globDirectory: paths.staticPath,
 globPatterns: [
 '**/*.{js,html,css,svg,woff2}'
 ],
 templatedUrls: {
 'index.html': [
 '../public/static/build/main-*.js',
 '../public/static/build/vendor-*.js',
 '../public/static/build/manifest-*.js',
 '../public/static/build/style.*.css'
 ]
 },
 maximumFileSizeToCacheInBytes: 4194304
 })]

Slide 112

Slide 112 text

IMPROVING PERFORMANCE IS A JOURNEY. LOTS OF SMALL CHANGES CAN LEAD TO BIG GAINS.

Slide 113

Slide 113 text

MEASURE mom. OPTIMIZE MONITOR

Slide 114

Slide 114 text

Links Thanks! Web Fundamentals developers.google.com/web Chrome User Experience Report bit.ly/introducing-crux React Perf Case Studies medium.com/@addyosmani Addy Osmani @addyosmani Ilya Grigorik @igrigorik Bryan McQuade @bryanmcquade Come chat with us at the demo area!