The code side of life

Best of both - Angular 2 AOT Compilation with Lazy JIT Compilation

Introduction

user

Shlomi Assaf


Angular2 Webpack AOT JIT

Best of both - Angular 2 AOT Compilation with Lazy JIT Compilation

Posted by Shlomi Assaf on .
Featured

Angular2 Webpack AOT JIT

Best of both - Angular 2 AOT Compilation with Lazy JIT Compilation

Posted by Shlomi Assaf on .

There's an old saying

You can't have the best of both worlds

That's true, especially for developers, trade-offs are an everyday struggle.

What should I choose, Ahead of Time or Just in Time compilation?

TL;DR

Choose Ahead of Time
But you can also combine BOTH, lets see how...

You can see a full working code example in this github repository.
Or you can head straight to the running demo, AOT compiled with JIT compiler :)

Compilation

Angular 2 is built around the compiler, you provide metadata and context the compiler outputs a JS module that acts as a unit of work.

This unit of work contains several parts, for simplicity lets call it View.

The View encapsulate all the angular 2 logic, it contains the code for creation, change detection, life cycle hooks, rendering, animation and more. Once we have our View compiled we don't need the metadata, templates and styles.

When?

We can control when we compile:
- When we bundle the code (AOT) ;or
- When the application bootstraps (JIT)

AOT has some significant benefit, one's that we just can't overlook.

Initial load / Bootstrap
With AOT we don't need to compile on the client. This has a huge impact especially on large project with big initial bundles it will shave 2-4 second off the bootstrap time.

Bundle Size
AOT means we don't need to ship the compiler in our bundle, think about what the compiler does, that's a lot of code!

So why would I JIT?

JIT compilation allows us to define new components on the fly, we can't do that when we compile AOT.
Large scale ERP's or CRM's usually requires that ability, graphic application and others.
In most cases there is way with AOT but sometimes it's just a feature you need.

OK, I want JIT!

Using JIT is straightforward but slow, what about using AOT and lazy load the compiler?

Let's demonstrate using a made-up use case:

We have an app, for simplicity it has only 1 bundle.
Out app is shipped using AOT Compilation.
Our boss wants the app to support runtime compilation but it needs to load fast, blazing fast!

The goal:
Use AOT to compile the common modules, use Lazy loading to load a feature module that ships with the JIT compiler.

The design:
1) Create A feature module that we load asynchronously.
2) Import the compiler module (@angular/compiler) in the feature module.
3) Create an Injector with the providers for the JITCompiler
4) Use the new injector to get the compiler.
5) Enjoy creating new components.

The Walkthrough:
Since we use AOT the compiler is not part of the app, theres no code with a reference to it. It is "shaken off" since it's not being used anywhere.

Once we import (and use) the providers (2) needed for the JITCompiler we create a hard reference to the compiler which means that now the compiler is part of the app, but we do it in an async module (1) so it will only be a part of it and not a part of out initial bundle.

The JITCompiler requires services injected to it, these are not defined in our DI tree and we need to manually provide them.
Luckily we have access to all of those provides contained in an array: COMPILER_PROVIDERS

Here's how it would look in a component, but you can move it to a service or as a factory...

import {  
  Component,
  Compiler,
  Injector,
  ReflectiveInjector
} from '@angular/core';

import { COMPILER_PROVIDERS }  from '@angular/compiler';

@Component({
  selector: 'my-lazy',
  templateUrl: './lazy.component.html'
})
export class LazyComponent {  
  private injector: Injector;
  private compiler: Compiler;

  constructor(injector: Injector) {
    this.injector = ReflectiveInjector.resolveAndCreate(COMPILER_PROVIDERS, injector);
    this.compiler = this.injector.get(Compiler);
  }
}

import { COMPILER_PROVIDERS } from '@angular/compiler' results in the compiler as part of the bundle, in this case a lazy loaded bundle. Since we use AOT the compiler will not be in the main bundle.

You can see a full working code example in this github repository.

Or you can head straight to the running demo, AOT compiled with JIT compiler :)

  • Open network tab on your dev-tools window.
  • Click on the Lazy menu.
  • See the compiler lazy load in the network tab
  • Click on the Add button to compile and instantiate a component on the fly.

Future?

Well, this POC shows it's possible, now what?

WebWorker

The compiler is in the heavy duty category, perfect for running in a WebWorker.
This requires some work, as it's not as straight forward as you might think.

WebWorkers run in a separate execution context, this means we can not share (not yet at least) objects stored in memory. All of the providers we used in our exapmle can not be used in a WebWorker, we will probably have to initialize a complete angular application in the WebWorker thread.

Once in place we can use a custom service that wraps the compiler and sends back serialized version of compiled components. Since the compiler outputs code it's quite easy to send a stringified version of that code and eval it in the main thread.

Server Compilation

Same concept as WebWorkers, send the server a request for compilation, get back a stringified version of the code.

Note that this has to go through security assessments, identify and handle security risks.

That's cool right, if you have any ideas notes or cool implementation drop a comment below!
user

Shlomi Assaf