feat(ivy): able to compile @angular/core with ngtsc (#24677)

@angular/core is unique in that it defines the Angular decorators
(@Component, @Directive, etc). Ordinarily ngtsc looks for imports
from @angular/core in order to identify these decorators. Clearly
within core itself, this strategy doesn't work.

Instead, a special constant ITS_JUST_ANGULAR is declared within a
known file in @angular/core. If ngtsc sees this constant it knows
core is being compiled and can ignore the imports when evaluating
decorators.

Additionally, when compiling decorators ngtsc will often write an
import to @angular/core for needed symbols. However @angular/core
cannot import itself. This change creates a module within core to
export all the symbols needed to compile it and adds intelligence
within ngtsc to write relative imports to that module, instead of
absolute imports to @angular/core.

PR Close #24677
This commit is contained in:
Alex Rickabaugh
2018-06-20 15:54:16 -07:00
committed by Miško Hevery
parent c57b491778
commit 104d30507a
14 changed files with 208 additions and 49 deletions

View File

@ -25,10 +25,11 @@ const EMPTY_MAP = new Map<string, Expression>();
export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMetadata> {
constructor(
private checker: ts.TypeChecker, private reflector: ReflectionHost,
private scopeRegistry: SelectorScopeRegistry) {}
private scopeRegistry: SelectorScopeRegistry, private isCore: boolean) {}
detect(decorators: Decorator[]): Decorator|undefined {
return decorators.find(decorator => decorator.name === 'Component' && isAngularCore(decorator));
return decorators.find(
decorator => decorator.name === 'Component' && (this.isCore || isAngularCore(decorator)));
}
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3ComponentMetadata> {
@ -43,7 +44,7 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
// @Component inherits @Directive, so begin by extracting the @Directive metadata and building
// on it.
const directiveMetadata =
extractDirectiveMetadata(node, decorator, this.checker, this.reflector);
extractDirectiveMetadata(node, decorator, this.checker, this.reflector, this.isCore);
if (directiveMetadata === undefined) {
// `extractDirectiveMetadata` returns undefined when the @Directive has `jit: true`. In this
// case, compilation of the decorator is skipped. Returning an empty object signifies

View File

@ -22,14 +22,16 @@ const EMPTY_OBJECT: {[key: string]: string} = {};
export class DirectiveDecoratorHandler implements DecoratorHandler<R3DirectiveMetadata> {
constructor(
private checker: ts.TypeChecker, private reflector: ReflectionHost,
private scopeRegistry: SelectorScopeRegistry) {}
private scopeRegistry: SelectorScopeRegistry, private isCore: boolean) {}
detect(decorators: Decorator[]): Decorator|undefined {
return decorators.find(decorator => decorator.name === 'Directive' && isAngularCore(decorator));
return decorators.find(
decorator => decorator.name === 'Directive' && (this.isCore || isAngularCore(decorator)));
}
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3DirectiveMetadata> {
const analysis = extractDirectiveMetadata(node, decorator, this.checker, this.reflector);
const analysis =
extractDirectiveMetadata(node, decorator, this.checker, this.reflector, this.isCore);
// If the directive has a selector, it should be registered with the `SelectorScopeRegistry` so
// when this directive appears in an `@NgModule` scope, its selector can be determined.
@ -57,7 +59,7 @@ export class DirectiveDecoratorHandler implements DecoratorHandler<R3DirectiveMe
*/
export function extractDirectiveMetadata(
clazz: ts.ClassDeclaration, decorator: Decorator, checker: ts.TypeChecker,
reflector: ReflectionHost): R3DirectiveMetadata|undefined {
reflector: ReflectionHost, isCore: boolean): R3DirectiveMetadata|undefined {
if (decorator.args === null || decorator.args.length !== 1) {
throw new Error(`Incorrect number of arguments to @${decorator.name} decorator`);
}
@ -108,7 +110,7 @@ export function extractDirectiveMetadata(
return {
name: clazz.name !.text,
deps: getConstructorDependencies(clazz, reflector),
deps: getConstructorDependencies(clazz, reflector, isCore),
host: {
attributes: {},
listeners: {},

View File

@ -20,15 +20,16 @@ import {getConstructorDependencies, isAngularCore} from './util';
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
*/
export class InjectableDecoratorHandler implements DecoratorHandler<R3InjectableMetadata> {
constructor(private reflector: ReflectionHost) {}
constructor(private reflector: ReflectionHost, private isCore: boolean) {}
detect(decorator: Decorator[]): Decorator|undefined {
return decorator.find(decorator => decorator.name === 'Injectable' && isAngularCore(decorator));
return decorator.find(
decorator => decorator.name === 'Injectable' && (this.isCore || isAngularCore(decorator)));
}
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<R3InjectableMetadata> {
return {
analysis: extractInjectableMetadata(node, decorator, this.reflector),
analysis: extractInjectableMetadata(node, decorator, this.reflector, this.isCore),
};
}
@ -48,8 +49,8 @@ export class InjectableDecoratorHandler implements DecoratorHandler<R3Injectable
* metadata needed to run `compileIvyInjectable`.
*/
function extractInjectableMetadata(
clazz: ts.ClassDeclaration, decorator: Decorator,
reflector: ReflectionHost): R3InjectableMetadata {
clazz: ts.ClassDeclaration, decorator: Decorator, reflector: ReflectionHost,
isCore: boolean): R3InjectableMetadata {
if (clazz.name === undefined) {
throw new Error(`@Injectables must have names`);
}
@ -63,7 +64,7 @@ function extractInjectableMetadata(
name,
type,
providedIn: new LiteralExpr(null),
deps: getConstructorDependencies(clazz, reflector),
deps: getConstructorDependencies(clazz, reflector, isCore),
};
} else if (decorator.args.length === 1) {
const metaNode = decorator.args[0];
@ -102,7 +103,7 @@ function extractInjectableMetadata(
}
return {name, type, providedIn, useFactory: factory, deps};
} else {
const deps = getConstructorDependencies(clazz, reflector);
const deps = getConstructorDependencies(clazz, reflector, isCore);
return {name, type, providedIn, deps};
}
} else {

View File

@ -29,10 +29,11 @@ export interface NgModuleAnalysis {
export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalysis> {
constructor(
private checker: ts.TypeChecker, private reflector: ReflectionHost,
private scopeRegistry: SelectorScopeRegistry) {}
private scopeRegistry: SelectorScopeRegistry, private isCore: boolean) {}
detect(decorators: Decorator[]): Decorator|undefined {
return decorators.find(decorator => decorator.name === 'NgModule' && isAngularCore(decorator));
return decorators.find(
decorator => decorator.name === 'NgModule' && (this.isCore || isAngularCore(decorator)));
}
analyze(node: ts.ClassDeclaration, decorator: Decorator): AnalysisOutput<NgModuleAnalysis> {
@ -89,7 +90,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler<NgModuleAnalys
const ngInjectorDef: R3InjectorMetadata = {
name: node.name !.text,
type: new WrappedNodeExpr(node.name !),
deps: getConstructorDependencies(node, this.reflector), providers,
deps: getConstructorDependencies(node, this.reflector, this.isCore), providers,
imports: new LiteralArrayExpr(
[...imports, ...exports].map(imp => referenceToExpression(imp, context))),
};

View File

@ -13,14 +13,15 @@ import {Decorator, ReflectionHost} from '../../host';
import {Reference} from '../../metadata';
export function getConstructorDependencies(
clazz: ts.ClassDeclaration, reflector: ReflectionHost): R3DependencyMetadata[] {
clazz: ts.ClassDeclaration, reflector: ReflectionHost,
isCore: boolean): R3DependencyMetadata[] {
const useType: R3DependencyMetadata[] = [];
const ctorParams = reflector.getConstructorParameters(clazz) || [];
ctorParams.forEach((param, idx) => {
let tokenExpr = param.type;
let optional = false, self = false, skipSelf = false, host = false;
let resolved = R3ResolvedDependencyType.Token;
(param.decorators || []).filter(isAngularCore).forEach(dec => {
(param.decorators || []).filter(dec => isCore || isAngularCore(dec)).forEach(dec => {
if (dec.name === 'Inject') {
if (dec.args === null || dec.args.length !== 1) {
throw new Error(`Unexpected number of arguments to @Inject().`);