Start learning today

Choose Your Plan

Build real life apps. Learn by creating.

Pay Monthly

12

Pay Yearly

10

JIT vs AOT in Angular2 and how to use it

Before we begin coding, we need to know what JIT (Just In Time) and AOT (Ahead of Time) compilation is and what the potential gain is of using AOT instead of JIT in the case of angular2.

JIT

With traditional web apps built with angular1 we use JIT compilation everyday. We may not know it and we may not understand it completely but we do heavily rely on JIT. But, when do we use it? The answer is quiet simple and obvious from the name, i.e. just before the app is run in your browser.

But JavaScript does not compile like other strictly typed languages, right?

Wrong! This is one of the myths associated with JavaScript that there is no compile time. Not only is there a compiler compiling your code, it does it on the fly, right before your JavaScript is loaded by the browser, thus making your user wait a teeny bit longer than you would have liked.

JIT is necessary for the browsers because they themselves do not understand any binaries served to them. Hence once we load the JavaScript file(s), they are compiled to the corresponding binary which can then be interpreted and executed by the browser.

AOT

Ahead of time compilation on the other hand is not very different from JIT except for when the code is compiled. But wait, didn't we just say that the browser does not understand the binary if provided directly and needs only JS files? Then what are we actually compiling ahead of time here?

Keep Reading :)

JIT and AOT in angular2

If you are using angular2, the probability of you using TypeScript (TS) along with angular2 is very high. Since majority of the features of TS are still not supported by the browser, we might be relying on tools such as TypeScript compiler. These convert our code from TS to JS first, and then we serve these JS files to the browser (which again compiles the JS files to a binary which they understand).

So whats the problem again?

Essentially we are compiling the code twice with angular2 apps, once when we convert TS to JS and then when the browser converts JS to binary.

While we cannot control the latter, we can however control when the compilation from TS to JS is performed.

With angular2, if you go with JIT (which is default), both the compiles happen after the code is loaded in the browser (i.e. TS -> JS -> binary). Not only is it an additional overhead to do the TS -> JS compilation on the fly on the browser, but also, the angular2 compiler is almost half the size of the angular2 package so if we avoid this, we can reduce the size of the payload significantly.

Fun Stuff

Now lets create an angular2 app. If you have the cli installed, it's pretty straight forward- just go to the console and run

    ng new jitVaot

Once you do this, you can run your app in the browser at localhost:4200 by doing

    cd jitVaot
    ng serve

Now, to be able to compile ahead of time, we need to have the compiler and the platform-server installed in the app.

    npm install @angular/compiler-cli @angular/platform-server --save

Before we move on to using the compiler in angular2, we need to understand what the current process is for converting any TS to JS.

Once TypeScript is installed globally, we get access to the tsc command line tool and then we can run this tsc command within any project to convert TS to JS.

How does tsc know that your app is writted in TS and how do we pass the parameters needed to this command? By using the tsconfig.json file which is placed in the root of the project and contains a set of properties like the target, the module system to use, output directory etc. You can read additional details about tsconfig.json here.

For angular2 AOT we will rely on the ngc command line tool (angular compiler) which we just installed instead of the standard tsc (TypeScript compiler). We need to now make sure that the ngc tool is used instead of the tsc for the AOT builds. The ngc command, similar to the tsc command, needs to have a config file. So lets create a file at the root of the project and call it tsconfig-aot.ts and add the following contents

    {
      "compilerOptions": {
        "target": "es5",
        "module": "es2015",
        "moduleResolution": "node",
        "sourceMap": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "lib": ["es2015", "dom"],
        "noImplicitAny": true,
        "suppressImplicitAnyIndexErrors": true
      },

      "files": [
        "src/app/app.module.ts",
        "src/main.ts"
      ],

      "angularCompilerOptions": {
        "genDir": "aot",
        "skipMetadataEmit" : true
      }
    }

Its pretty similar to the tsconfig file but we are adding a couple of things specific to our ngc tool, which are:

  1. files array which specify the entry points for our app
  2. genDir which will be our output folder.
  3. skipMetadataEmit which prevents the compiler from generating metadata files with the compiled application.

Before we run these changes, we need to realize that we will no longer be relying on any module system to load the modules and the associated templates and stylesheets. For this, we need to modify the components to use moduleId: module.id and change the paths of the template and the stylesheets to be relative to the component. Your new app component should look as follows:

    import { Component } from '@angular/core';

    @Component({
        moduleId: module.id,
        selector: 'app-root',
        templateUrl: 'app.component.html',
        styleUrls: ['app.component.css']
    })
    export class AppComponent {
        title = 'app works!';
    }

To run these changes, all we need now is to recompile the app with the tsconfig-aot.json file.

    node_modules/.bin/ngc -p tsconfig-aot.json

Once you run this, you should be able to see a new folder aot. If you expand it you should see a bunch of .ng*. files such as .ngsummary, .ngmodule, .ngfactory, .ngstyle etc. DO NOT CHANGE THESE FILES AS THEY WILL BE OVERWRITTEN ON NEXT AOT COMPILE

Now that we have the aot files generated, one thing to understand here is that there is no longer a module loader or rather we want to avoid having a module loader altogether (i.e. we want to compress, minify and generate a single js file which we can hand over to the browser). But if you notice the main.ts file, it still relies on modules namely app.module which is later bootstrapped using platformBrowserDynamic().bootstrapModule(AppModule);


    import './polyfills.ts';

    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    import { enableProdMode } from '@angular/core';
    import { environment } from './environments/environment';
    import { AppModule } from './app/app.module';

    if (environment.production) {
      enableProdMode();
    }

    platformBrowserDynamic().bootstrapModule(AppModule);

Now our goal is to remove the dependency on the module. So, clone the main.ts file and name it main-aot.ts which we change to load our newly generated appModuleNgFactory under the aot module

    import './polyfills.ts';

    import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    import { enableProdMode } from '@angular/core';
    import { environment } from './environments/environment';
    import { AppModuleNgFactory } from '../aot/src/app/app.module.ngfactory';

    if (environment.production) {
      enableProdMode();
    }

    platformBrowserDynamic().bootstrapModuleFactory(AppModuleNgFactory);

Notice that we are using

import { AppModuleNgFactory } from '../aot/src/app/app.module.ngfactory';

and

platformBrowserDynamic().bootstrapModuleFactory(AppModuleNgFactory);

which is relying on the aot files rather than the AppModule. But with this, we need to make a minor change to tsconfig-aot.json: The entry point needs to be changed from src/main.ts to src/main-aot.ts and we are all set.

Now recompile your app so that it picks up the new main-aot.ts file instead of the previous main.ts file

    node_modules/.bin/ngc -p tsconfig-aot.json

Minification and Treeshaking

So now that we have the aot files generated, we are going to further enhance the capabilities of our app by minifying and treeshaking our app.

For minification we are going to rely on a tool called rollup.js which will be doing both minification and treeshaking for us. To install rollup run the following command:

    npm install rollup rollup-plugin-node-resolve rollup-plugin-commonjs rollup-plugin-uglify --save-dev

Once rollup.js is installed, we need to setup the config required for running it. So create a file called rollup-config.js and add the following contents

    import rollup      from 'rollup'
    import nodeResolve from 'rollup-plugin-node-resolve'
    import commonjs    from 'rollup-plugin-commonjs';
    import uglify      from 'rollup-plugin-uglify'

    export default {
        entry: 'src/main-aot.js',
        dest: 'aot-dist/build.js', 
        sourceMap: false,
        format: 'iife',
        onwarn: function(warning) {
            // Skip certain warnings

            // should intercept ... but doesn't in some rollup versions
            if ( warning.code === 'THIS_IS_UNDEFINED' ) { return; }
            // intercepts in some rollup versions
            if ( warning.indexOf("The 'this' keyword is equivalent to 'undefined'") > -1 ) { return; }

            // console.warn everything else
            console.warn( warning.message );
        },
        plugins: [
            nodeResolve({jsnext: true, module: true}),
            commonjs({
                include: 'node_modules/**/*'
            }),
            uglify()
        ]
    }

What we have is a pretty standard setup for rollup except that we are adding a few angular specific things here:

  1. The entry point is specified by the entry property.
  2. The destination is specified by the dest property. This is where our minified file will be generated.
  3. plugins at the end specify the plugins that we want to run after rollup is complete. Here, we are doing 2 additional things than usual: a. Invoking commonjs on all node_modules which will remove all the dependency on required statements and change them to ES2015 compliant import and export statements. b. Followed by uglify which will compress and mangle your application logic.

Now, to run the rollup command, in the root folder, run the following commands:

    node_modules/.bin/rollup -c rollup-config.js

Barring any errors, you should now be able to see the build.js file generated in the aot-dest folder. Now we just need to create an index.html file in this folder which would be a copy of your src/index.html file but with 2 main differences:

  1. Add the script file to the bottom of you html after the </body> tag. <script src="build.js"></script>

  2. Add <script>window.module = 'aot';</script> in the head tag. We need this because when declaring the components we will rely on relative positioning of the files with the help of moduleId which will be provided by the above script tag.

This is what your aot-dist/index.html file should look like

    <!doctype html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>JitVSaot</title>
        <base href="/">
        <script>window.module = 'aot';</script>

        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="icon" type="image/x-icon" href="favicon.ico">
    </head>
    <body>
    <app-root>Loading...</app-root>
    </body>

    <script src="build.js"></script>
    </html>

Now to test this aot app we need to have any web server. Going by the angular docs, just install the lite-server using the command npm install --save-dev lite-server and then create its configuration in a file called lite-config.json as follows:

{
  "server": {
    "baseDir": "aot-dist",
    "routes": {
      "/node_modules": "node_modules"
    }
  }
}

Now to run, go to terminal and run node_modules/.bin/lite-server -c=lite-config.json. You can also run the JIT version of the app as it is by running the ng serve command. This can be used during development and the aot-dist folder can be later regenerated and used for distributing your application.

We can further simplify this by combining all AOT commands into a single statement in your application's package.json file, under scripts add

"aot": "ngc -p tsconfig-aot.json && rollup -c rollup-config.js && lite-server -c=lite-config.json"

and run it using npm run aot

Conclusion

What we have seen here is an easier variant of the AOT compilation than that in the official angular docs

The code for the above example is posted here

Kashyap Mukkamala

I'm a Developer Advocate at Egen Solutions Inc, I fiddle around with JavaScript, Angular, Ionic & NativeScript during the day. Devoted Coder.