I am diving into angular 4 and I am trying to understand the compilation. I've read that AOT and JIT both compile TypeScript to JavaScript whether that is server side or on the client side. If I am compiling it when I build it with Webpack and grunt and deploying that minified javascript how does AOT and JIT even come into the picture?
问题:
回答1:
I've read that AOT and JIT both compile TypeScript to JavaScript whether that is server side or on the client side.
No, that is not what AOT and JIT compilers do. TypeScript is transpiled into JavaScript using typescript compiler.
Angular compiler
There are two compilers that do the hard work of compilation and code generation:
- view compiler
- provider compiler
The view compiler compiles component templates and generates view factories. It parses expressions and html elements inside the template and goes through many of the standard compiler phases:
parse-tree (lexer) -> abstract-syntax-tree (parser) -> intermediate-code-tree -> output
The provider compiler compiles module providers and generates module factories.
JIT vs AOT
These two compilers are used in both JIT and AOT compilation. JIT and AOT compilations differ in how they get the metadata associated with the component or module:
// the view compiler needs this data
@Component({
providers: ...
template: ...
})
// the provider compiler needs this data
@NgModule({
providers: ...
});
JIT compiler uses runtime to get the data. The decorator functions @Component
and @NgModule
are executed and they attach metadata to the component or module class that is later read by Angular compilers using reflective capabiliteis (Reflect library).
AOT compiler uses static code analysis provided by typescript compiler to extract metadata and doesn't rely on code evaluation. Hence it's a bit limited when compared to JIT compiler since it can't evaluate in-explicit code - for example it requires exporting a function:
// this module scoped function
function declarations() {
return [
SomeComponent
]
}
// should be exported
export function declarations() {
return [
SomeComponent
];
}
@NgModule({
declarations: declarations(),
})
export class SomeModule {}
Again, both JIT and AOT compilers are mostly wrappers to extract metadata associated with a component or module and they both use the underlying view and provider compiler to generate factories.
If I am compiling it when I build it with Webpack and grunt and deploying that minified javascript how does AOT and JIT even come into the picture?
Angular provides webpack plugin that performs transpilation from typescript during build. This plugin can also AOT compile your project so that you don't include JIT compiler into the bundle and don't perform compilation on the client.
回答2:
First of all angular is moving away from JIT compilation. I hope we will see it in angular@5.x.x
Angular compiler takes all metadata you write by using decorators like
@Component({
selector: 'my-app',
template: '<h1>Hello</h1>'m
styles: [ ':host { display: block }' ]
})
constructor(
@Host() @Optional() private parent: Parent,
@Attribute('name') name: string) {}
@ViewChild('ref') ref;
@ContentChildren(MyDir) children: QueryList<MyDir>;
@HostBinding('title') title;
@HostListener('click') onClick() { ... }
// and so on
and analizes it. Then it takes template and stylesheets and parses it. Compiler goes through many steps that i won't describe here. You can take a look at the following page that describes the compilation process. There is also great talk from Tobias Bosch. Finally compiler creates ngfactories to instantiate our application.
I think main differences between AOT in JIT are
- when and where angular runs compilation
- how compiler collects metadata
- what's the format of
ngfactory
that compiler produces
JIT compiler
runs on client side in our browser on every page load.
It collects metadata by using ReflectionCapabilities API from @angular/core
package. We have the following options to work with metadata in JIT mode:
1) direct API
for example we can declare our component like
export class AppComponent {
static annotations = [
new Component({
selector: 'my-app',
templateUrl: `./app.component.html`,
styles: [ ':host { display: block }' ]
})
];
test: string;
static propMetadata = {
test: [new HostBinding('title')]
};
ngOnInit() {
this.test = 'Some title'
}
}
Similar code we can write in ES5. JIT compiler will read annotations
and propMetadata
static properties. AOT compiler won't work with it.
2) tsickle API
export class AppComponent {
static decorators = [{
type: Component,
args: [{
selector: 'my-app',
templateUrl: `./app.component.html`,
styles: [ ':host { display: block }' ]
},]
}];
test: string;
static propDecorators = {
'test': [{ type: HostBinding, args: ['title'] }]
};
ngOnInit() {
this.test = 'Some title'
}
}
The code above usually is generated by some library. Angular package also has the same format. This also won't work with aot. We have to ship metadata.json
file with our library for AOT compilation.
3) getting metadata created by invoking the decorators
@Component({
selector: 'my-app',
templateUrl: `./app.component.html`
})
export class AppComponent {
@HostBinding('title') test = 'Some title';
}
Typescript compiler transforms the preceding code to
var AppComponent = (function () {
function AppComponent() {
this.test = 'Some title';
}
return AppComponent;
}());
__decorate([
HostBinding('title')
], AppComponent.prototype, "test", void 0);
AppComponent = __decorate([
Component({
selector: 'my-app',
templateUrl: "./app.component.html"
})
], AppComponent);
this code is executed in JIT mode so angular calls Component decorator
const TypeDecorator: TypeDecorator = <TypeDecorator>function TypeDecorator(cls: Type<any>) {
// Use of Object.defineProperty is important since it creates non-enumerable property which
// prevents the property is copied during subclassing.
const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
(cls as any)[ANNOTATIONS] :
Object.defineProperty(cls, ANNOTATIONS, {value: []})[ANNOTATIONS];
annotations.push(annotationInstance);
return cls;
};
Today it doesn't use Reflect api anymore. Compiler reads data directly from __annotations__
property
if (typeOrFunc.hasOwnProperty(ANNOTATIONS)) {
return (typeOrFunc as any)[ANNOTATIONS];
}
JIT compiler produces javascript ngfactories
AOT compiler
runs on server side (nodejs) at build time by using ngc
.
With AOT, there is no runtime compile step. When we run our application in browser we have already precompiled ngfactories
. It gives us better performance at first and lazy load. We also don't ship @angular/compiler
code in our production bundle anymore. But our bundle can grow significantly because of our ngfactories
code.
AOT compiler uses typescript api to analize typescript code. To get metadata compiler goes through StaticSymbolResolver and MetadataCollector APIs.
So it takes app.component.ts
file and creates typescript object model. So our AppComponent
class will be presented like NodeObject
with type 229
(ClassDeclaration
)
as we can see this object has decorators
property
and special typescript wrapper written by angular team and called tsc-wrapper
does hard work to extract this metadata.
When compiler meets d.ts
file it is trying to get metadata from metadata.json
:
if (DTS.test(filePath)) {
var metadataPath = filePath.replace(DTS, '.metadata.json');
if (this.context.fileExists(metadataPath)) {
return this.readMetadata(metadataPath, filePath);
}
else {
// If there is a .d.ts file but no metadata file we need to produce a
// v3 metadata from the .d.ts file as v3 includes the exports we need
// to resolve symbols.
return [this.upgradeVersion1Metadata({ '__symbolic': 'module', 'version': 1, 'metadata': {} }, filePath)];
}
}
And finally AOT compiler uses TypeScriptEmitter to produce typescript ngfactories (angular < 4.4.0)
See also
- How aot works
回答3:
After the browser loads your app bundles, the Angular compiler (packaged inside vendor.bundle.js)performs the compilation of the templates from main.bundle.js. This is called Just-in-Time compilation. This term means that the compilation happens in time of the arrival of the bundles to the browser.
The drawbacks of the JIT compilation are:
There is a time gap between the loading bundles and rendering the UI. This time is spent on JiT compilation. On a small app this time is minimal, but in larger apps, the JiT compilation can take a couple of seconds, so the user needs to wait longer for just seeing your app.
The Angular compiler has to be included in the vendor.bundle.js, which adds to the size of your app.
Using the JiT compilation in the prod is discouraged, and we want the templates to be pre-compiled into JavaScript before the bundles are created. This is what Ahead-of-Time (AoT) compilation is about.
The advantages of the AoT compilation are:
The browser can render the UI as soon as you app is loaded. There is no need to wait for code compilation.
The ngc compiler is not included in the vendor.bundle.js and the resulting size of your app might be smaller.
If you're using Webpack, to do AoT you need to invoke the ngc compiler. For example:
"build:aot": "ngc -p tsconfig.json && webpack --config webpack.config.js"