Angular 2 AoT compilation with Webpack

In this article I am going to explain how to enable Angular 2 Ahead-of-Time (AoT) template compilation with Webpack 2, using the @ngtools/webpack loader.

Ahead-of-Time (AoT, also called “offline”) template compilation results in smaller application code size and faster initial rendering compared to Just-in-Time (JiT, also called “dynamic”) template compilation. If you’d like more details on those two options have a look a the Angular documentation. Also, Tobias Bosch in this Angular 2 Compiler talk gave a very interesting explanation of why the AoT compiler was introduced.

The @angular/compiler-cli package provides a command line tool called ngc that can perform template compilation, however using it with Webpack it’s a bit cumbersome.

Thankfully there is now a Webpack loader available in the form of the @ngtools/webpack package, making Webpack integration a lot nicer. It was developed for use in the Angular CLI so it’s part of the same repository, but we can use it for our own Webpack projects as well.

As an example I have a very basic Angular 2 Webpack project in my angular2-course-webpack-starter repository. It’s based on Webpack 1, that’s the current stable version, and doesn’t support AoT template compilation.

For the impatient, “just show me the code” types among you, the same project but with AoT support is available in the webpack2-aot branch, and here’s a diff showing the relevant changes made to enable AoT.

Now let me go through those changes. First of all, we need to use Webpack 2, that is currently in beta. Unlike Webpack 1, Webpack 2 supports ES2015 modules (also known as ES6 modules, or ESM for short) and we need that feature because the Angular template compiler emits code with es2015 modules.

You can install the beta version of Webpack and its dev server with

npm install --save-dev webpack@beta webpack-dev-server@beta

Upgrading to Webpack 2 requires a few tweaks to the Webpack configuration in webpack.config.js. For example, resolve.extensions no longer requires, and in fact will reject, passing an empty string. Also you may get a strange warning saying Critical dependency: the request of a dependency is an expression unless you add some equally obscure ContextReplacementPlugin configuration. (I’m sure there’s a proper explanation for it, I’m just not sure it’s worth my time trying to fully understand it.)

Once you get Webpack 2 working again, we can add the @ngtools/webpack loader, and that also requires the compiler-cli and platform-server Angular packages to perform template compilation:

npm install --save-dev @ngtools/webpack @angular/compiler-cli @angular/platform-server

Note that at the time of this writing @ngtools/webpack is at version 1.1.2. If you’re using a later version it’s always possible that something may have changed. (If so, let me know and I’ll update the instructions below.)

We can then use @ngtools/webpack in webpack.config.js as the loader for .ts files, replacing the existing TypeScript loader, in my case ts-loader:

  module: {
    loaders: [
      { test: /\.ts$/, loader: '@ngtools/webpack' },

So the @ngtools/webpack loader also takes care of compiling TypeScript code (in fact, it wraps the TypeScript compiler) while performing template compilation. Since compiling Angular templates requires parsing the TypeScript code anyway it’s more efficient to do the TypeScript compilation as well in the same pass.

The loader also takes care of automatically rewriting our bootstrap code in main.ts to call bootstrapModuleFactory and not use platformBrowserDynamic, so there’s no need to keep two separate main files, one for JiT and one for AoT.

For all that to work however we also need to add the AotPlugin exported by the @ngtools/webpack package to our array of plugins:

var ngToolsWebpack = require('@ngtools/webpack');
// ...
  plugins: [
    // ...
    new ngToolsWebpack.AotPlugin({
      tsConfigPath: './tsconfig.json',
      entryModule: './src/app/app.module#AppModule'
    })

The AotPlugin expects a tsConfig option pointing to the TypeScript configuration file, and it’s good to also specify the entryModule i.e. the main @NgModule for our app.

At this point we need to tweak our tsconfig.json configuration a little bit, instructing the TypeScript compiler to emit code including es2015 modules. Such modules can be better optimised by Webpack 2.

  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "sourceMap": true

It may seem strange to target es5 code but with es2015 modules, however that’s a combination supported by the TypeScript compiler. Webpack will then take care of converting es2015 modules into code that works in any es5-compliant web browser.

Make sure to also enable source maps, otherwise you may get an error from the loader saying it could not parse some string as JSON. (Probably a small bug that may be fixed in a future release.)

At this point you should be able to build your application with AoT, e.g. using the production settings

npm run build:prod

In my tests the app.bundle.js size went from 524K with Webpack 1 and JiT to 253K with Webpack 2 and AoT. After gzip compression that becomes 53K, which is pretty good.

That’s all very well, however at this stage the webpack-dev-server won’t work properly any more. I couldn’t find a way to make the @ngtools/webpack loader play nicely with automatic refresh. I think it has to do with AoT compilation happening in two phases: first it generates *.ngfactory.ts files for each Angular template first, then it compiles all the code.

The bottom line is that I had to tweak the Webpack configuration to only use AoT for production builds, and stick to JiT for dev builds including for the dev server. I use an environment variable called APP_ENVIRONMENT to tell which kind of build it is, so I’m using the same variable to decide whether to use the @ngtools/webpack loader and the AotPlugin, or the ts-loader together with the angular2-template-loader for JiT template compilation.

Once again you can find the full working example in this repository.

13 thoughts on “Angular 2 AoT compilation with Webpack

  1. estechco says:

    Whist I am loving the improvements it makes to bundle sizes and can get your own seed example working as expected, I have had some fairly worrying issues when I applied the same changes to a different seed project of my own.

    Whilst the “npm run build” continues to work as it did before, the “npm run build:prod” can often produce quite different results. Initially being far stricter with its compilation (which I could resolve), but then compiling fully and without error but still producing fundamental errors at initial runtime e.g.

    app.js:676 EXCEPTION: Cannot read property ‘replace’ of undefined

    For the record this is not a complex seed project:- simply using a few simple Material components on a single page and does not reference “replace” explicitly within the typescript.

    Whilst I am not expecting you to help resolve my issues for me, I wonder if it highlights fairly fundamental issues either in Webpack 2 (alpha) or @ngtools/webpack? Your thoughts about robustness/suitability (or timescales in which it might be) would be much appreciated?

    Like

    • mirkonasato says:

      Webpack 2 is currently beta (not alpha). I personally haven’t found any major issues with it. The roadmap is here: https://webpack.github.io/docs/roadmap.html

      As for your specific error it’s impossible for me to guess what may be causing it without having access to the code and investigating. Note however that in my starter project the prod build simply uses the production shortcut flag; you can selectively enable different optimisations in the webpack config instead and see what difference they make.

      Like

      • estechco says:

        Sorry, yes, I spotted the alpha/beta mistake after I pressed post.

        I will tinker with the flags and chop sections out of my code to try and narrow down the problem in my instance.

        So you haven’t experienced any obvious problems with bigger projects than your seed example?

        Like

      • estechco says:

        On closer inspection, Webpack isn’t to blame for my problems. It seems to be more about the rather less forgiving @ngtools/webpack (versus angular2-template) as the ts loader?

        My test project contains simplistic use of two separate modules for @ng-bootstrap/ng-bootstrap and @angular/material, which I have now dug into, one at a time.

        The former I could eventually get to work once I moved to 1.0.0-alpha.10 from 1.0.0-alpha.8.

        The latter I remain unable to get working, even with a trivial use of material based components. It seems to be this that is generating the ‘replace’ errors mentioned already. I am using the latest (2.0.0-alpha.9-3) version, so perhaps it this combination that is not quite ready for adoption.

        Like

  2. Dark19 says:

    Hello. I use scroll plugin angular2-perfect-scrollbar, on the developer mode one good work, but on the production mode when I compile my app I have an error: Failed at the angular2-starter@0.2.5 build:prod script ‘cross-env APP_ENVIRONMENT=production webpack -p –progress’.
    npm ERR! Make sure you have the latest version of node.js and npm installed.
    npm ERR! If you do, this is most likely a problem with the angular2-starter package,
    npm ERR! not with npm itself.
    But I have the latest version of node and npm. When I am not importing this scroll, then production compilation is good. What could it be?

    Like

    • estechco says:

      I’ve seen the same error message and it has never been as it describes i.e. versions of npm/node etc. Sadly I can’t quite recall how I resolved my instances, but stick with it!

      As you’ll see from my earlier comment it may simply be that 0.2.5 isn’t yet production ready in terms of full AOT

      Like

      • estechco says:

        Mirko,

        I continue to find this process excruciatingly hard to make progress with.

        I noted that you had inched your own repo forward slightly (to ng 2.2.3) so did likewise without major concern.

        My bundles build, but any use of @angular/material still result in runtime errors.

        I also noted you were now using @ngtools/webpack 1.1.9. However, by simply changing that one entry, the entire compilation fails and npm-debug.log isn’t proving especially helpful.

        Revert back and I return to the previous state.

        This update is primarily just for information only, but if you have any words of wisdom or encouragement to help me through this tiresome maze, I would be grateful?

        Regards

        David

        Like

    • mirkonasato says:

      “Make sure you have the latest version of node.js and npm installed”

      That’s just a generic message, any error while running an npm script will contain that text. Hopefully there should be some more detailed information somewhere in the output?

      Like

      • Dark19 says:

        npm-debug.log file:

        0 info it worked if it ends with ok
        1 verbose cli [ ‘C:\\Program Files\\nodejs\\node.exe’,
        1 verbose cli ‘C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js’,
        1 verbose cli ‘run’,
        1 verbose cli ‘build:prod’ ]
        2 info using npm@3.10.9
        3 info using node@v6.9.2
        4 verbose run-script [ ‘prebuild:prod’, ‘build:prod’, ‘postbuild:prod’ ]
        5 info lifecycle angular2-starter@0.2.5~prebuild:prod: angular2-starter@0.2.5
        6 silly lifecycle angular2-starter@0.2.5~prebuild:prod: no script for prebuild:prod, continuing
        7 info lifecycle angular2-starter@0.2.5~build:prod: angular2-starter@0.2.5
        8 verbose lifecycle angular2-starter@0.2.5~build:prod: unsafe-perm in lifecycle true
        9 verbose lifecycle angular2-starter@0.2.5~build:prod: PATH: C:\Program Files\nodejs\node_modules\npm\bin\node-gyp-bin;C:\Users\Pasha\PhpstormProjects\kodiAngular(min)\node_modules\.bin;C:\Program Files\ConEmu\ConEmu\Scripts;C:\Program Files\ConEmu;C:\Program Files\ConEmu\ConEmu;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Microsoft SQL Server\110\Tools\Binn\ManagementStudio\;C:\Program Files (x86)\Microsoft SQL Server\110\Tools\Binn\;C:\Program Files (x86)\Microsoft SQL Server\110\DTS\Binn\;C:\Program Files (x86)\Skype\Phone\;C:\Program Files\nodejs\;C:\Users\Pasha\AppData\Local\Programs\Git\cmd;C:\Users\Pasha\AppData\Roaming\npm
        10 verbose lifecycle angular2-starter@0.2.5~build:prod: CWD: C:\Users\Pasha\PhpstormProjects\kodiAngular(min)
        11 silly lifecycle angular2-starter@0.2.5~build:prod: Args: [ ‘/d /s /c’,
        11 silly lifecycle ‘cross-env APP_ENVIRONMENT=production webpack -p –progress’ ]
        12 silly lifecycle angular2-starter@0.2.5~build:prod: Returned: code: 1 signal: null
        13 info lifecycle angular2-starter@0.2.5~build:prod: Failed to exec build:prod script
        14 verbose stack Error: angular2-starter@0.2.5 build:prod: `cross-env APP_ENVIRONMENT=production webpack -p –progress`
        14 verbose stack Exit status 1
        14 verbose stack at EventEmitter. (C:\Program Files\nodejs\node_modules\npm\lib\utils\lifecycle.js:255:16)
        14 verbose stack at emitTwo (events.js:106:13)
        14 verbose stack at EventEmitter.emit (events.js:191:7)
        14 verbose stack at ChildProcess. (C:\Program Files\nodejs\node_modules\npm\lib\utils\spawn.js:40:14)
        14 verbose stack at emitTwo (events.js:106:13)
        14 verbose stack at ChildProcess.emit (events.js:191:7)
        14 verbose stack at maybeClose (internal/child_process.js:877:16)
        14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5)
        15 verbose pkgid angular2-starter@0.2.5
        16 verbose cwd C:\Users\Pasha\PhpstormProjects\kodiAngular(min)
        17 error Windows_NT 6.3.9600
        18 error argv “C:\\Program Files\\nodejs\\node.exe” “C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js” “run” “build:prod”
        19 error node v6.9.2
        20 error npm v3.10.9
        21 error code ELIFECYCLE
        22 error angular2-starter@0.2.5 build:prod: `cross-env APP_ENVIRONMENT=production webpack -p –progress`
        22 error Exit status 1
        23 error Failed at the angular2-starter@0.2.5 build:prod script ‘cross-env APP_ENVIRONMENT=production webpack -p –progress’.
        23 error Make sure you have the latest version of node.js and npm installed.
        23 error If you do, this is most likely a problem with the angular2-starter package,
        23 error not with npm itself.
        23 error Tell the author that this fails on your system:
        23 error cross-env APP_ENVIRONMENT=production webpack -p –progress
        23 error You can get information on how to open an issue for this project with:
        23 error npm bugs angular2-starter
        23 error Or if that isn’t available, you can get their info via:
        23 error npm owner ls angular2-starter
        23 error There is likely additional logging output above.
        24 verbose exit [ 1, true ]

        If I run command npm run build and in main.ts add enableProdMode();? Will it be AOT build?

        Like

      • mirkonasato says:

        No, enableProdMode() is a separate thing from AoT.

        I don’t see any useful info in the full log either I’m afraid. You can try running the webpack command on its own to see if you get some more meaningful error.

        Like

  3. Dark19 says:

    I run webpack-dev-server with mode: webpack-dev-server –inline –hot –progress, but refresh after my changes in code or styles not working,why?

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s