Skip to content

Commit 27bc7dc

Browse files
alxhubmhevery
authored andcommitted
feat(ivy): ngtsc compiles @component, @directive, @NgModule (#24427)
This change supports compilation of components, directives, and modules within ngtsc. Support is not complete, but is enough to compile and test //packages/core/test/bundling/todo in full AOT mode. Code size benefits are not yet achieved as //packages/core itself does not get compiled, and some decorators (e.g. @input) are not stripped, leading to unwanted code being retained by the tree-shaker. This will be improved in future commits. PR Close #24427
1 parent 0f7e4fa commit 27bc7dc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+1882
-605
lines changed

modules/benchmarks/src/largetable/render3/table.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
*/
88

99
import {ɵC as C, ɵE as E, ɵRenderFlags as RenderFlags, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as detectChanges, ɵe as e, ɵsn as sn, ɵt as t, ɵv as v} from '@angular/core';
10-
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
10+
import {ComponentDefInternal} from '@angular/core/src/render3/interfaces/definition';
1111

1212
import {TableCell, buildTable, emptyTable} from '../util';
1313

1414
export class LargeTableComponent {
1515
data: TableCell[][] = emptyTable;
1616

1717
/** @nocollapse */
18-
static ngComponentDef: ComponentDef<LargeTableComponent> = defineComponent({
18+
static ngComponentDef: ComponentDefInternal<LargeTableComponent> = defineComponent({
1919
type: LargeTableComponent,
2020
selectors: [['largetable']],
2121
template: function(rf: RenderFlags, ctx: LargeTableComponent) {

modules/benchmarks/src/tree/render3/tree.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {ɵC as C, ɵE as E, ɵRenderFlags as RenderFlags, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵi1 as i1, ɵp as p, ɵsn as sn, ɵt as t, ɵv as v} from '@angular/core';
10-
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
10+
import {ComponentDefInternal} from '@angular/core/src/render3/interfaces/definition';
1111

1212
import {TreeNode, buildTree, emptyTree} from '../util';
1313

@@ -35,7 +35,7 @@ export class TreeComponent {
3535
data: TreeNode = emptyTree;
3636

3737
/** @nocollapse */
38-
static ngComponentDef: ComponentDef<TreeComponent> = defineComponent({
38+
static ngComponentDef: ComponentDefInternal<TreeComponent> = defineComponent({
3939
type: TreeComponent,
4040
selectors: [['tree']],
4141
template: function(rf: RenderFlags, ctx: TreeComponent) {
@@ -95,7 +95,7 @@ export class TreeFunction {
9595
data: TreeNode = emptyTree;
9696

9797
/** @nocollapse */
98-
static ngComponentDef: ComponentDef<TreeFunction> = defineComponent({
98+
static ngComponentDef: ComponentDefInternal<TreeFunction> = defineComponent({
9999
type: TreeFunction,
100100
selectors: [['tree']],
101101
template: function(rf: RenderFlags, ctx: TreeFunction) {

packages/compiler-cli/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ ts_library(
2525
tsconfig = ":tsconfig",
2626
deps = [
2727
"//packages/compiler",
28+
"//packages/compiler-cli/src/ngtsc/annotations",
2829
"//packages/compiler-cli/src/ngtsc/transform",
2930
],
3031
)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
load("//tools:defaults.bzl", "ts_library")
4+
5+
ts_library(
6+
name = "annotations",
7+
srcs = glob([
8+
"index.ts",
9+
"src/**/*.ts",
10+
]),
11+
module_name = "@angular/compiler-cli/src/ngtsc/annotations",
12+
deps = [
13+
"//packages/compiler",
14+
"//packages/compiler-cli/src/ngtsc/metadata",
15+
"//packages/compiler-cli/src/ngtsc/transform",
16+
],
17+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export {ComponentDecoratorHandler} from './src/component';
10+
export {DirectiveDecoratorHandler} from './src/directive';
11+
export {InjectableDecoratorHandler} from './src/injectable';
12+
export {NgModuleDecoratorHandler} from './src/ng_module';
13+
export {CompilationScope, SelectorScopeRegistry} from './src/selector_scope';
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {ConstantPool, Expression, R3ComponentMetadata, R3DirectiveMetadata, WrappedNodeExpr, compileComponentFromMetadata, makeBindingParser, parseTemplate} from '@angular/compiler';
10+
import * as ts from 'typescript';
11+
12+
import {Decorator, reflectNonStaticField, reflectObjectLiteral, staticallyResolve} from '../../metadata';
13+
import {AnalysisOutput, CompileResult, DecoratorHandler} from '../../transform';
14+
15+
import {extractDirectiveMetadata} from './directive';
16+
import {SelectorScopeRegistry} from './selector_scope';
17+
18+
const EMPTY_MAP = new Map<string, Expression>();
19+
20+
/**
21+
* `DecoratorHandler` which handles the `@Component` annotation.
22+
*/
23+
export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMetadata> {
24+
constructor(private checker: ts.TypeChecker, private scopeRegistry: SelectorScopeRegistry) {}
25+
26+
detect(decorators: Decorator[]): Decorator|undefined {
27+
return decorators.find(
28+
decorator => decorator.name === 'Component' && decorator.from === '@angular/core');
29+
}
30+
31+
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3ComponentMetadata> {
32+
const meta = decorator.args[0];
33+
if (!ts.isObjectLiteralExpression(meta)) {
34+
throw new Error(`Decorator argument must be literal.`);
35+
}
36+
37+
// @Component inherits @Directive, so begin by extracting the @Directive metadata and building
38+
// on it.
39+
const directiveMetadata = extractDirectiveMetadata(node, decorator, this.checker);
40+
if (directiveMetadata === undefined) {
41+
// `extractDirectiveMetadata` returns undefined when the @Directive has `jit: true`. In this
42+
// case, compilation of the decorator is skipped. Returning an empty object signifies
43+
// that no analysis was produced.
44+
return {};
45+
}
46+
47+
// Next, read the `@Component`-specific fields.
48+
const component = reflectObjectLiteral(meta);
49+
50+
// Resolve and parse the template.
51+
if (!component.has('template')) {
52+
throw new Error(`For now, components must directly have a template.`);
53+
}
54+
const templateExpr = component.get('template') !;
55+
const templateStr = staticallyResolve(templateExpr, this.checker);
56+
if (typeof templateStr !== 'string') {
57+
throw new Error(`Template must statically resolve to a string: ${node.name!.text}`);
58+
}
59+
60+
let preserveWhitespaces: boolean = false;
61+
if (component.has('preserveWhitespaces')) {
62+
const value = staticallyResolve(component.get('preserveWhitespaces') !, this.checker);
63+
if (typeof value !== 'boolean') {
64+
throw new Error(`preserveWhitespaces must resolve to a boolean if present`);
65+
}
66+
preserveWhitespaces = value;
67+
}
68+
69+
const template = parseTemplate(
70+
templateStr, `${node.getSourceFile().fileName}#${node.name!.text}/template.html`,
71+
{preserveWhitespaces});
72+
if (template.errors !== undefined) {
73+
throw new Error(
74+
`Errors parsing template: ${template.errors.map(e => e.toString()).join(', ')}`);
75+
}
76+
77+
// If the component has a selector, it should be registered with the `SelectorScopeRegistry` so
78+
// when this component appears in an `@NgModule` scope, its selector can be determined.
79+
if (directiveMetadata.selector !== null) {
80+
this.scopeRegistry.registerSelector(node, directiveMetadata.selector);
81+
}
82+
83+
return {
84+
analysis: {
85+
...directiveMetadata,
86+
template,
87+
viewQueries: [],
88+
89+
// These will be replaced during the compilation step, after all `NgModule`s have been
90+
// analyzed and the full compilation scope for the component can be realized.
91+
pipes: EMPTY_MAP,
92+
directives: EMPTY_MAP,
93+
}
94+
};
95+
}
96+
compile(node: ts.ClassDeclaration, analysis: R3ComponentMetadata): CompileResult {
97+
const pool = new ConstantPool();
98+
99+
// Check whether this component was registered with an NgModule. If so, it should be compiled
100+
// under that module's compilation scope.
101+
const scope = this.scopeRegistry.lookupCompilationScope(node);
102+
if (scope !== null) {
103+
// Replace the empty components and directives from the analyze() step with a fully expanded
104+
// scope. This is possible now because during compile() the whole compilation unit has been
105+
// fully analyzed.
106+
analysis = {...analysis, ...scope};
107+
}
108+
109+
const res = compileComponentFromMetadata(analysis, pool, makeBindingParser());
110+
return {
111+
field: 'ngComponentDef',
112+
initializer: res.expression,
113+
statements: pool.statements,
114+
type: res.type,
115+
};
116+
}
117+
}

0 commit comments

Comments
 (0)