Optimizing an Angular App with the AoT compilation

On a large Angular project, it may happen that there are slowdowns in the page loading. To reduce this time, we have implemented several optimizations, the main one being the AoT compilation.

The AoT compilation provides a pre-compiled version of the application, which eliminates the compilation time when the page is loading. Moreover, with the AoT compilation, it is no longer necessary to integrate the Angular compiler into the application.

Ideally, this compilation must be set up from the beginning of the project, because it is a tedious and difficult task to debug once the codebase is very large.

To do this, we used the webpack AotPlugin in the production configuration (which allows us to keep the conventional JIT compilation during development).

Setting up

First, you will need to install the ngtools / webpack package:

npm install --save-dev @ngtools/webpack

Then add the following rule to your webpack production configuration:

{
  test: /\.ts$/,
  use: ['@ngtools/webpack']
}

Make sure to remove the basic rule on .ts files and to place it only in your local configuration file:

{
  test: /\.ts$/,
  use: [
    'awesome-typescript-loader?silent=true',
    'angular2-template-loader',
    'angular-router-loader'
  ]
}

Finally, add the AoT plugin to the list of plugins in your webpack production configuration:

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

...

new AotPlugin({
 tsConfigPath: 'tsconfig-aot.json',
 entryModule: helpers.root('src/app/app.module#AppModule')
})

The corresponding tsconfig file as well as the main-aot.ts.

Then, set a command of your package.json to create the build with your production configuration:

"build": "rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail"

At first, it will be necessary to correct the various “simple” errors that you will get at the output of your build: a classic error is the use of non-public variables in the html templates.

You may then encounter other issues as you set up the configuration. Here is what has been done to overcome these difficulties: npm-shrinkwrap.

Whatever the package manager you use, it is absolutely essential to create a file to fix the different package versions. It’s really important, and we had it from the beginning of the project development. Without this file, it is difficult to guarantee a clean and clear project from one machine to another.

In addition to the npm shrinkwrap command, we use the npm-shrinkwrap package, which ensures that the node modules, the package.json and the npm-shrinkwrap.json are on the same page. It also prunes some unused fields in the npm-shrinkwrap.

Typescript and node version

It seems that the AoT compilation is not supported by the recent versions of Typescript. We fixed the version at 2.2.2. Similarly for node: we use nvm to manage several node versions simultaneously, as well as a .nvmrc file specifying the node version to use for this project. We fixed the version at 7.10.1. You may encounter this type of error when trying to use the AoT compilation with node 5:

const { __NGTOOLS_PRIVATE_API_2 } = require('@angular/compiler-cli');
     ^
SyntaxError: Unexpected token {
   at exports.runInThisContext (vm.js:53:16)

Priority of Webpack plugins

This is not necessarily obvious, and sometimes it is not important, but the order of the different plugins that you apply in webpack has an influence on the output build. In this case, the AoT plugin must be placed first in the list of plugins of the configuration. Thus, if you need to merge several configurations, the configuration containing the AoT plugin must be the first argument of the webpackMerge. Without this, we could not get our “chunk” files corresponding to the various lazy-loaded modules of the application.

See the article on webpack here.

Bundle Analyzer

This webpack plugin allows analysing the different imports used by the application. It can be used completely outside the AoT setup, it allowed us to optimize the size of our application by removing unnecessary imports.

Results

Before AOT compilation:

Before AOT - Libraries
Before AOT - Libraries

Not surprisingly, the libraries used, as well as Angular itself, represent most of our application (1.3 MB). The total size of the application is 2.8 MB (excluding assets).

Before AOT - Load time
Before AOT - Load time

The application takes about 2 seconds to load.

After AOT compilation:

After AOT - Libraries
After AOT - Libraries

After the AOT compilation, there is no need to provide the angular compiler in your application, which reduces the size of the vendor part to a certain extent. The total size of the application is now 2.06MB, about 700Ko less than before the AOT compilation. Thus, despite the compilation of HTML / CSS templates into Javascript files, the size of the final export is reduced.

After AOT - Load time
After AOT - Load time

The application loading takes now a little more than one second, meaning a time interval practically divided by 2 after the AOT compilation. This gain is very visible when accessing the site via a mobile phone, which javascript engine is probably less powerful. In particular, on versions prior to Android 4.4, there were loading times ranging from 10 to 15 seconds!

Conclusion and possible improvement

Setting up the AOT compilation, although a tedious task, allowed us to gain considerable loading time when launching the application. Together with the lazy-loaded modules, we can offer a pleasant and fluid user experience. It would still be possible to improve the size of the application by analyzing more precisely the libraries used: for example, the moment.js library loads all the package locales, but we do not use them all. To import only the locales actually used, we have added to the list of webpack plugins the following line to load only the French and English locales:

new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en|fr/)