Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

loading is a user journey with many disparate expectations you’ve probably heard to REDUCE DNS LOOKUPS, REDUCE ROUND-TRIP TIMES, MINIMIZE REDIRECTS, ELIMINATE UNN

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

USERS LOOK FOR VISUAL FEEDBACK TO reassure them everything is working as expected.

Slide 5

Slide 5 text

first Interactive consistently Interactive

Slide 6

Slide 6 text

Time to Interactive <5s on an average mobile device over 3G *2s on repeat-load a:er Service Worker registered GOAL

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

PERFORMANCE JavaScript Start-up Start-up o o V8 Runtime Call Stats

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

about:inspect in Chrome DevTools

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

webpagetest.org/easy Moto G4 + 3G

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Do I need to split? Try Code Coverage in Chrome DevTools

Slide 20

Slide 20 text

Tree-shaking // app.js import { a } from ‘./module.js’; // module.js export function a () {} export function b () {} ❌

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

workflow

Slide 23

Slide 23 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 24

Slide 24 text

No content

Slide 25

Slide 25 text

Plenty of lightweight options for mobile Lower total cost on size + parse times from the get-go

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

Byte savings @

Slide 29

Slide 29 text

Brotli Display Ads from Google now served using Brotli compression! 40% Data-savings up to 15% in aggregate over gzip https://developers.googleblog.com/

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

WebP 30% smaller than JPEG 25% smaller than PNG

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

WebP Conversion XNConvert Windows/Mac/Linux Can convert in batch Supports most formats Alternatively: imagemin Pixelmator ImageMagick GIMP Leptonica

Slide 34

Slide 34 text

WebP Serving Or use the Accept header + .htaccess to serve WebP if a browser supports it and it exists on disk.

Slide 35

Slide 35 text

Service Workers Inbox by Gmail 10% improvement in Time-to-Interactive

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

Let’s hack

Slide 40

Slide 40 text

ResourceLoadPriorityVeryHigh

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

ResourceLoadPriorityVeryHigh

Slide 44

Slide 44 text

FLUENTinium

Slide 45

Slide 45 text

Original Everything is high priority Preloaded JS Preloaded CSS + fonts

Slide 46

Slide 46 text

Original Everything is high priority JS + CSS is high priority CSS + fonts are high prio

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

.. Link: 1.js; rel="preload"; as="script"

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

bit.ly/prpl-paFern PRPL bit.ly/prpl-paFern

Slide 58

Slide 58 text

HTTP/2 with 3G

Slide 59

Slide 59 text

HTTP/2 + preload with 3G

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

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', ` ; rel=preload; as='style', ; rel=preload; as='script', ; rel=preload; as='script'`)

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

Alternatively: Track cache content using cookies if (supports_http2() && !http_cached('/app.js')) { header('link:; rel=preload; as=script’); setcookie('/app.js', 'is-cached', 0, '/'); }

Slide 67

Slide 67 text

Alternatively: Track cache content using cookies function http_cached($filename) { if ('is-cached' === $_COOKIE[$filename]) { return true; } else { return false; } } Try CASPer

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

Repeat visit with Service Worker

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

Debugging: HTTP/2 Server Push in DevTools

Slide 72

Slide 72 text

Debugging: HTTP/2 Server Push in DevTools

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

PRPL In-A-Box Polymer App Toolbox PREACT CLI

Slide 76

Slide 76 text

No content

Slide 77

Slide 77 text

No content

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

With Service Workers

Slide 80

Slide 80 text

With HTTP/2 Server Push

Slide 81

Slide 81 text

babel-preset-env + per-browser bundles

Slide 82

Slide 82 text

babel-preset-env + per-browser bundles

Slide 83

Slide 83 text

Twitter Lite

Slide 84

Slide 84 text

No content

Slide 85

Slide 85 text

Interactive in <5s on 3G

Slide 86

Slide 86 text

Can we get fast 3G numbers across the board or regular 3G?

Slide 87

Slide 87 text

PRPL Push / Preload

Slide 88

Slide 88 text

18% improvement

Slide 89

Slide 89 text

36% improvement

Slide 90

Slide 90 text

Render

Slide 91

Slide 91 text

HTML Streaming reduced TTFB by 30% (200ms), increasing time user’s spent in the app. Nicolas Gallagher, Technical lead for Twitter Lite

Slide 92

Slide 92 text

No content

Slide 93

Slide 93 text

4x improvement to render perf by using requestIdleCallback() to defer JS loading of images. Nicolas Gallagher, Technical lead for Twitter Lite

Slide 94

Slide 94 text

Heavy image decode Lower image decode

Slide 95

Slide 95 text

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 High-perf Images

Slide 96

Slide 96 text

Data Saver Mode introduced up to 70% savings Next up: Save-Data client hint

Slide 97

Slide 97 text

Precache

Slide 98

Slide 98 text

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.

Slide 99

Slide 99 text

Before Service Worker After Service Worker

Slide 100

Slide 100 text

Lazy-load

Slide 101

Slide 101 text

No content

Slide 102

Slide 102 text

Before code-splitting

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

After code-splitting bit.ly/twitterlite-perf bit.ly/twitter-casestudy

Slide 105

Slide 105 text

No content

Slide 106

Slide 106 text

No content

Slide 107

Slide 107 text

https://meowni.ca/font-style-matcher/

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

No content

Slide 110

Slide 110 text

No content

Slide 111

Slide 111 text

Link: ; rel=preload; as=font; type='font/woff'

Slide 112

Slide 112 text

Heaviest use of rel=preload is for Web Fonts HTTPArchive

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

No content

Slide 116

Slide 116 text

No content

Slide 117

Slide 117 text

bit.ly/font-subsetting

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

/* 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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

Web Font Loading Tips Understand the anatomy of a web font and how browsers load font-display: optional (i.e if you can’t do it fast, load a fallback) Minimize font downloads by limiting range of characters you’re loading Minimize FOIT by using If you need more control try out the Font Loading API 1. 2. 3. 4. 5. https://meowni.ca/posts/web-fonts/

Slide 122

Slide 122 text

No content

Slide 123

Slide 123 text

Streams API bit.ly/streams-ftw Progressive Loading: HTML

Slide 124

Slide 124 text

in body bit.ly/progressive-css Progressive Loading: CSS

Slide 125

Slide 125 text

in body bit.ly/progressive-css Progressive Loading: CSS … … … …

Slide 126

Slide 126 text

No content

Slide 127

Slide 127 text

medium.com/reloading

Slide 128

Slide 128 text

No content