【发布时间】:2018-01-23 23:59:27
【问题描述】:
我正在深入研究 Angular 4,并试图理解编译。我读过 AOT 和 JIT 都将 TypeScript 编译为 JavaScript,无论是服务器端还是客户端。如果我在使用 Webpack 和 grunt 构建它并部署那个缩小的 javascript 时编译它,那么 AOT 和 JIT 是如何出现的?
【问题讨论】:
标签: angular compilation
我正在深入研究 Angular 4,并试图理解编译。我读过 AOT 和 JIT 都将 TypeScript 编译为 JavaScript,无论是服务器端还是客户端。如果我在使用 Webpack 和 grunt 构建它并部署那个缩小的 javascript 时编译它,那么 AOT 和 JIT 是如何出现的?
【问题讨论】:
标签: angular compilation
在浏览器加载您的应用程序包后,Angular 编译器(打包在 vendor.bundle.js 中)执行来自 main.bundle.js 的模板的编译。这称为即时编译。该术语意味着编译发生在捆绑包到达浏览器的时间。
JIT 编译的缺点是:
加载包和呈现 UI 之间存在时间间隔。这段时间花在了 JiT 编译上。在小型应用程序中这个时间很少,但在大型应用程序中,JiT 编译可能需要几秒钟,因此用户需要等待更长时间才能看到您的应用程序。
Angular 编译器必须包含在 vendor.bundle.js 中,这会增加应用程序的大小。
不鼓励在 prod 中使用 JiT 编译,我们希望在创建包之前将模板预编译为 JavaScript。这就是 Ahead-of-Time (AoT) 编译的意义所在。
AoT 编译的优点是:
浏览器可以在您的应用程序加载后立即呈现 UI。无需等待代码编译。
ngc 编译器未包含在 vendor.bundle.js 中,因此您的应用程序的大小可能会更小。
如果您使用 Webpack,要执行 AoT,您需要调用 ngc 编译器。例如:
"build:aot": "ngc -p tsconfig.json && webpack --config webpack.config.js"
【讨论】:
我读过 AOT 和 JIT 都将 TypeScript 编译为 JavaScript 无论是服务器端还是客户端。
不,这不是 AOT 和 JIT 编译器所做的。 TypeScript 使用 typescript 编译器转译成 JavaScript。
有两个编译器负责编译和代码生成的繁重工作:
视图编译器编译组件模板和generates view factories。它解析模板内的表达式和 html 元素,并经过许多标准编译器阶段:
parse-tree (lexer) -> abstract-syntax-tree (parser) -> intermediate-code-tree -> output
提供者编译器编译模块提供者和generates module factories。
这两个编译器用于 JIT 和 AOT 编译。 JIT 和 AOT 编译在获取与组件或模块关联的元数据的方式上有所不同:
// the view compiler needs this data
@Component({
providers: ...
template: ...
})
// the provider compiler needs this data
@NgModule({
providers: ...
});
JIT 编译器使用运行时来获取数据。装饰器函数 @Component 和 @NgModule 被执行,they attach metadata 被 Angular 编译器使用反射能力(反射库)读取的组件或模块类。
AOT 编译器使用 typescript 编译器提供的静态代码分析来提取元数据,并且不依赖于代码评估。因此,与 JIT 编译器相比,它有点受限,因为它无法评估非显式代码 - 例如它需要导出一个函数:
// this module scoped function
function declarations() {
return [
SomeComponent
]
}
// should be exported
export function declarations() {
return [
SomeComponent
];
}
@NgModule({
declarations: declarations(),
})
export class SomeModule {}
同样,JIT 和 AOT 编译器大多是用于提取与组件或模块关联的元数据的包装器,它们都使用底层视图和提供程序编译器来生成工厂。
如果我在使用 Webpack 和 grunt 构建它时编译它 部署那个缩小的 javascript AOT 和 JIT 是如何进入的 图片?
Angular 提供了webpack plugin,它在构建期间从打字稿执行转译。该插件还可以对你的项目进行 AOT 编译,这样你就不会在 bundle 中包含 JIT 编译器,也不会在客户端执行编译。
【讨论】:
首先,Angular 正在远离 JIT 编译。我希望我们能在angular@5.x.x看到它
Angular 编译器通过使用装饰器来获取您编写的所有元数据,例如
@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
并对其进行分析。然后它获取模板和样式表并对其进行解析。编译器经历了许多我不会在这里描述的步骤。您可以查看描述编译过程的the following page。还有来自 Tobias Bosch 的 great talk。最后编译器创建 ngfactories 来实例化我们的应用程序。
我认为 JIT 中 AOT 的主要区别是
ngfactory 的格式是什么在每次页面加载时在浏览器的客户端运行。
它使用来自@angular/core 包的ReflectionCapabilities API 收集元数据。我们有以下选项可以在 JIT 模式下处理元数据:
1) 直接 API
例如我们可以像这样声明我们的组件
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'
}
}
我们可以在 ES5 中编写类似的代码。 JIT 编译器将读取 annotations 和 propMetadata 静态属性。 AOT 编译器无法使用它。
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'
}
}
上面的代码通常是由一些库生成的。 Angular 包也有相同的格式。这也不适用于aot。我们必须将 metadata.json 文件与我们的库一起提供以进行 AOT 编译。
3) 通过调用装饰器获取元数据
@Component({
selector: 'my-app',
templateUrl: `./app.component.html`
})
export class AppComponent {
@HostBinding('title') test = 'Some title';
}
Typescript 编译器将前面的代码转换为
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);
这段代码在 JIT 模式下执行,所以角度 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;
};
今天是doesn't use Reflect api anymore。编译器直接从__annotations__属性读取数据
if (typeOrFunc.hasOwnProperty(ANNOTATIONS)) {
return (typeOrFunc as any)[ANNOTATIONS];
}
JIT 编译器生成 javascript ngfactories
在构建时使用 ngc 在服务器端 (nodejs) 上运行。
使用 AOT,没有运行时编译步骤。当我们在浏览器中运行我们的应用程序时,我们已经预编译了ngfactories。它一开始就为我们提供了更好的性能和延迟加载。我们也不再在我们的生产包中提供@angular/compiler 代码。但是由于我们的ngfactories 代码,我们的捆绑包可以显着增长。
AOT 编译器使用typescript api 来分析打字稿代码。要获取元数据编译器,需要通过 StaticSymbolResolver 和 MetadataCollector API。
所以它需要app.component.ts 文件并创建打字稿对象模型。所以我们的AppComponent 类将呈现为NodeObject 类型为229 (ClassDeclaration)
我们可以看到这个对象有decorators 属性
以及由 Angular 团队编写并调用 tsc-wrapper does hard work 来提取此元数据的特殊 typescript 包装器。
当编译器meets d.ts file 尝试从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)];
}
}
最后 AOT 编译器使用TypeScriptEmitter 来生成打字稿 ngfactories(角度
另见
【讨论】: