Workout plan to lose kilobytes

We talk about JavaScript. Each month in Warsaw, Poland.

Speaker

Paweł Kondraciuk

Workout plan to lose kilobytes

2017-06-14

@pawelkondraciuk

Who Am I

Who Am I

Specs

Library Version Description
@angular 4.1.3 core, compiler, router
Rxjs 5.0.3 Observable, timer, map
lodash 4.17.4 _.repeat
Webpack 2.6.1 Bundler

Statistics

source: Akamai 10For10, KissMetrics

Every Second Counts

source: Amazon, Walmart, KissMetrics

That's not all!

Mobile vs Tablet vs Desktop

source: StatCounter (2.5mln sites)

Download time for 100KB

App bundle

3.9MB => 10min @ 3G

Minification, obfuscation and optimisation

Minification - Webpack + UglifyJsPlugin

            
                new webpack.optimize.UglifyJsPlugin({
                    beautify: false,
                    output: { comments: false },
                    compress: {
                        unused: true,
                        dead_code: true
                    }
                })
            
		

App bundle + UglifyJs

1.3MB => 3:25min @ 3G

Closure*

* UglifyJS is more than enough on a daily basis

            
                function square(x) { return x * x };
                function cube(x) { return x * x * x};
                let x = 5;
                if (x % 2 === 0) {
                    console.log(square(x)); 
                } else {
                    console.log(cube(x));
                }
            
		
After optimisation
                
                    console.log(125);
                
            

Don't believe?

Deeper analysis of the application

Deeper analysis of the application

Graph analyse

Deeper analysis of the application

Webpack - what does it do?

            
                // webpack.config.js
                const path = require('path');

                module.exports =  = {
                    entry: './src/main.ts',
                    output: {
                        path: path.resolve(__dirname, 'dist'),
                        filename: 'app.js'
                    }
                };
            
        
app.js A B C
No optimisation

Bundle splitting

            
                module.exports = {
                    entry: {
                        app: './src/main.ts',
                        vendor: './src/vendor.ts'
                    },
                    plugins: {
                        new webpack.optimize.CommonsChunkPlugin({
                            name: ['app', 'vendor']
                        }),
                    }
                };
            
        
app.js A B C vendor.js
No optimisation
No optimisation
            
                module.exports = {
                    entry: {
                        app: './src/main.ts'
                    },
                    plugins: {
                        new webpack.optimize.CommonsChunkPlugin({
                            name: 'vendor',
                            chunks: ['app'],
                            minChunks: module => /node_modules/.test(module.resource)
                        }),
                    }
                };
            
        
app.js A B C vendor.js
No optimisation

Bundling

            
                // maths.js
                export function square(x) { return x * x; }
                export function cube(x) { return x * x * x; }
                
                // main.js
                import { cube } from './maths.js';
                console.log(cube(5)); // 125
            
		
            
                /***/ (function(module, exports, __webpack_require__) {
                    "use strict";
                    exports.__esModule = true;
                    exports.square = square;
                    exports.cube = cube;
                    function square(x) {
                        return x * x;
                    }
                    function cube(x) {
                        return x * x * x;
                    }
                /***/ }),
            
		

Tree shaking

Tree Shaking - Webpack

            
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                options: {
                    presets: [
                        [ 'es2015', { modules: false } ] // default CommonJS
                    ]
                }
            
		
            
                /***/ (function(module, __webpack_exports__, __webpack_require__) {
                    "use strict";
                    /* unused harmony export square */
                    /* harmony export (immutable) */ __webpack_exports__["a"] = cube;
                    function square(x) {
                        return x * x;
                    }
                    function cube(x) {
                        return x * x * x;
                    }
                /***/ }),
            
        

Bundle + Minification = Tree Shaking

            
                function(module, __webpack_exports__, __webpack_require__) {
                    "use strict";
                    function cube(x) {
                        return x * x * x;
                    }
                    __webpack_exports__.a = cube;
                }
            
        
No optimisation

App bundle + UglifyJs + Tree Shaking

1.1MB => 2:54min @ 3G
Performance

@angular/compiler

Ahead of Time compilation

Compiler map

@angular/compiler

            
                <h1>Hello World!</h1>
            
        
            
                // Compiles into
                ...
                const parentRenderNode:any = this.renderer.createViewRoot(this.parentElement);
                this._el_0 = import3.createRenderElement(this.renderer,parentRenderNode,'h1',import3.EMPTY_INLINE_ARRAY,(null as any));
                this._text_1 = this.renderer.createText(this._el_0,'Hello World!',(null as any));
                this._text_2 = this.renderer.createText(parentRenderNode,'\n',(null as any));
                ...
            
        

@ngtools/webpack

@ngtools/webpack

                
                    let AotPlugin = require('@ngtools/webpack').AotPlugin;

                    module.exports = {
                        rules: [{ test: /\.ts/, use: '@ngtools/webpack' }]
                        plugins: [
                            new AotPlugin({
                                tsConfigPath: path.resolve('tsconfig.json'),
                                entryModule: path.resolve('app.module#AppModule')
                            })
                        ]
                    }
                
            
AoT performance
Aot source map

Angular + UglifyJs + Tree Shaking + AoT

792KB => 2:02min @ 3G

Who needs all modules at once?

Lazy loading

Lazy loading

            
                const routes: Routes = [
                    { path: '', component: Component },
                    {
                        path: 'lazy',
                        loadChildren: './+lazy/lazy.module#LazyModule'
                    }
                ];
            
		

Lazy loading - preloading strategy

            
                import {
                    RouterModule, PreloadAllModules
                } from '@angular/router';
                ...
                imports: [
                    RouterModule.forRoot(routes, {
                        preloadingStrategy: PreloadAllModules 
                    })
                ],
            
        

GZip compression

			.selector { /* ... */ }
			.selector .child-selector { /* ... */ }
		
bootstrap.min.css 118KB
bootstrap.min.css.gz 19KB
Difference 83,9%

compression-webpack-plugin

            
                new CompressionPlugin({
                    regExp: /\.css$|\.html$|\.js$$/,
                    threshold: 2 * 1024
                })
            
		
            
                AddOutputFilterByType DEFLATE text/javascript
                AddOutputFilterByType DEFLATE text/css
                AddOutputFilterByType DEFLATE text/html
            
		

Angular + UglifyJs + Tree Shaking + AoT + GZip

134KB => 0:20sec @ 3G

Resources

@pawelkondraciuk

See you next month at WarsawJS