Search code examples
javascriptangulartypescriptwebpackts-loader

Optimize Angular 2 application build duration with webpack


I build an Angular 2 application and bundle it with webpack. At the moment, my application is still small but the webpack task already takes around 10 seconds. Is it possible to optimize my webpack config or the TypeSript compilation options to improve the compilation and packaging duration ?

This is the webpack config I use :

var webpack = require('webpack');
var LiveReloadPlugin = require('webpack-livereload-plugin');

module.exports = {
  entry: __dirname + '/assets/app/app.ts',
  output: {
    filename: 'myApp.bundle.js',
    path: __dirname + '/build/'
  },
  // Turn on sourcemaps
  devtool: 'source-map',
  resolve: {
    extensions: ['.ts', '.js']
  },
  plugins: [
    new LiveReloadPlugin({
      appendScriptTag: true
    }),
    // Fixes angular 2 warning
    new webpack.ContextReplacementPlugin(
      /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
      __dirname
    )
  ],
  module: {
    rules: [{
        enforce: 'pre',
        test: /\.js$/,
        loader: "source-map-loader"
      },
      {
        enforce: 'pre',
        test: /\.tsx?$/,
        use: "ts-loader"
      }
    ]
  }
}

And the tsconfig :

{
  "compilerOptions": {
    "target": "ES5",
    "module": "commonjs",
    "moduleResolution": "node",
    "sourceMap": true,
    "pretty": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "noUnusedLocals": false,
    "removeComments": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "baseUrl": "./src",
    "typeRoots": ["node_modules/@types"],
    "types": [
      "core-js",
      "systemjs"
    ],
    "outDir": "./build"
  },
  "exclude": [
    "node_modules"
  ]
}

UPDATE (see my answer for the fixed webpack.config)

I give a try to the DLL webpack plugin suggested by @jpwiddy by compiling angular in a separate build, in order to have to rebuild only the application code during developments and gain considerable time of compilation.

However, after an inspection of the output JS, the file size is quite the same and there is still angular code inside.

Here is the new webpack config file for angular sources :

var webpack = require('webpack');

module.exports = {
  entry: {
      angular:[
        '@angular/platform-browser',
        '@angular/platform-browser-dynamic',
        '@angular/core',
        '@angular/common',
        '@angular/compiler',
        '@angular/http',
        '@angular/router',
        '@angular/forms'        
    ]
  },
  output: {
    filename: 'ng2.dll.js',
    path: __dirname + '/build/',
    library: 'ng2'
  },
  plugins: [
    // Fixes angular 2 warning
    new webpack.ContextReplacementPlugin(
      /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
      __dirname
    ),
    new webpack.DllPlugin({ 
        name: 'ng2', 
        path: __dirname + '/build/ng2.json'
    })
  ]
}

And the updated webpack config for application :

var webpack = require('webpack');
var LiveReloadPlugin = require('webpack-livereload-plugin');

module.exports = {
  entry: __dirname + '/assets/app/app.ts',
  output: {
    filename: 'myApp.bundle.js',
    path: __dirname + '/build/'
  },
  // Turn on sourcemaps
  devtool: 'source-map',
  resolve: {
    extensions: ['.ts', '.js']
  },
  plugins: [
    new LiveReloadPlugin({
      appendScriptTag: true
    }),
    // Fixes angular 2 warning
    new webpack.ContextReplacementPlugin(
      /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
      __dirname
    ),
    new webpack.DllReferencePlugin({
      context: __dirname + '/build/',
      manifest: require(__dirname + '/build/ng2.json')
    })
  ],
  module: {
    rules: [{
        enforce: 'pre',
        test: /\.js$/,
        loader: "source-map-loader"
      },
      {
        enforce: 'pre',
        test: /\.tsx?$/,
        use: "ts-loader"
      }
    ]
  }
}

Here is one of the angular code I found in my application JS output :

_TsEmitterVisitor.prototype.visitBuiltintType = function (type, ctx) {
    var typeStr;
    switch (type.name) {
        case __WEBPACK_IMPORTED_MODULE_2__output_ast__["R" /* BuiltinTypeName */].Bool:
            typeStr = 'boolean';
            break;
        case __WEBPACK_IMPORTED_MODULE_2__output_ast__["R" /* BuiltinTypeName */].Dynamic:
            typeStr = 'any';
            break;
        case __WEBPACK_IMPORTED_MODULE_2__output_ast__["R" /* BuiltinTypeName */].Function:
            typeStr = 'Function';
            break;
        case __WEBPACK_IMPORTED_MODULE_2__output_ast__["R" /* BuiltinTypeName */].Number:
            typeStr = 'number';
            break;
        case __WEBPACK_IMPORTED_MODULE_2__output_ast__["R" /* BuiltinTypeName */].Int:
            typeStr = 'number';
            break;
        case __WEBPACK_IMPORTED_MODULE_2__output_ast__["R" /* BuiltinTypeName */].String:
            typeStr = 'string';
            break;
        default:
            throw new Error("Unsupported builtin type " + type.name);
    }
    ctx.print(typeStr);
    return null;
 };

Did I missed something in the new config to prevent webpack including angular sources in the output ?

Thank you


Solution

  • I managed to fix my config with the brand new module webpack-dll-bundles-plugin (which uses DllPlugin and DllReferencePlugin in background) doing exactly what I was looking for : isolating the build of Angular 2 in his own bundle, and avoid rebuilding my whole bundle each time I want to rebuild my application code (eg with a watcher).

    My re-build time dropped from 10sec to 1sec.

    Here is my new webpack config :

    var webpack = require('webpack');
    var LiveReloadPlugin = require('webpack-livereload-plugin');
    const DllBundlesPlugin = require('webpack-dll-bundles-plugin').DllBundlesPlugin;
    
    module.exports = {
      entry: __dirname + '/assets/app/app.ts',
      output: {
        filename: 'myApp.bundle.js',
        path: __dirname + '/build/'
      },
      // Turn on sourcemaps
      devtool: 'source-map',
      resolve: {
        extensions: ['.ts', '.js']
      },
      plugins: [
        new LiveReloadPlugin({
          appendScriptTag: true
        }),
        // Fixes angular 2 warning
        new webpack.ContextReplacementPlugin(
          /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
          __dirname
        ),
        new DllBundlesPlugin({
            bundles: {
              vendor: [
                '@angular/platform-browser',
                '@angular/platform-browser-dynamic',
                '@angular/core',
                '@angular/common',
                '@angular/forms',
                '@angular/http',
                '@angular/router',
                'rxjs',
              ]
            },
            dllDir:  __dirname + '/build/',
            webpackConfig: {}
          })
      ],
      module: {
        rules: [{
            enforce: 'pre',
            test: /\.js$/,
            loader: "source-map-loader"
          },
          {
            enforce: 'pre',
            test: /\.tsx?$/,
            use: "ts-loader"
          }
        ]
      }
    }