Bundling an Angular 2/4 app with AOT Compilation

Table of contents
Reading Time: 4 minutes

Bundling an Angular 2/4 app with AOT Compilation:

Prerequisite :

  1. Good knolwledge of Angular 2/4
  2. Basic knowledge of gulp.

Step 1 :

Ahead-of-Time Compilation or AOT Compilation:
Angular application consists of components and their html templates. To run them on browser, they are compiled to executable javascript by the angular compiler. One option is to compile them on browser at runtime using just-in-time(JIT) compiler. It’s good for development environment, but it has shortcomings.

JIT compilation incurs a runtime performance penalty. Views take longer to render because of the in-browser compilation step. The application is bigger because it includes the Angular compiler and a lot of library code that the application won’t actually need. Bigger apps take longer to transmit and are slower to load.

Also, while compiling at runtime on browser, all the template binding errors will occur on browser only.

Coming to the other and better option, AOT compilation.
AOT improves performance by compiling at build time and hence find template errors early as well.

Here are some advantages of AOT compilation

  • Faster rendering
  • Fewer asynchronous requests
  • Smaller Angular framework download size
  • Detect template errors earlier
  • Better security

How to compile the app with ngc compiler :

We will use ngc compiler provided in the @angular/compiler-cli. To install it, run :

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

ngc requires its own tsconfig.json with AOT-oriented settings. We add tsconfig-aot.json with the following code :



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es2015", "dom"],
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"outDir" : "dist/"
},
"files": [
"app/app.module.ts",
"app/boot-aot.ts"
],
"angularCompilerOptions": {
"genDir": "dist/aot",
"skipMetadataEmit" : true
}
}

angularCompilerOptions :
“genDir” : specifies the directory for compiled code
“skipMetadataEmit” : true : property prevents the compiler from generating metadata files

To compile your code, run :

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

What it does, is first creare ngFactory.ts files in dist/aot(specified in genDir) , then again transpiles it again to ngFactory.js using compilerOptions.

After compilation, you can see NgFactory files in the folder specified in genDir. Each component factory creates an instance of the component at runtime by combining the original class file and a JavaScript representation of the component’s template.

Note : JIT compilation also generates these same NgFactories in memory where they are largely invisible.

Now, instead of bootstrapping AppModule, we will bootstrapp the AppModuleNgFactory. Add boot-aot.ts, with the following code :



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


/// <reference path="../node_modules/@types/node/index.d.ts" />
import { platformBrowser } from '@angular/platform-browser';
import { AppModuleNgFactory } from '../dist/aot/app/app.module.ngfactory';
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);


Note : In tsconfig.json, add boot-aot.ts to "exclude", so that it won't compile while you run your typescript compiler. Otherwise it will show error that, AppModuleNgFactory not found.

Step 2 :

Tree Shaking

A tree shaker walks the dependency graph, top to bottom, and shakes out unused code like dead leaves in a tree.

We are going to use a tree shaking utility called Rollup.

Rollup statically analyzes the application by following the trail of import and export statements. It produces a final code bundle that excludes code that is exported, but never imported. Rollup works on only ES2015, and that’s the reason we specified module as ES2015 in our tsconfig-aot.json.

Add the following dependencies :

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

Create a rollup-config.js and add the following code :



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


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: './dist/app/boot-aot.js',
dest: './dist/temp/prod/app/bundle.min.js', // output a single application bundle
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, main: true
}),
commonjs({
include: 'node_modules/**/**',
namedExports: {
'node_modules/angular2-cookie/core.js': [ 'CookieOptions' ]
}
}),
uglify()
]
}

entry: the App entry point.
dest: this attribute tells Rollup to create a bundle called build.js in the dist folder.
format: Rollup supports several output formats. Since we’re running in the browser, we want to use an immediately-invoked function expression (IIFE).

For rollup, Run :

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

Step 3 :

Remove moduleId : module.id :

In Angular, for each component we add some metadata to it, one of that is moduleId. By specifying moduleId, we tell the component that its templates and styles are in the current folder. Since we compiled the bundle with aot, all the templates and css gets inlined to the js files, so we need to remove the moduleId property.

Add a new task in gulpfile, as follows :



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


var gulp = require('gulp'); //Add dependency to package.json if not added
var replace = require('gulp-replace'); //Add dependency to package.json if not added
gulp.task('tweak-minJs', function () {
gulp.src(['dist/temp/prod/app/bundle.min.js'])
.pipe(replace('moduleId:module.id,', ''))
.pipe(gulp.dest('dist/prod/src/app'));
});
view raw

gulp_moduleId

hosted with ❤ by GitHub

Run :

gulp tweak-minJs

Note : To install any gulp dependency, run the following command :
$ npm install --save-dev

Step 5 :

Vendor all assets :

Next we need to get assets from the development folder and put them corresponding to your production bundle. We will again use gulp for this.

Vendor css files
Add the following code in gulpfile.js



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


var uglifycss = require('gulp-uglifycss'); //Add dependency to package.json if not added
gulp.task('vendor-css', function () {
gulp.src(['./assets/css/*.css', './assets/css/**/*.css']) //All css files
.pipe(uglifycss({
'maxLineLen': 80,
'uglyComments': true
}))
.pipe(concat('vendor.css'))
.pipe(gulp.dest('dist/prod/src/assets/css/'));
});
view raw

gulp_vendor_css

hosted with ❤ by GitHub

Vendor JS files

To concatenate, all javascripts files to a single javascript file, add the following code to your gulpfile.js



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


var concat = require('gulp-concat'); //Add dependency to package.json if not added
gulp.task('vendor-js', function () {
gulp.src(["assets/js/sweetalert2.min.js","assets/js/jquery-ui.min.js'" … ]) // Add all js files path
.pipe(concat('vendor.js')) //Concatenate to a single js file
.pipe(gulp.dest('dist/prod/src/assets/js/')); // destination folder
});
view raw

gulp_vendor_js

hosted with ❤ by GitHub

If you just want them to be copied, and not get concatenated, you can add the following code :



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


gulp.task('vendor-top-js', function () {
gulp.src(["assets/js/sweetalert2.min.js","assets/js/jquery-ui.min.js", … ]) // Add all js files path
.pipe(gulp.dest('dist/prod/src/assets/js/')); // destination folder
});
view raw

vendor_all

hosted with ❤ by GitHub

Similarly, we copy fonts and other assets that we need.


Step 5 :

Minify Images :

Next we will minify all our images used in the website. Here’s the gulp code :



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


var plumber = require('gulp-plumber'); //Add dependency to package.json if not added
var imagemin = require('gulp-imagemin'); //Add dependency to package.json if not added
gulp.task('minify-images', function () {
return gulp.src(['assets/images/**/*.*'])
.pipe(plumber({
errorHandler: function (err) {
console.log(err);
this.emit('end');
}
}))
.pipe(imagemin({
progressive: true
}))
.pipe(gulp.dest('dist/prod/src/assets/images/'));
});
view raw

minify

hosted with ❤ by GitHub

Step 6 :

Add index.html

Change your index.html name to index-jit.html, now this index-jit.html we will use for development. And add a new index.html file with same content. This index.html we will use in production mode.

Update your index.html as follows :
Remove this code :



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script src="assets/js/modernizr.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
view raw

scripts

hosted with ❤ by GitHub

And Add :



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


<script id="bundle"></script>
<script type="text/javascript">
var randomString = Math.random();
document.getElementById("bundle").src = "app/bundle.min.js?v="+ randomString.toString();
</script>
view raw

script2

hosted with ❤ by GitHub

Since we have created the precompiled bundle that is ready to for browser, all we need it to include that, we no longer need systemjs.config.js to load our application. You can either just add a script tag to add your js bundle, or you can follow the above code to add your bundle which disable caching of your bundle on browser. Disabling cache is only required if you constantly change your Application.

Next we need to copy our index.html to our production folder, and add our vendor js and vendor css files to it.

Add the following gulp code :



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


var replace = require('gulp-replace'); //Add dependency to package.json if not added
gulp.task('index-build', function () {
gulp.src('index.html')
.pipe(inject(gulp.src('dist/prod/src/assets/css/vendor.css'), {name: 'styles'}))
.pipe(inject(gulp.src('dist/prod/src/assets/js/vendor.js'), {name: 'scripts'}))
.pipe(replace('/dist/prod/src/assets/css/vendor.css', 'assets/css/vendor.css'))
.pipe(replace('/dist/prod/src/assets/js/vendor.js', 'assets/js/vendor.js'))
.pipe(gulp.dest('dist/prod/src'));
});
view raw

gulp_index

hosted with ❤ by GitHub


Step 7 :

Copy remaining assets



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


gulp.task('copy-rootfiles-prod', function () {
return gulp.src([
"/assets/remaining1", "/assets/remaining2" // copy remaining assets
]).pipe(gulp.dest('dist/prod/src/'));
});
view raw

gulp_copy_rest

hosted with ❤ by GitHub

Step 9 :

GZIP :
The 9th and final step is to compress your bundle and vendor js and vendor css. We will use gulp-gzip dependency for this.



This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters


var gzip = require('gulp-gzip'); //Add dependency to package.json if not added
gulp.task('gzip-app', function () {
gulp.src(['dist/prod/src/app/bundle.min.js'])
.pipe(gzip())
.pipe(gulp.dest('dist/prod/src/app'));
});
gulp.task('gzip-css', function () {
gulp.src(['dist/prod/src/assets/css/vendor.css'])
.pipe(gzip())
.pipe(gulp.dest('dist/prod/src/assets/css'));
});
gulp.task('gzip-vendorjs', function () {
gulp.src(['dist/prod/src/assets/js/*.*'])
.pipe(gzip())
.pipe(gulp.dest('dist/prod/src/assets/js'));
});
view raw

gulp_gzip

hosted with ❤ by GitHub

Reference : https://angular.io/guide/aot-compiler

Written by 

Principal Architect at Knoldus Inc

1 thought on “Bundling an Angular 2/4 app with AOT Compilation5 min read

Comments are closed.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading