Slide 1

Slide 1 text

twitter.com/mgechev github.com/mgechev blog.mgechev.com Faster Applications Minko Gechev

Slide 2

Slide 2 text

twitter.com/mgechev

Slide 3

Slide 3 text

twitter.com/mgechev twitter.com/mgechev github.com/mgechev

Slide 4

Slide 4 text

twitter.com/mgechev

Slide 5

Slide 5 text

twitter.com/mgechev JavaScript is powerful

Slide 6

Slide 6 text

twitter.com/mgechev Complex business applications

Slide 7

Slide 7 text

twitter.com/mgechev Everything comes with a price tag

Slide 8

Slide 8 text

twitter.com/mgechev

Slide 9

Slide 9 text

twitter.com/mgechev

Slide 10

Slide 10 text

twitter.com/mgechev

Slide 11

Slide 11 text

twitter.com/mgechev

Slide 12

Slide 12 text

twitter.com/mgechev

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

twitter.com/mgechev

Slide 16

Slide 16 text

twitter.com/mgechev • Build optimizer • Tree-shakable providers • Compression • Minification Angular (CLI) does much more!

Slide 17

Slide 17 text

twitter.com/mgechev Why should we even care?

Slide 18

Slide 18 text

twitter.com/mgechev

Slide 19

Slide 19 text

twitter.com/mgechev

Slide 20

Slide 20 text

twitter.com/mgechev https://medium.com/dev-channel/the-cost-of-javascript-84009f51e99e

Slide 21

Slide 21 text

twitter.com/mgechev Application specific optimizations

Slide 22

Slide 22 text

twitter.com/mgechev lazy-loading

Slide 23

Slide 23 text

twitter.com/mgechev Lazy-loading strategies Route-Based Component-Based

Slide 24

Slide 24 text

twitter.com/mgechev Lazy-loading strategies Route-Based Component-Based

Slide 25

Slide 25 text

export const appRoutes: Routes = [{ path: '', pathMatch: 'full', redirectTo: 'intro' }, { loadChildren: './intro/intro.module#IntroModule', path: 'intro' }, { loadChildren: './main/main.module#MainModule', path: 'main' }];

Slide 26

Slide 26 text

twitter.com/mgechev Lazy-load all the things!

Slide 27

Slide 27 text

twitter.com/mgechev • Bad UX for subsequent page views • Structural changes • Behavioral changes • Nested routes • Cross-bundle dependencies • Webpack’s extra bundle runtime Code-split gotchas

Slide 28

Slide 28 text

twitter.com/mgechev Lazy-load all the things!??

Slide 29

Slide 29 text

twitter.com/mgechev • Carefully pick the split points • Use prefetching Improve lazy-loading UX

Slide 30

Slide 30 text

twitter.com/mgechev How do we pick the split points?

Slide 31

Slide 31 text

twitter.com/mgechev often subjectively

Slide 32

Slide 32 text

twitter.com/mgechev Data-driven bundling - Users behavior - Bundle sizes

Slide 33

Slide 33 text

twitter.com/mgechev Data-driven bundling - Users behavior - Bundle sizes

Slide 34

Slide 34 text

twitter.com/mgechev Step 1: Open https://example.com/ Step 2: Determine JavaScript which is likely to be required Step 3: Download the chunks Step 4: Store chunks in browser cache Pre-fetching

Slide 35

Slide 35 text

twitter.com/mgechev Prefetching Strategies

Slide 36

Slide 36 text

@NgModule({ bootstrap: [MailAppCmp], imports: [ RouterModule.forRoot(ROUTES, { preloadingStrategy: PreloadAllModules }) ] }) class MailModule {}

Slide 37

Slide 37 text

@NgModule({ bootstrap: [MailAppCmp], imports: [ RouterModule.forRoot(ROUTES, { preloadingStrategy: PreloadAllModules }) ] }) class MailModule {}

Slide 38

Slide 38 text

twitter.com/mgechev Draining user’s network connection

Slide 39

Slide 39 text

export const appRoutes: Routes = [{ path: '', pathMatch: 'full', redirectTo: 'intro' }, { loadChildren: './intro/intro.module#IntroModule', path: 'intro', data: { preload: true } }, { loadChildren: './main/main.module#MainModule', path: 'main' }];

Slide 40

Slide 40 text

export const appRoutes: Routes = [{ path: '', pathMatch: 'full', redirectTo: 'intro' }, { loadChildren: './intro/intro.module#IntroModule', path: 'intro', data: { preload: true } }, { loadChildren: './main/main.module#MainModule', path: 'main' }];

Slide 41

Slide 41 text

export class PreloadSelctedModulesList implements PreloadingStrategy { preload(route: Route, load: Function): Observable { return route.data.preload ? load() : of(null); } }

Slide 42

Slide 42 text

twitter.com/mgechev How do we pick the priorities of the bundles to pre-fetch?

Slide 43

Slide 43 text

twitter.com/mgechev

Slide 44

Slide 44 text

twitter.com/mgechev What if the application changes?

Slide 45

Slide 45 text

twitter.com/mgechev Update the app Collect data Update prefetch logic

Slide 46

Slide 46 text

twitter.com/mgechev Update the app Collect data Update prefetch logic manual work

Slide 47

Slide 47 text

twitter.com/mgechev Nested route resolution

Slide 48

Slide 48 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / twitter.com/mgechev

Slide 49

Slide 49 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / twitter.com/mgechev

Slide 50

Slide 50 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / twitter.com/mgechev

Slide 51

Slide 51 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / twitter.com/mgechev

Slide 52

Slide 52 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / twitter.com/mgechev

Slide 53

Slide 53 text

twitter.com/mgechev Cluster bundles associated with nested routes, when it makes sense

Slide 54

Slide 54 text

twitter.com/mgechev Cluster bundles associated with nested routes, when it makes sense

Slide 55

Slide 55 text

twitter.com/mgechev Cluster bundles associated with nested routes, when it makes sense when the bundles are often requested together

Slide 56

Slide 56 text

twitter.com/mgechev Cluster bundles associated with nested routes, when it makes sense when the bundles are often requested together when bundles are not too big

Slide 57

Slide 57 text

twitter.com/mgechev • Lazy-load! • Lazy-load based on data • Overfetching matters • Use data-driven prefetching of assets • Beware of cascading route resolution • Use link[rel=“preload|prefetch”] Tips on lazy-loading

Slide 58

Slide 58 text

twitter.com/mgechev Tooling

Slide 59

Slide 59 text

twitter.com/mgechev • Manage the bundle sizes • Prefetch only the bundles likely to be used • Cluster small bundles in nested routes The tooling should

Slide 60

Slide 60 text

twitter.com/mgechev Prefetch tooling for webpack webpack 4 preload-webpack-plugin import( !/* webpackPrefetch: true !*/ '!!...'); plugins: [ new HtmlWebpackPlugin(), new PreloadWebpackPlugin() ]

Slide 61

Slide 61 text

twitter.com/mgechev Prefetch bundles which might be required in future No manual prefetching logic Uses link[rel=“prefetch|preload”]

Slide 62

Slide 62 text

twitter.com/mgechev Prefetch bundles which might be required in future No manual prefetching logic Uses link[rel=“prefetch|preload”] Over-fetching & bandwidth consumption Does not allow prioritization of bundles Does not work with content

Slide 63

Slide 63 text

twitter.com/mgechev

Slide 64

Slide 64 text

twitter.com/mgechev slide by @addyosmani

Slide 65

Slide 65 text

twitter.com/mgechev $ npm run eject $ vim webpack.config.js

Slide 66

Slide 66 text

twitter.com/mgechev const { GuessPlugin } = require('guess-webpack'); !// !!... plugins: [ !// !!... new GuessPlugin({ GA: 'XXXXXXXXX' }) ] !// !!...

Slide 67

Slide 67 text

twitter.com/mgechev /a /a/a /a/b /b /b/a /

Slide 68

Slide 68 text

twitter.com/mgechev Markov Chain / /a /a/:id /b /b/a / 0 0.3 0 0.7 0 /a 0 0 0.9 0.1 0 /a/:id 0 1 0 0 0 /b 0 0 0 0 1 /b/a 0 1 0 0 0 /a /a/a /a/b /b /b/a /

Slide 69

Slide 69 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / Activity: / /a /a/:id /b /b/a / 0 0.3 0 0.7 0 /a 0 0 0.9 0.1 0 /a/:id 0 1 0 0 0 /b 0 0 0 0 1 /b/a 0 1 0 0 0 Probability threshold: 0.5

Slide 70

Slide 70 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / Activity: / /a /a/:id /b /b/a / 0 0.3 0 0.7 0 /a 0 0 0.9 0.1 0 /a/:id 0 1 0 0 0 /b 0 0 0 0 1 /b/a 0 1 0 0 0 Probability threshold: 0.5

Slide 71

Slide 71 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / Activity: / /a /a/:id /b /b/a / 0 0.3 0 0.7 0 /a 0 0 0.9 0.1 0 /a/:id 0 1 0 0 0 /b 0 0 0 0 1 /b/a 0 1 0 0 0 Probability threshold: 0.5

Slide 72

Slide 72 text

twitter.com/mgechev / /a /a/:id /b /b/a / 0 0.3 0 0.7 0 /a 0 0 0.9 0.1 0 /a/:id 0 1 0 0 0 /b 0 0 0 0 1 /b/a 0 1 0 0 0 /a /a/a /a/b /b /b/a / Activity: - Download b.bundle.js Probability threshold: 0.5

Slide 73

Slide 73 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / Activity: - Download b.bundle.js / /a /a/:id /b /b/a / 0 0.3 0 0.7 0 /a 0 0 0.9 0.1 0 /a/:id 0 1 0 0 0 /b 0 0 0 0 1 /b/a 0 1 0 0 0 Probability threshold: 0.5

Slide 74

Slide 74 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / Activity: - Download b.bundle.js / /a /a/:id /b /b/a / 0 0.3 0 0.7 0 /a 0 0 0.9 0.1 0 /a/:id 0 1 0 0 0 /b 0 0 0 0 1 /b/a 0 1 0 0 0 Probability threshold: 0.5

Slide 75

Slide 75 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / / /a /a/:id /b /b/a / 0 0.3 0 0.7 0 /a 0 0 0.9 0.1 0 /a/:id 0 1 0 0 0 /b 0 0 0 0 1 /b/a 0 1 0 0 0 Activity: - Download b.bundle.js - No action Probability threshold: 0.5

Slide 76

Slide 76 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / / /a /a/:id /b /b/a / 0 0.3 0 0.7 0 /a 0 0 0.9 0.1 0 /a/:id 0 1 0 0 0 /b 0 0 0 0 1 /b/a 0 1 0 0 0 Activity: - Download b.bundle.js - No action Probability threshold: 0.5

Slide 77

Slide 77 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / / /a /a/:id /b /b/a / 0 0.3 0 0.7 0 /a 0 0 0.9 0.1 0 /a/:id 0 1 0 0 0 /b 0 0 0 0 1 /b/a 0 1 0 0 0 Activity: - Download b.bundle.js - No action Probability threshold: 0.5

Slide 78

Slide 78 text

twitter.com/mgechev /a /a/a /a/b /b /b/a / / /a /a/:id /b /b/a / 0 0.3 0 0.7 0 /a 0 0 0.9 0.1 0 /a/:id 0 1 0 0 0 /b 0 0 0 0 1 /b/a 0 1 0 0 0 Activity: - Download b.bundle.js - No action - Download a.bundle.js Probability threshold: 0.5

Slide 79

Slide 79 text

twitter.com/mgechev • https://mgv.io/guess • https://mgv.io/dd-bundling • https://mgv.io/cost-of-js • https://mgv.io/predictive-fetching Resources

Slide 80

Slide 80 text

Runtime Performance

Slide 81

Slide 81 text

twitter.com/mgechev Simplified Business application

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

AppComponent EmployeesListComponent

Slide 84

Slide 84 text

AppComponent EmployeesListComponent

Slide 85

Slide 85 text

AppComponent EmployeesListComponent

Slide 86

Slide 86 text

{{ item.label }} {{ calculate(item.num) }} !

Slide 87

Slide 87 text

{{ item.label }} {{ calculate(item.num) }} !

Slide 88

Slide 88 text

{{ item.label }} {{ calculate(item.num) }} !

Slide 89

Slide 89 text

{{ item.label }} {{ calculate(item.num) }} !

Slide 90

Slide 90 text

@Component(!!...) export class EmployeeListComponent { @Input() data: EmployeeData[]; @Output() remove = new EventEmitter(); @Output() add = new EventEmitter(); handleKey(event: any) { !!... } calculate(num: number) { return fibonacci(num); } }

Slide 91

Slide 91 text

@Component(!!...) export class EmployeeListComponent { @Input() data: EmployeeData[]; @Output() remove = new EventEmitter(); @Output() add = new EventEmitter(); handleKey(event: any) { !!... } calculate(num: number) { return fibonacci(num); } }

Slide 92

Slide 92 text

AppComponent EmployeesListComponent data

Slide 93

Slide 93 text

@Component(!!...) export class EmployeeListComponent { @Input() data: EmployeeData[]; @Output() remove = new EventEmitter(); @Output() add = new EventEmitter(); handleKey(event: any) { !!... } calculate(num: number) { return fibonacci(num); } }

Slide 94

Slide 94 text

const fibonacci = n !=> { if (n !!=== 1 !|| n !!=== 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2); };

Slide 95

Slide 95 text

Slowing it down artificially

Slide 96

Slide 96 text

twitter.com/mgechev Application Structure • An application component • Two list components • Slow computation for each entry

Slide 97

Slide 97 text

twitter.com/mgechev Real data…

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

140 entries

Slide 100

Slide 100 text

No content

Slide 101

Slide 101 text

Why that slow?

Slide 102

Slide 102 text

No content

Slide 103

Slide 103 text

AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1 E2 En … Ignored details for simplicity

Slide 104

Slide 104 text

AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1 E2 En … Ignored details for simplicity

Slide 105

Slide 105 text

AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1 E2 En … Ignored details for simplicity

Slide 106

Slide 106 text

AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1 E2 En … Ignored details for simplicity

Slide 107

Slide 107 text

AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1 E2 En … Ignored details for simplicity

Slide 108

Slide 108 text

AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1 E2 En … Ignored details for simplicity

Slide 109

Slide 109 text

AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1 E2 En … Ignored details for simplicity

Slide 110

Slide 110 text

twitter.com/mgechev Ideas for optimization?

Slide 111

Slide 111 text

twitter.com/mgechev OnPush Change Detection Strategy

Slide 112

Slide 112 text

twitter.com/mgechev With OnPush change detection will be triggered when the framework, with reference check, determines that any of the inputs of a component has changed…

Slide 113

Slide 113 text

twitter.com/mgechev Passing new reference triggers the change detection

Slide 114

Slide 114 text

twitter.com/mgechev Should we copy the array every time?

Slide 115

Slide 115 text

Why would we do that…?

Slide 116

Slide 116 text

No content

Slide 117

Slide 117 text

twitter.com/mgechev Immutable.js helps: • We get a new reference on change • We do not copy the entire data structure

Slide 118

Slide 118 text

twitter.com/mgechev Let’s see how fast it is now!

Slide 119

Slide 119 text

No content

Slide 120

Slide 120 text

Non Optimized On Push Typing Speed

Slide 121

Slide 121 text

~3x faster but still slow…

Slide 122

Slide 122 text

No content

Slide 123

Slide 123 text

twitter.com/mgechev With OnPush change detection will be triggered when the framework, with reference check, determines that any of the inputs of a component has changed…or when an event in the component is triggered

Slide 124

Slide 124 text

twitter.com/mgechev Let’s do some refactoring!

Slide 125

Slide 125 text

AppComponent EmployeesListComponent NameInputComponent ListComponent

Slide 126

Slide 126 text

AppComponent EmployeesListComponent NameInputComponent ListComponent

Slide 127

Slide 127 text

AppComponent EmployeesListComponent NameInputComponent ListComponent

Slide 128

Slide 128 text

No content

Slide 129

Slide 129 text

twitter.com/mgechev Sooo much faster!

Slide 130

Slide 130 text

AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1 E2 En … ListComponenet (sales) ListComponenet (r&d) Ignored details for simplicity

Slide 131

Slide 131 text

AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1 E2 En … ListComponenet (sales) ListComponenet (r&d) Ignored details for simplicity

Slide 132

Slide 132 text

AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1 E2 En … ListComponenet (sales) ListComponenet (r&d) Ignored details for simplicity

Slide 133

Slide 133 text

AppComponent EmployeeListComponenet (sales) EmployeeListComponenet (r&d) E1 E2 En … E1 E2 En … ListComponenet (sales) ListComponenet (r&d) Ignored details for simplicity

Slide 134

Slide 134 text

Unoptimized Refactored with On Push Typing Speed

Slide 135

Slide 135 text

Adding items

Slide 136

Slide 136 text

twitter.com/mgechev Recomputing everything every time we add a new entry

Slide 137

Slide 137 text

const fibonacci = n !=> { if (n !!=== 1 !|| n !!=== 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2); };

Slide 138

Slide 138 text

// Two properties // - No side effects // - Same result for same arguments const fibonacci = n !=> { if (n !!=== 1 !|| n !!=== 2) return 1; return fibonacci(n - 1) + fibonacci(n - 2); };

Slide 139

Slide 139 text

twitter.com/mgechev Pure Function

Slide 140

Slide 140 text

twitter.com/mgechev Pipes in Angular • Pure • Impure

Slide 141

Slide 141 text

{{ birthday | date }} {{ birthday | impureDate }}

Slide 142

Slide 142 text

{{ birthday | date }} {{ birthday | impureDate }} i1.ɵunv(_v, 1, 0, _ck(_v, 2, 0, i1.ɵnov(_v, 0), _co.birthday)); i1.ɵunv(_v, 1, 1, i1.ɵnov(_v, 3).transform(_co.birthday));

Slide 143

Slide 143 text

{{ birthday | date }} {{ birthday | impureDate }} i1.ɵunv(_v, 1, 0, _ck(_v, 2, 0, i1.ɵnov(_v, 0), _co.birthday)); i1.ɵunv(_v, 1, 1, i1.ɵnov(_v, 3).transform(_co.birthday));

Slide 144

Slide 144 text

twitter.com/mgechev Angular executes a pure pipe only when it detects a change to the input value. A pure change is either a change to a primitive input value (String, Number, Boolean, Symbol) or a changed object reference (Date, Array, Function, Object).

Slide 145

Slide 145 text

@Pipe({ name: 'calculate', pure: true }) export class CalculatePipe { transform(num: number) { return fibonacci(num); } }

Slide 146

Slide 146 text

@Component({ changeDetection: ChangeDetectionStrategy.OnPush, template: ` !!... {{ item.num | calculate }} !!... ` }) export class ListComponent { !!... }

Slide 147

Slide 147 text

twitter.com/mgechev Lets benchpress it!

Slide 148

Slide 148 text

No content

Slide 149

Slide 149 text

Refactored with On Push Calculation in a Pure Pipe Adding / Removing Entries

Slide 150

Slide 150 text

twitter.com/mgechev • Use OnPush in an isolated context • Consider immutable.js with OnPush • Use pure pipes instead of method calls Tips on runtime performance

Slide 151

Slide 151 text

twitter.com/mgechev mgv.io/ng-cd - Angular’s OnPush Change Detection Strategy mgv.io/ng-pure - Pure Pipes and Referential Transparency mgv.io/ng-diff - Understanding Angular Differs mgv.io/ng-perf-checklist - Angular Performance Checklist mgv.io/ng-checklist-video - Angular Performance Checklist (video) Resources

Slide 152

Slide 152 text

Thank you! twitter.com/mgechev github.com/mgechev blog.mgechev.com