AOT Compilation And Bundling In Angular

In the world of web development, developers nowadays prefer to develop any SPA (Single Page Application) using Angular. One of the main reasons behind this is the modular approach of Angular, with which we can separate the client-side scripting code just like MVC pattern. But, the most problematic issue is that adding all the JavaScript files into our index.html page is really very painful. Not only that, it decreases the performance of the application since, at the loading of the index.html, it tries to download all the JavaScript files including related HTML files. So, if we assume that our application contains more than 500 components, then we can clarify how many files need to download at the beginning.

It's not only the files related to the components which are developed for the application, index file also downloads the environmental files related to Angular from the Node Modules. So, in this way, it increases the application network traffic and decreases the performance. So, the most preferred solution is if we can bundle all the files into a single file just like other JavaScript-based application or MVC applications. So, to achieve this, we need to change the compilation process of our Angular applications.
 
JIT (Just In Time) Compilation

The traditional Angular applications (i.e. developed in Angular 1.x) are normally built in the JIT compilation. Maybe, the developers who are mainly developed using client-side language like JavaScript, jQuery do not know about it or believe that there is no compile-time requirement in case of script language. But, it is not the case. Actually, in the case of JavaScript, there is always a compiler which is compiling our code. This does not occur in the development or deployment time. Basically, it occurrs on the fly, that means just before our JavaScript code is going to get loaded by the browser. This process always takes a little bit more time we considered. This compilation process is very much required for the browsers because the browser does not read or understand any binary languages to execute or run. So, when we load any JavaScript files, those files first compile to the corresponding binary which can be interpreted by the browser and then executed by the browser. So, AOT compilation process actually switches the compilation process from runtime to build time.

Now, one confusion will be arising to us, which is that in the JIT section, we said that browsers do not understand the binary languages. Browser always needs the JS file to execute. So, the question is, what is compiled at the build time by AOT Compiler?

AOT (Ahead of Time) Compilation

AOT stands for Ahead of Time Compilation. AOT compilation is one the most important features introduced in the Angular 2 or above versions which make these frameworks ahead of JavaScript by comparison. The process mechanism is very different in the case of AOT compilation in respect of JIT Compilation. It was first introduced in Angular 2.0 and then only in the 2.3.0 version for ready to use. So, if anyone wants to use this compilation process, they need to use Angular 2.3.0 or above versions. Now, the main objective of AOT compilation is improving the performance of the application when it runs into the browsers. Actually, the Angular framework has a compiler library package known as @angular/compiler. If we compile the angular application with AOT mode, then it performs compilation of the templates with the help of the compiler and then generates the typescript files (file names like *ngfactory.ts, where * represents the actual name of the file, for example, if the component name is employee.ts then the related file will be employee.ngfactory.ts). Then compiler again compiles these typescript files to the JavaScript files.

In this compilation process, also a new type of file (*.metadata.json) has been created for every typescript files. These metadata files are required to compile the components for their templates. So, AOT Compiler validates the HTML templates at the build time and also raises an error if it finds any issues in our coding. The most common issues are,
  1. Properties and method which are used within the HTML template must be declared as public in the typescript class.
  2. In case of *ngIf directives, the expression must be the Boolean type.
  3. In case of event binding, method signature must be matched exactly with the typescript class method declaration.
After the concept of AOT Compilation, we also need to bundle all  of our .js files into one JavaScript files so that our application’s index page does not need to load all files. For bundling the compiled JavaScript files, we have different tools available. One of the most important tools is Webpack. Webpack has a few advantages over the other tools like,
  • Webpack can provide us a module loading concept just like Node.js can do.
  • The bundle file is easy to read and debug in the browser.
  • Webpack can be configured writing a config file code in ES2015.
  • Also, it provides a similar environment like development for the production deployment.
  • With the help of a webpack, we can install the Angular using NPM (Node Package Manager).
Application Configuration

For using the bundle concept, we need to develop an Angular based application containing modules, components etc. The module structure is as below,

 

In the above images, we can see that there exist two config files, namely webpack.config.js and webpack.prod-config.js. These two files are basically webpack configuration files which are required to build the application either in development mode or production mode. Before going to configuration of the file code, we first need to install the webpack using this npm command. 

  1. npm install angular webpack --save-dev  

Now, we can add two webpack config files. Basically, we can bundle our Angular application either using JIT Compilation or AOT compilation. The config file webpack.config.js is basically used for bundling the application with JIT Compilation. For bundling the application, in this mode, we need to use the below command from the applications root directory.

  1. npm run build  

Sample Code of webpack.config.js

  1. 'use strict'  
  2. const path = require('path');  
  3. const ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin');  
  4. const HtmlWebpackPlugin = require('html-webpack-plugin');  
  5. const StatsPlugin = require('stats-webpack-plugin');  
  6. const TsConfigPathsPlugin = require('awesome-typescript-loader').TsConfigPathsPlugin;  
  7. const webpack = require('webpack');  
  8.   
  9. const testHTML = path.resolve(__dirname, 'submodules''index.html');  
  10.   
  11. module.exports = {  
  12.   devtool: '',  
  13.   profile: true,  
  14.   
  15.   entry: {  
  16.     'shared/polyfills': [  
  17.       './submodules/app1/polyfills.ts',  
  18.       './submodules/app2/polyfills.ts',  
  19.       './submodules/app3/polyfills.ts'  
  20.     ],  
  21.     'app1/firstapp': ['./submodules/app1/index.ts'],  
  22.     'app2/secondapp': ['./submodules/app2/index.ts'],  
  23.     'app3/thirdapp': ['./submodules/app3/index.ts']  
  24.   },  
  25.   
  26.   output: {  
  27.     path: path.join(__dirname, 'dist''dev'),  
  28.     publicPath: '/',  
  29.     filename: '[name].js',  
  30.     chunkFilename: '[name].js'  
  31.   },  
  32.   
  33.   resolve: {  
  34.     extensions: ['.ts''.js''.json'],  
  35.     modules: [  
  36.       path.resolve(__dirname, 'node_modules'),  
  37.       path.resolve(__dirname, 'build-cache')  
  38.     ]  
  39.   },  
  40.   
  41.   module: {  
  42.     rules: [  
  43.       {  
  44.         test: /\.ts$/,  
  45.         use: [  
  46.           'awesome-typescript-loader',  
  47.           'angular2-template-loader'  
  48.         ],  
  49.         exclude: [/\.(spec|e2e)\.ts$/]  
  50.       },  
  51.       {  
  52.         test: /\.html$/,  
  53.         use: 'raw-loader'  
  54.       },  
  55.       {  
  56.         test: /\.css$/,  
  57.         use: [  
  58.           'to-string-loader',  
  59.           'css-loader'  
  60.         ]  
  61.       },  
  62.     ]  
  63.   },  
  64.   
  65.   plugins: [  
  66.     new StatsPlugin('stats.json''verbose'),  
  67.   
  68.     new webpack.optimize.OccurrenceOrderPlugin(),  
  69.   
  70.     new webpack.optimize.CommonsChunkPlugin({  
  71.       name: 'shared/polyfills',  
  72.       chunks: ['shared/polyfills']  
  73.     }),  
  74.     new webpack.optimize.CommonsChunkPlugin({  
  75.       name: 'shared/vendors',  
  76.       chunks: [  
  77.         'app1/firstapp',  
  78.         'app2/secondapp',  
  79.         'app3/thirdapp'  
  80.       ],  
  81.       // Move all node_modules into vendors chunk but not @angular  
  82.       minChunks: module => /node_modules\/(?!@angular)/.test(module.resource)  
  83.     }),  
  84.     new webpack.optimize.CommonsChunkPlugin({  
  85.       name: 'shared/angular-vendors',  
  86.       chunks: [  
  87.         'app1/firstapp',  
  88.         'app2/secondapp',  
  89.         'app3/thirdapp'  
  90.       ],  
  91.       // Move all node_modules into vendors chunk but not @angular  
  92.       minChunks: module => /node_modules\/@angular/.test(module.resource)  
  93.     }),  
  94.     new webpack.optimize.CommonsChunkPlugin({  
  95.       name: 'shared/shared-modules',  
  96.       chunks: [  
  97.         'app1/firstapp',  
  98.         'app2/secondapp',  
  99.         'app3/thirdapp'  
  100.       ],  
  101.       // Move duplicate modules to a shared-modules chunk  
  102.       minChunks: 2  
  103.     }),  
  104.     // Specify the correct order the scripts will be injected in  
  105.     new webpack.optimize.CommonsChunkPlugin({  
  106.       name: [  
  107.         'shared/polyfills',  
  108.         'shared/vendors',  
  109.         'shared/angular-vendors'  
  110.       ].reverse()  
  111.     }),  
  112.   
  113.     new HtmlWebpackPlugin({  
  114.       template: testHTML,  
  115.       inject: false  
  116.     })  
  117.   ],  
  118.   
  119.   node: {  
  120.     global: true,  
  121.     crypto: 'empty',  
  122.     process: true,  
  123.     module: false,  
  124.     clearImmediate: false,  
  125.     setImmediate: false  
  126.   },  
  127.   target: 'web'  
  128. }  

Another config file, i.e., webpack.prod-config.js, is required for bundling the applications with AOT Compilation. Since, we all know that every Angular application needs a module bootstrapper file, where we bootstrapped the Angular module. Normally, this file is named as main.ts or index.ts. In the above image, we can see that every Angular app module folders (like app1, app2 etc) contains one other file named index.aot.ts which is basically required to bootstrap the Angular module in the AOT compilation mode. In this file, we bootstrapped the Angular module ngfactory files which are created after the AOT compilations. For bundling applications, in this mode, we need to use the below command from the application's root directory,

  1. npm run build:prod  

Sample code of Index.aot.ts

  1. import { enableProdMode } from '@angular/core';  
  2. import { platformBrowser } from '@angular/platform-browser';  
  3. import { AppModuleNgFactory } from 'submodules/app1/app.module.ngfactory';  
  4.   
  5. try {  
  6.   enableProdMode();  
  7. catch (err) {  
  8.   /** 
  9.    * We do not care if the call to enableProdMode() failes 
  10.    * because we only need one of the calls to enableProdMode() 
  11.    * to succeed. 
  12.    */  
  13.   if (err.message.indexOf('Cannot enable prod mode after platform setup.') === -1)  
  14.     console.error(err);  
  15. }  
  16.   
  17. platformBrowser()  
  18.   .bootstrapModuleFactory(AppModuleNgFactory);   
Sample code of webpack.prod-config.ts
  1. 'use strict'  
  2. const path = require('path');  
  3. const ContextReplacementPlugin = require('webpack/lib/folderstReplacementPlugin');  
  4. const HtmlWebpackPlugin = require('html-webpack-plugin');  
  5. const StatsPlugin = require('stats-webpack-plugin');  
  6. const TsConfigPathsPlugin = require('awesome-typescript-loader').TsConfigPathsPlugin;  
  7. const webpack = require('webpack');  
  8.   
  9. const testHTML = path.resolve(__dirname, 'submodules''index.html');  
  10.   
  11. module.exports = {  
  12.   devtool: '',  
  13.   profile: true,  
  14.   
  15.   entry: {  
  16.     'shared/polyfills': [  
  17.       './submodules/app1/polyfills.ts',  
  18.       './submodules/app2/polyfills.ts',  
  19.       './submodules/app3/polyfills.ts'  
  20.     ],  
  21.     'app1/app': ['./submodules/app1/index.aot.ts'],  
  22.     'app2/app': ['./submodules/app2/index.aot.ts'],  
  23.     'app3/app': ['./submodules/app3/index.aot.ts']  
  24.   },  
  25.   
  26.   output: {  
  27.     path: path.join(__dirname, 'dist''prod'),  
  28.     publicPath: '/',  
  29.     filename: '[name].js',  
  30.     chunkFilename: '[name].js'  
  31.   },  
  32.   
  33.   resolve: {  
  34.     extensions: ['.ts''.js''.json'],  
  35.     modules: [  
  36.       path.resolve(__dirname, 'node_modules'),  
  37.       path.resolve(__dirname, 'build-cache'),  
  38.     ]  
  39.   },  
  40.   
  41.   module: {  
  42.     rules: [  
  43.       {  
  44.         test: /\.ts$/,  
  45.         use: [  
  46.           'awesome-typescript-loader',  
  47.           'angular2-template-loader'  
  48.         ],  
  49.         exclude: [/\.(spec|e2e)\.ts$/]  
  50.       },  
  51.       {  
  52.         test: /\.html$/,  
  53.         use: 'raw-loader'  
  54.       },  
  55.       {  
  56.         test: /\.css$/,  
  57.         use: [  
  58.           'to-string-loader',  
  59.           'css-loader'  
  60.         ]  
  61.       },  
  62.     ]  
  63.   },  
  64.   
  65.   plugins: [  
  66.     new StatsPlugin('stats.json''verbose'),  
  67.   
  68.     new webpack.optimize.OccurrenceOrderPlugin(),  
  69.     new webpack.optimize.UglifyJsPlugin({  
  70.       beautify: false,  
  71.       output: {  
  72.         comments: false  
  73.       },  
  74.       mangle: {  
  75.         screw_ie8: true,  
  76.         except: ['$']  
  77.       },  
  78.       compress: {  
  79.         screw_ie8: true,  
  80.         warnings: false,  
  81.         collapse_vars: true,  
  82.         reduce_vars: true,  
  83.         conditionals: true,  
  84.         unused: true,  
  85.         comparisons: true,  
  86.         sequences: true,  
  87.         dead_code: true,  
  88.         evaluate: true,  
  89.         if_return: true,  
  90.         join_vars: true,  
  91.         drop_console: false,  
  92.         drop_debugger: true  
  93.       }  
  94.     }),  
  95.   
  96.     new webpack.optimize.CommonsChunkPlugin({  
  97.       name: 'shared/polyfills',  
  98.       chunks: ['shared/polyfills']  
  99.     }),  
  100.     new webpack.optimize.CommonsChunkPlugin({  
  101.       name: 'shared/vendors',  
  102.       chunks: [  
  103.         'app1/app',  
  104.         'app2/app',  
  105.         'app3/app'  
  106.       ],  
  107.       // Move all node_modules into vendors chunk but not @angular  
  108.       minChunks: module => /node_modules\/(?!@angular)/.test(module.resource)  
  109.     }),  
  110.     new webpack.optimize.CommonsChunkPlugin({  
  111.       name: 'shared/angular-vendors',  
  112.       chunks: [  
  113.         'app1/app',  
  114.         'app2/app',  
  115.         'app3/app'  
  116.       ],  
  117.       // Move all node_modules into vendors chunk but not @angular  
  118.       minChunks: module => /node_modules\/@angular/.test(module.resource)  
  119.     }),  
  120.     new webpack.optimize.CommonsChunkPlugin({  
  121.       name: 'shared/shared-modules',  
  122.       chunks: [  
  123.         'app1/app',  
  124.         'app2/app',  
  125.         'app3/app'  
  126.       ],  
  127.       // Move duplicate modules to a shared-modules chunk  
  128.       minChunks: 2  
  129.     }),  
  130.     // Specify the correct order the scripts will be injected in  
  131.     new webpack.optimize.CommonsChunkPlugin({  
  132.       name: [  
  133.         'shared/polyfills',  
  134.         'shared/vendors',  
  135.         'shared/angular-vendors'  
  136.       ].reverse()  
  137.     }),  
  138.   
  139.     new HtmlWebpackPlugin({  
  140.       template: testHTML,  
  141.       inject: false  
  142.     })  
  143.   ],  
  144.   
  145.   node: {  
  146.     global: true,  
  147.     crypto: 'empty',  
  148.     process: true,  
  149.     module: false,  
  150.     clearImmediate: false,  
  151.     setImmediate: false  
  152.   },  
  153.   target: 'web'  
  154. }  
After running any one command of the above, two new folders, namely build-cache and dist, have been created in the root application folder directory, as in the below images.

 

The dist folder is the output folder where final files have been stored after compilation. If we use JIT compilation, then the output files will be stored under the dev folder, whereas the same will stored under prod folder for the AOT compilations. After it, we just need to deploy the contents of these folders (anyone either dev or prod) in the server as an application. Also, note that this compiled folder does not contain any node_modules folder because it already bundled the related content of the required node modules package files within the shared-modules.js and polyfill.js files.

 
You can download the source code from the link - Angular AOT Bundle.