Real World Angular - Part 8: Just Ahead of in Time

Angular compiler, JIT (Just in Time) & AOT (Ahead of Time) compilation, tree shaking and using Firebase hosting to serve our application.
This is Part 8 of our Real World Angular series. For other parts, see links at the bottom of this post or go to https://blog.realworldfullstack.io/. The app is still in development. Check it out @ https://bitwiser.io.
Recap
In Part 7 of our series, we saw how we could split our application into smaller modules and how we could optionally lazy load modules, if needed, thereby improving performance by shipping less code at start up time and shipping parts of the code only if and when needed.
In this part, we’ll see how angular compilation works and how we can our compile our application during our build (Ahead of Time), instead of run-time (Just In Time), further optimizing our deployed application. In addition, tree shaking our application will remove unused library code from our application build and further reduce the size of our shipped code.
Finally, we’ll talk about setting up our app for different environments (local, dev, qa, prod) and we’ll deploy our app to the firebase server, so we can see it in action outside our development machine.
The Angular compiler
An Angular application consist largely of components and their HTML templates. Before the browser can render the application, the components and templates must be converted to executable JavaScript by the Angular compiler.
The @NgModule metadata tells the Angular compiler what components to compile for this module and how to link this module with other modules.
The components and their associated HTML template markup, css styles, directives & pipes are compiled into component factories.
A component factory creates a pure, 100% JavaScript representation of the component that incorporates everything described in its @Component metadata: the HTML, the binding instructions, the attached styles.
See references below for more details on Angular compiler.
JIT vs AOT
So far, whenever we’ve used ng serve to serve our Angular application, the application has actually been compiled by the Angular compiler within the browser when the application first loads up. Everytime the page is reloaded the compiler recompiles the application in memory prior to the browser rendering our app. This process of run-time compilation in the browser is referred to as Just in Time compilation.
JIT compilation incurs a run-time 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. Compilation can uncover many component-template binding errors. JIT compilation discovers them at runtime which is later than we’d like.
AOT (Ahead of Time) compilation occurs during the build of the application. The shipped application contains the component factory classes and since the compilation has already happened, the Angular compilation need not be shipped as part of our deployed code.
Advantages of AOT (straight from the Angular docs) -
Faster rendering
With AOT, the browser downloads a pre-compiled version of the application. The browser loads executable code so it can render the application immediately, without waiting to compile the app first.
Fewer asynchronous requests
The compiler inlines external html templates and css style sheets within the application JavaScript, eliminating separate ajax requests for those source files.
Smaller Angular framework download size
There’s no need to download the Angular compiler if the app is already compiled. The compiler is roughly half of Angular itself, so omitting it dramatically reduces the application payload.
Detect template errors earlier
The AOT compiler detects and reports template binding errors during the build step before users can see them.
Better security
AOT compiles HTML templates and components into JavaScript files long before they are served to the client. With no templates to read and no risky client-side HTML or JavaScript evaluation, there are fewer opportunities for injection attacks.
AOT using cli
We’ll start where we left off. If you’re new to this series, you can get the code from the Part 7 branch of our repo.
In order to use AOT for our app, we can use the cli to serve our app pre-compiled by Angular -
ng serve --aot
It might’ve taken slightly longer to compile, but our application looks noticeably the same. Since our app is still small, I didn’t even notice any real performance difference. So how could one tell if anything was different?
Let’s take a quick look at Chrome Dev Tools -> Network tab and compare the sizes of our network requests when using “ng serve” and “ng serve - aot”.
Two things have happened here -
- The size of vendor.bundle.js has gone down from 4.6MB to 3.6 MB . That’s because the compiler code is no longer shipped with the Angular code.
- The size of main.bundle.js has gone up from 146 KB to 856 KB. That’s because the compiler has compiled our components into component factories and this code is much bigger than our uncompiled components and templates.
Note that sizes may vary for your bundles depending on the versions of various libraries and your code.
Even though the size of our application bundle has gone up and will continue to go up as we add more functionality, while the gain from the excluded compiler’s size is constant, the benefits of AOT are still much greater than just the bundle size gains. The Angular compiler is constantly improving with each new release of Angular and the tree shaking / minification process should further decrease our eventual bundle size.
ng build
Just looking at the bundle sizes is not a very scientific way of gauging what’s happening :). Let’s look under the hood as to what’s happening.
Instead of serving our app from in-memory using ng serve, let’s build our app -
ng build
The build command will create a dist folder for our app.
Now, let’s use the command to do an AOT build (and output to a different folder)-
ng build --aot --output-path dist.aot
Open up dist/main.bundle.js & dist.aot/main.bundle.js and review the code for one of our components, say DashboardComponent. You’ll see that the DashboardComponentNgFactory is only present in the aot build. You’ll also see the size difference as we saw earlier in our Chrome Dev Tools.
You could also compare vendor.bundle and see the difference in sizes, but comparing their code to see what’s included is a bit of a challenge.
source-map-explorer
Let’s further inspect this with some visual tools -
npm install source-map-explorer --save-dev
And then, let’s use the source-map-explorer to see the two generated vendor bundles (I’m using windows. Please change your paths and “\” as needed) -
node_modules\.bin\source-map-explorer dist/vendor.bundle.jsnode_modules\.bin\source-map-explorer dist.aot/vendor.bundle.js
You would see a big chunk of compiler code (mine is 24.4% of the vendor.bundle, by far the biggest individual component) is removed from the aot bundle.
You could also use this to inspect the main bundle and inspect the differences visually. You’ll notice the ngfactory components are usually the biggest ones.
Tree shaking
AOT compilation sets the stage for further optimization through a process called Tree Shaking. A Tree Shaker walks the dependency graph, top to bottom, and shakes out unused code and removes it from the final bundle.
Tree Shaking can greatly reduce the downloaded size of the application by removing unused portions of both source and library code.
The prod option of the cli produces a tree shaken and minified code
ng build --aot --prod --output-path dist.aot.prod
This will produce a tree shaken, minified(uglified) version our bundle.
If you need to inspect it using source-map-explorer, you would need to generate sourcemaps using the sourcemap option on the ng build command
Note that Tree Shaking and AOT compilation are separate steps.
A bug in webpack (used by the cli) prevents elimination of all unused classes from our final bundle. See https://github.com/webpack/webpack/issues/2899.
This should get resolved pretty soon. If you have an application that needs to go to production you can use the rollup library for tree shaking. The Angular docs provide information on rollup - https://angular.io/docs/ts/latest/cookbook/aot-compiler.html#!#tree-shaking.
Environment & configuration
Before we deploy our app to the server, let’s look at how we can configure different settings for different environment.
The cli provides us with a couple of environments by default. If you take a look at the .angular-cli.json file, you would see the environments property has dev and prod environment pointing to the environment.ts and environment.prod.ts files respectively. These files are located in the src/environments folder. Based on our ng command, the appropriate environment file is picked up when building our bundle. If environment is not provided, dev is assumed by default.
We can use these files for additional configuration settings needed (such as database connection info, api endpoints, etc) for each environment. For us, one such config setting is the firebase config, currently buried in the src/app/core/core.module.ts file. Let’s move this to our environment files.
We’ll first define an interface for our CONFIG. Add a file iconfig.ts to environments folder -
//iconfig.tsimport { FirebaseAppConfig } from 'angularfire2';export interface IConfig {
firebaseConfig: FirebaseAppConfig;
}
And let’s move our firebase configuration to the environment.ts files. For now, both our environments will have the same configuration (use your firebase db configuration below) -
//environment.ts import { IConfig } from './iconfig';...export const CONFIG: IConfig = {
"firebaseConfig" : {apiKey: "<api key>",
authDomain: "<auth domain>",
databaseURL: "<database url>",
storageBucket: "<storage bucket>",
messagingSenderId: "<messaging sender id>"
}
};
Modify core.module.ts to use this -
//core.module.tsimport { CONFIG } from '../../environments/environment';
...export const firebaseConfig: FirebaseAppConfig = CONFIG.firebaseConfig;...
Let’s test it out. Make sure it works with both environment (dev and prod)
ng serve --environment=prod
Deploy to server
Our application has enough functionality that we can deploy it to the server and play with it live on the server, rather than on the localhost.
We’ll use Firebase to host and serve our single page application. Read more @ https://firebase.google.com/docs/hosting/.
Install the Firebase CLI
We’ll start by installing the firebase cli in global mode -
npm install -g firebase-tools
Authenticate with your firebase credentials
firebase login
You’ll be prompted to login to your firebase account using your credentials.
Initialize our app with firebase hosting configuration
firebase init
The init command will prompt us for the Firebase features we want to use. For now, we’ll just configure the hosting feature.
It’ll then prompt us to select the firebase project you want your app to be associated with. Please select your appropriate project.
Select your public folder at the next prompt and single page application at the following. The firebase cli will create a firebase.json file along with .firebaserc file. Take a moment to inspect these files.
Let’s modify our firebase.json to reflect our dist folder -
//firebase.json
{
"hosting": {
"public": "dist",
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}}
We’ll build our app to the dist folder. (Feel free to try the AOT and prod options)
ng build --aot --output-path dist
And then serve up the firebase server locally
firebase serve
This will host your application locally from your dist folder using the firebase cli’s firebase server (default port 5000). Test out your application.
Once tested, let’s deploy it to the firebase cloud server -
firebase deploy
Test it out. You can test mine at https://rwa-trivia.firebaseapp.com/dashboard
Cool! We have a working app online. You can play around with it. From this point on, we’ll make sure the latest version of our app gets deployed to our dev site online.
Next Up
One of the big advantages of using Angular as our framework for front-end development is (automated) testing. Breaking up the application into smaller modules and then modules into components, templates and services allows us to test each of these components individually.
Even though tests are an essential element of a software project, I’ve dodged writing any tests until this point as I wanted to develop something functional and tangible, so we have something to test, rather than talk about it in an abstract manner.
Coming Up
Feel free to provide feedback on the comments section or reach me on twitter @anihalaney.
Real World Angular Series (quick links) :
Series summary and index: Part X
- 0: From zero to cli-ng, 1: Not another todo list!
- 2: It’s a Material World!, 3: Form Formation
- 4: State of my SPA, 5: Light my fire
- 6: 3Rs — Roles, Rules & Routes, 6.1: Upgrading to 4.0.0-rc.2
- 7: Split my lazy loaded code, 8: Just Ahead of in Time
- Parts 9, 9.1 and 9.2 : Unit Testing
- 11: Game Play, 12: Firebase Cloud functions
- Part 13: Elasticsearch
References
Angular Compiler
- https://angular.io/docs/ts/latest/cookbook/aot-compiler.html
- https://www.youtube.com/watch?v=kW9cJsvcsGo
- https://docs.google.com/document/d/195L4WaDSoI_kkW094LlShH6gT3B7K1GZpSBnnLkQR-g/preview#heading=h.jyatjfcqlaur
- https://blog.nrwl.io/essential-angular-2-compilation-cfbebf9bb6e4
Angular cli wiki
- https://github.com/angular/angular-cli/wiki
- http://blog.mgechev.com/2016/06/26/tree-shaking-angular2-production-build-rollup-javascript/
Firebase Hosting