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

@ -91,17 +91,21 @@ export class NgtscProgram implements api.Program {
const mergeEmitResultsCallback = opts && opts.mergeEmitResultsCallback || mergeEmitResults;
const checker = this.tsProgram.getTypeChecker();
const isCore = isAngularCorePackage(this.tsProgram);
const reflector = new TypeScriptReflectionHost(checker);
const scopeRegistry = new SelectorScopeRegistry(checker, reflector);
// Set up the IvyCompilation, which manages state for the Ivy transformer.
const handlers = [
new ComponentDecoratorHandler(checker, reflector, scopeRegistry),
new DirectiveDecoratorHandler(checker, reflector, scopeRegistry),
new InjectableDecoratorHandler(reflector),
new NgModuleDecoratorHandler(checker, reflector, scopeRegistry),
new ComponentDecoratorHandler(checker, reflector, scopeRegistry, isCore),
new DirectiveDecoratorHandler(checker, reflector, scopeRegistry, isCore),
new InjectableDecoratorHandler(reflector, isCore),
new NgModuleDecoratorHandler(checker, reflector, scopeRegistry, isCore),
];
const compilation = new IvyCompilation(handlers, checker, reflector);
const coreImportsFrom = isCore && getR3SymbolsFile(this.tsProgram) || null;
const compilation = new IvyCompilation(handlers, checker, reflector, coreImportsFrom);
// Analyze every source file in the program.
this.tsProgram.getSourceFiles()
@ -115,7 +119,7 @@ export class NgtscProgram implements api.Program {
sourceFiles: ReadonlyArray<ts.SourceFile>) => {
if (fileName.endsWith('.d.ts')) {
data = sourceFiles.reduce(
(data, sf) => compilation.transformedDtsFor(sf.fileName, data), data);
(data, sf) => compilation.transformedDtsFor(sf.fileName, data, fileName), data);
}
this.host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
};
@ -128,7 +132,7 @@ export class NgtscProgram implements api.Program {
options: this.options,
emitOnlyDtsFiles: false, writeFile,
customTransformers: {
before: [ivyTransformFactory(compilation)],
before: [ivyTransformFactory(compilation, coreImportsFrom)],
},
});
return emitResult;
@ -152,3 +156,47 @@ function mergeEmitResults(emitResults: ts.EmitResult[]): ts.EmitResult {
}
return {diagnostics, emitSkipped, emittedFiles};
}
/**
* Find the 'r3_symbols.ts' file in the given `Program`, or return `null` if it wasn't there.
*/
function getR3SymbolsFile(program: ts.Program): ts.SourceFile|null {
return program.getSourceFiles().find(file => file.fileName.indexOf('r3_symbols.ts') >= 0) || null;
}
/**
* Determine if the given `Program` is @angular/core.
*/
function isAngularCorePackage(program: ts.Program): boolean {
// Look for its_just_angular.ts somewhere in the program.
const r3Symbols = getR3SymbolsFile(program);
if (r3Symbols === null) {
return false;
}
// Look for the constant ITS_JUST_ANGULAR in that file.
return r3Symbols.statements.some(stmt => {
// The statement must be a variable declaration statement.
if (!ts.isVariableStatement(stmt)) {
return false;
}
// It must be exported.
if (stmt.modifiers === undefined ||
!stmt.modifiers.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword)) {
return false;
}
// It must declare ITS_JUST_ANGULAR.
return stmt.declarationList.declarations.some(decl => {
// The declaration must match the name.
if (!ts.isIdentifier(decl.name) || decl.name.text !== 'ITS_JUST_ANGULAR') {
return false;
}
// It must initialize the variable to true.
if (decl.initializer === undefined || decl.initializer.kind !== ts.SyntaxKind.TrueKeyword) {
return false;
}
// This definition matches.
return true;
});
});
}