25

I just updated to webpack 2.1.0-beta.15 with an Angular 2 (version rc.2) app (with Typescript), but I was wondering how to use the tree shaking feature. I read it should work "out of the box", but I'm still having a bundle of 1.7Mb for a very simple application, so probably I'm doing something wrong.

This is what I have so far:

tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  },
  "exclude": [
    "node_modules",
    "typings/main",
    "typings/main.d.ts"
  ]
}

package.json

{
  "name": "angular2-quickstart",
  "version": "1.0.0",
  "scripts": {
    "build": "webpack --progress",
    "build:prod": "webpack -p --progress --optimize-minimize",
    "postinstall": "typings install",
    "serve": "webpack-dev-server --inline --progress --output-public-path=/dist/"
  },
  "license": "ISC",
  "dependencies": {
    "@angular/common": "2.0.0-rc.2",
    "@angular/compiler": "2.0.0-rc.2",
    "@angular/core": "2.0.0-rc.2",
    "@angular/forms": "0.1.0",
    "@angular/http": "2.0.0-rc.2",
    "@angular/platform-browser": "2.0.0-rc.2",
    "@angular/platform-browser-dynamic": "2.0.0-rc.2",
    "@angular/router": "2.0.0-rc.2",
    "@angular/router-deprecated": "2.0.0-rc.2",
    "@angular/upgrade": "2.0.0-rc.2",
    "angular-pipes": "1.4.0",
    "bootstrap": "3.3.6",
    "core-js": "2.4.0",
    "file-loader": "0.9.0",
    "jquery": "2.2.3",
    "lodash": "4.13.1",
    "reflect-metadata": "0.1.3",
    "rxjs": "5.0.0-beta.6",
    "script-loader": "0.7.0",
    "style-loader": "0.13.1",
    "url-loader": "0.5.7",
    "zone.js": "0.6.12"
  },
  "devDependencies": {
    "concurrently": "^2.0.0",
    "css-loader": "0.23.1",
    "html-loader": "0.4.3",
    "json-loader": "^0.5.4",
    "raw-loader": "0.5.1",
    "ts-loader": "0.8.2",
    "typescript": "1.8.10",
    "typings": "1.0.4",
    "webpack": "^2.1.0-beta.15",
    "webpack-dev-server": "^2.1.0-beta",
  }
}

webpack.config.js

var webpack = require('webpack');

var PROD = JSON.parse(process.env.PROD_ENV || '0');

module.exports = {
    entry: './app/main.ts',
    output: {
        path: './dist',
        filename: 'app.bundle.js'
    },
    module: {
        loaders: [
        { test: /\.json$/, loader: 'json', include: [/node_modules/] },
        { test: /\.ts$/, loader: 'ts' },
        { test: /\.html$/, loader: 'html' },
        { test: /\.css$/, loader: 'style!css' },
        { test: /\.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000' }
        ]
    },
    resolve: {
        extensions: ['', '.js', '.ts', '.css']
    },
    'htmlLoader': {
        caseSensitive: true
    },
    plugins: PROD ? [
        new webpack.optimize.UglifyJsPlugin({
            compress: { warnings: false }
        })
    ] : []
}

I'm using fairly standard import statements such as import {FORM_DIRECTIVES,REACTIVE_FORM_DIRECTIVES} from '@angular/forms' and trying to import only what I need (import 'rxjs/add/operator/map';) and running the build with npm run build:prod with the environment variable PROD_ENV=1.

Maybe something like jquery or lodash is causing issues (import * as $ from 'jquery' or import {orderBy} from 'lodash'), but I believe both are relatively small. According to the webpack visualizer, Jquery and Lodash account for 6.2% and 12% of the bundle size, respectively. Angular uses 49% of the bundle size.

What am I missing?

r_31415
  • 8,752
  • 17
  • 74
  • 121
  • The question lacks the most important thing - how the packages are used. It doesn't matter if you do `import 'rxjs/add/operator/map`. If the app imports `rxjs` or `rxjs/Rx` somewhere, the entire RxJS is imported. Stick to later Angular versions because later releases are oriented toward footprint improvement. And 49% says literally nothing. Angular is heavily packed with comments. – Estus Flask Jul 09 '16 at 21:16
  • Yes, I forgot to include the version of Angular. It is rc.2. Not sure if I need to do something to avoid the comments (as you can see, there is an uglifier plugin) and I'm using the --optimize-minimize flag in webpack. By the way, I was thinking that maybe I was doing something wrong, not so much complaining about Angular being heavy. – r_31415 Jul 09 '16 at 21:43
  • Well, it is supposed to be heavy, I don't think that tree shaking can do something here. I would suggest to try RC4 and directed RxJS imports, e.g. `import {Observable} from 'rxjs/Observable'`. As for Lodash, modular packages may help https://github.com/lodash/lodash/tree/4.2.1-npm-packages – Estus Flask Jul 09 '16 at 22:16
  • Thank you. After some initial struggles, I updated to RC4. I believe I'm using that kind of imports, but in the case of the map operator, I'm still using `import 'rxjs/add/operator/map`. I hope that is okay. Unfortunately, I haven't seen an improvement. Most of the size is located in core and compiler (12% and 19%, respectively), but maybe that is normal. – r_31415 Jul 10 '16 at 18:10
  • 1
    Yes, that's ok, just keep your eyes open for `'rxjs'` and `'rxjs/Rx'` mass imports. Yes, I'm pretty sure nothing can be done at this point. Hope this will be improved on 2.0 release. Any way, the 'real' figures for A2 are often given as min+gzip. At least they are much less stressful to the eye and show x10 reduction. Btw, [here's a nice article](http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/) that sums this up. – Estus Flask Jul 10 '16 at 18:48
  • Yes, 1,7Mb after minification and 460Kb after gzip in my case. That sounds better, but I'm still looking for a sizable reduction when Angular is released. Thank you for all your help! – r_31415 Jul 10 '16 at 19:14

3 Answers3

7

For treeshaking you have to use typescript 2

npm i typescript@next --save-dev

which supports

tsconfig.json

{
  "compilerOptions": {
    "module": "es6",
    "target": "es5"
   }
}

This is because es6 modules are statically analyzable and Webpack can determine which of your dependencies are used and which are not.

evandertino
  • 358
  • 1
  • 3
  • 1
    Thanks. I tried your suggestion, but I found that now it complains about `Cannot find module '@angular/core'.`. What could be the cause? The imports being used look something like this: `import {Component, ViewChild} from '@angular/core';` – r_31415 Jul 18 '16 at 01:07
  • 1
    I fixed the errors using `"moduleResolution": "node"`, however, after building, I don't see any improvement in the size of app.bundle.js – r_31415 Jul 19 '16 at 16:46
  • Same observation, using typescript 2.0.0 + module: es6 led to 20kb bigger app.js and same vendor.js size (got reflect-metadata, es6-shim and zone.js in there). Ended up with: vendor.js.gz 117KB, app.js.gz 159KB. Good, but could be better still. – Łukasz Biały Aug 06 '16 at 19:24
  • Sadly, that doesn't work. Tried it with this project: https://github.com/blacksonic/typescript-webpack-tree-shaking The resulting bundle still contains the unused class V6Engine. – schlingel Mar 23 '17 at 08:42
1

Tree Shaking wasn't shipped with Webpack 2 and is still blocked without a clear way forward according to Core team comments in the issue. I'm afraid you may be wearing The Emperor's New Clothes.

vhs
  • 9,316
  • 3
  • 66
  • 70
0

Switching to angular-cli currently seems to be the way to go on with Angular apps. Its based on webpack and now even supports aot with tree shaking capabilities. Just use ng build --prod

vidit
  • 643
  • 1
  • 5
  • 13