perf(compiler): make the creation of ts.Program faster. (#19275)

We now create 2 programs with exactly the same fileNames and
exactly the same `import` / `export` declarations,
allowing TS to reuse the structure of first program
completely. When passing in an oldProgram and the files didn’t change,
TS can also reuse the old program completely.

This is possible buy adding generated files to TS
in `host.geSourceFile` via `ts.SourceFile.referencedFiles`.

This commit also:
- has a minor side effect on how we generate shared stylesheets:
  - previously every import in a stylesheet would generate a new
    `.ngstyles.ts` file.
  - now, we only generate 1 `.ngstyles.ts` file per entry in `@Component.styleUrls`.
  This was required as we need to be able to determine the program files
  without loading the resources (which can be async).
- makes all angular related methods in `CompilerHost`
  optional, allowing to just use a regular `ts.CompilerHost` as `CompilerHost`.
- simplifies the logic around `Compiler.analyzeNgModules` by introducing `NgAnalyzedFile`.

Perf impact: 1.5s improvement in compiling angular io
PR Close #19275
This commit is contained in:
Tobias Bosch
2017-09-12 09:40:28 -07:00
committed by Igor Minar
parent 9d2236a4b5
commit edd5f5a333
35 changed files with 1929 additions and 1536 deletions

View File

@ -6,8 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, createHostComponentMeta, flatten, identifierName, sourceUrl, templateSourceUrl} from '../compile_metadata';
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, componentFactoryName, flatten, identifierName, sourceUrl, templateSourceUrl} from '../compile_metadata';
import {CompilerConfig} from '../config';
import {ViewEncapsulation} from '../core';
import {MessageBundle} from '../i18n/message_bundle';
import {Identifiers, createTokenForExternalReference} from '../identifiers';
import {CompileMetadataResolver} from '../metadata_resolver';
@ -21,7 +22,7 @@ import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {SummaryResolver} from '../summary_resolver';
import {TemplateAst} from '../template_parser/template_ast';
import {TemplateParser} from '../template_parser/template_parser';
import {OutputContext, syntaxError} from '../util';
import {OutputContext, ValueVisitor, syntaxError, visitValue} from '../util';
import {TypeCheckCompiler} from '../view_compiler/type_check_compiler';
import {ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
@ -33,6 +34,12 @@ import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolv
import {createForJitStub, serializeSummaries} from './summary_serializer';
import {ngfactoryFilePath, splitTypescriptSuffix, summaryFileName, summaryForJitFileName, summaryForJitName} from './util';
export enum StubEmitFlags {
Basic = 1 << 0,
TypeCheck = 1 << 1,
All = TypeCheck | Basic
}
export class AotCompiler {
private _templateAstCache =
new Map<StaticSymbol, {template: TemplateAst[], pipes: CompilePipeSummary[]}>();
@ -40,20 +47,20 @@ export class AotCompiler {
constructor(
private _config: CompilerConfig, private _host: AotCompilerHost,
private _reflector: StaticReflector, private _metadataResolver: CompileMetadataResolver,
private _htmlParser: HtmlParser, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _typeCheckCompiler: TypeCheckCompiler, private _ngModuleCompiler: NgModuleCompiler,
private _outputEmitter: OutputEmitter,
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _viewCompiler: ViewCompiler, private _typeCheckCompiler: TypeCheckCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
private _summaryResolver: SummaryResolver<StaticSymbol>, private _localeId: string|null,
private _translationFormat: string|null, private _enableSummariesForJit: boolean|null,
private _symbolResolver: StaticSymbolResolver) {}
private _translationFormat: string|null,
/** TODO(tbosch): remove this flag as it is always on in the new ngc */
private _enableSummariesForJit: boolean|null, private _symbolResolver: StaticSymbolResolver) {
}
clearCache() { this._metadataResolver.clearCache(); }
analyzeModulesSync(rootFiles: string[]): NgAnalyzedModules {
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
const analyzeResult = analyzeAndValidateNgModules(
programSymbols, this._host, this._symbolResolver, this._metadataResolver);
rootFiles, this._host, this._symbolResolver, this._metadataResolver);
analyzeResult.ngModules.forEach(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
ngModule.type.reference, true));
@ -61,9 +68,8 @@ export class AotCompiler {
}
analyzeModulesAsync(rootFiles: string[]): Promise<NgAnalyzedModules> {
const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host);
const analyzeResult = analyzeAndValidateNgModules(
programSymbols, this._host, this._symbolResolver, this._metadataResolver);
rootFiles, this._host, this._symbolResolver, this._metadataResolver);
return Promise
.all(analyzeResult.ngModules.map(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
@ -71,21 +77,183 @@ export class AotCompiler {
.then(() => analyzeResult);
}
emitAllStubs(analyzeResult: NgAnalyzedModules): GeneratedFile[] {
const {files, ngModuleByPipeOrDirective} = analyzeResult;
const sourceModules = files.map(
file => this._compileStubFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules));
return flatten(sourceModules);
analyzeFile(fileName: string): NgAnalyzedFile {
return analyzeFile(this._host, this._symbolResolver, this._metadataResolver, fileName);
}
emitAllImpls(analyzeResult: NgAnalyzedModules): GeneratedFile[] {
const {ngModuleByPipeOrDirective, files} = analyzeResult;
const sourceModules = files.map(
file => this._compileImplFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules,
file.injectables));
return flatten(sourceModules);
emitBasicStubs(file: NgAnalyzedFile): GeneratedFile[] {
return this._emitStubs(file, StubEmitFlags.Basic);
}
emitTypeCheckStubs(files: NgAnalyzedModules): GeneratedFile[] {
const generatedFiles: GeneratedFile[] = [];
files.files.forEach(
file => this._emitStubs(file, StubEmitFlags.TypeCheck)
.forEach(genFile => generatedFiles.push(genFile)));
return generatedFiles;
}
loadFilesAsync(files: NgAnalyzedFile[]): Promise<NgAnalyzedModules> {
const loadingPromises: Promise<NgAnalyzedModules>[] = [];
files.forEach(
file => file.ngModules.forEach(
ngModule =>
loadingPromises.push(this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
ngModule.type.reference, false))));
return Promise.all(loadingPromises).then(_ => mergeAndValidateNgFiles(files));
}
loadFilesSync(files: NgAnalyzedFile[]): NgAnalyzedModules {
files.forEach(
file => file.ngModules.forEach(
ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
ngModule.type.reference, true)));
return mergeAndValidateNgFiles(files);
}
private _emitStubs(file: NgAnalyzedFile, emitFlags: StubEmitFlags): GeneratedFile[] {
return [
...this._createNgFactoryStub(file, emitFlags),
...this._createExternalStyleSheetNgFactoryStubs(file, emitFlags),
...this._createNgSummaryStub(file, emitFlags)
];
}
private _createNgFactoryStub(file: NgAnalyzedFile, emitFlags: StubEmitFlags): GeneratedFile[] {
const generatedFiles: GeneratedFile[] = [];
const outputCtx = this._createOutputContext(ngfactoryFilePath(file.fileName, true));
file.ngModules.forEach((ngModuleMeta, ngModuleIndex) => {
// Note: the code below needs to executed for StubEmitFlags.Basic and StubEmitFlags.TypeCheck,
// so we don't change the .ngfactory file too much when adding the typecheck block.
// create exports that user code can reference
this._ngModuleCompiler.createStub(outputCtx, ngModuleMeta.type.reference);
// add references to the symbols from the metadata.
// These can be used by the type check block for components,
// and they also cause TypeScript to include these files into the program too,
// which will make them part of the analyzedFiles.
const externalReferences: StaticSymbol[] = [
...ngModuleMeta.declaredDirectives.map(d => d.reference),
...ngModuleMeta.declaredPipes.map(d => d.reference),
...ngModuleMeta.importedModules.map(m => m.type.reference),
...ngModuleMeta.exportedModules.map(m => m.type.reference),
];
const externalReferenceVars = new Map<any, string>();
externalReferences.forEach((ref, typeIndex) => {
if (this._host.isSourceFile(ref.filePath)) {
externalReferenceVars.set(ref, `_decl${ngModuleIndex}_${typeIndex}`);
}
});
externalReferenceVars.forEach((varName, reference) => {
outputCtx.statements.push(
o.variable(varName)
.set(o.NULL_EXPR.cast(o.DYNAMIC_TYPE))
.toDeclStmt(o.expressionType(outputCtx.importExpr(reference))));
});
if (emitFlags & StubEmitFlags.TypeCheck) {
// add the typecheck block for all components of the NgModule
ngModuleMeta.declaredDirectives.forEach((dirId) => {
const compMeta = this._metadataResolver.getDirectiveMetadata(dirId.reference);
if (!compMeta.isComponent) {
return;
}
this._createTypeCheckBlock(
outputCtx, ngModuleMeta, this._metadataResolver.getHostComponentMetadata(compMeta),
[compMeta.type], externalReferenceVars);
this._createTypeCheckBlock(
outputCtx, ngModuleMeta, compMeta, ngModuleMeta.transitiveModule.directives,
externalReferenceVars);
});
}
});
// make sure we create a .ngfactory if we have a least one component
// in the file.
// Only do this for StubEmitFlags.Basic, as adding a type check block
// does not change this file (as we generate type check blocks based on NgModules).
if (outputCtx.statements.length === 0 && (emitFlags & StubEmitFlags.Basic) &&
file.directives.some(
dir => this._metadataResolver.getNonNormalizedDirectiveMetadata(
dir) !.metadata.isComponent)) {
_createEmptyStub(outputCtx);
}
// make sure we create a .ngfactory if we reexport a non source file.
// Only do this for StubEmitFlags.Basic, as adding a type check block
// does not change this file (as we generate type check blocks based on NgModules).
if (outputCtx.statements.length === 0 && (emitFlags & StubEmitFlags.Basic) &&
file.exportsNonSourceFiles) {
_createEmptyStub(outputCtx);
}
if (outputCtx.statements.length > 0) {
generatedFiles.push(this._codegenSourceModule(file.fileName, outputCtx));
}
return generatedFiles;
}
private _createExternalStyleSheetNgFactoryStubs(file: NgAnalyzedFile, emitFlags: StubEmitFlags):
GeneratedFile[] {
const generatedFiles: GeneratedFile[] = [];
if (!(emitFlags & StubEmitFlags.Basic)) {
// note: stylesheet stubs don't change when we produce type check stubs
return generatedFiles;
}
const fileSuffix = splitTypescriptSuffix(file.fileName, true)[1];
file.directives.forEach((dirSymbol) => {
const compMeta =
this._metadataResolver.getNonNormalizedDirectiveMetadata(dirSymbol) !.metadata;
if (!compMeta.isComponent) {
return;
}
// Note: compMeta is a component and therefore template is non null.
compMeta.template !.styleUrls.forEach((styleUrl) => {
const normalizedUrl = this._host.resourceNameToFileName(styleUrl, file.fileName);
if (!normalizedUrl) {
throw new Error(`Couldn't resolve resource ${styleUrl} relative to ${file.fileName}`);
}
const encapsulation =
compMeta.template !.encapsulation || this._config.defaultEncapsulation;
const outputCtx = this._createOutputContext(_stylesModuleUrl(
normalizedUrl, encapsulation === ViewEncapsulation.Emulated, fileSuffix));
_createEmptyStub(outputCtx);
generatedFiles.push(this._codegenSourceModule(normalizedUrl, outputCtx));
});
});
return generatedFiles;
}
private _createNgSummaryStub(file: NgAnalyzedFile, emitFlags: StubEmitFlags): GeneratedFile[] {
const generatedFiles: GeneratedFile[] = [];
// note: .ngsummary.js stubs don't change when we produce type check stubs
if (!this._enableSummariesForJit || !(emitFlags & StubEmitFlags.Basic)) {
return generatedFiles;
}
if (file.directives.length || file.injectables.length || file.ngModules.length ||
file.pipes.length || file.exportsNonSourceFiles) {
const outputCtx = this._createOutputContext(summaryForJitFileName(file.fileName, true));
file.ngModules.forEach(ngModule => {
// create exports that user code can reference
createForJitStub(outputCtx, ngModule.type.reference);
});
if (outputCtx.statements.length === 0) {
_createEmptyStub(outputCtx);
}
generatedFiles.push(this._codegenSourceModule(file.fileName, outputCtx));
}
return generatedFiles;
}
private _createTypeCheckBlock(
ctx: OutputContext, moduleMeta: CompileNgModuleMetadata, compMeta: CompileDirectiveMetadata,
directives: CompileIdentifierMetadata[], externalReferenceVars: Map<any, string>) {
const {template: parsedTemplate, pipes: usedPipes} =
this._parseTemplate(compMeta, moduleMeta, directives);
ctx.statements.push(...this._typeCheckCompiler.compileComponent(
compMeta, parsedTemplate, usedPipes, externalReferenceVars));
}
emitMessageBundle(analyzeResult: NgAnalyzedModules, locale: string|null): MessageBundle {
@ -107,7 +275,8 @@ export class AotCompiler {
const html = compMeta.template !.template !;
const interpolationConfig =
InterpolationConfig.fromArray(compMeta.template !.interpolation);
errors.push(...messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig) !);
errors.push(
...messageBundle.updateFromTemplate(html, file.fileName, interpolationConfig) !);
});
});
@ -118,69 +287,18 @@ export class AotCompiler {
return messageBundle;
}
private _compileStubFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], pipes: StaticSymbol[],
ngModules: StaticSymbol[]): GeneratedFile[] {
const fileSuffix = splitTypescriptSuffix(srcFileUrl, true)[1];
const generatedFiles: GeneratedFile[] = [];
const ngFactoryOutputCtx = this._createOutputContext(ngfactoryFilePath(srcFileUrl, true));
const jitSummaryOutputCtx = this._createOutputContext(summaryForJitFileName(srcFileUrl, true));
// create exports that user code can reference
ngModules.forEach((ngModuleReference) => {
this._ngModuleCompiler.createStub(ngFactoryOutputCtx, ngModuleReference);
createForJitStub(jitSummaryOutputCtx, ngModuleReference);
});
// create stubs for external stylesheets (always empty, as users should not import anything from
// the generated code)
directives.forEach((dirType) => {
const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType);
if (!compMeta.isComponent) {
return;
}
const ngModule = ngModuleByPipeOrDirective.get(dirType);
if (!ngModule) {
throw new Error(
`Internal Error: cannot determine the module for component ${identifierName(compMeta.type)}!`);
}
this._compileComponentTypeCheckBlock(
ngFactoryOutputCtx, compMeta, ngModule, ngModule.transitiveModule.directives);
// Note: compMeta is a component and therefore template is non null.
compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => {
const styleContext = this._createOutputContext(_stylesModuleUrl(
stylesheetMeta.moduleUrl !, this._styleCompiler.needsStyleShim(compMeta), fileSuffix));
_createStub(styleContext);
generatedFiles.push(this._codegenSourceModule(stylesheetMeta.moduleUrl !, styleContext));
});
});
if (ngFactoryOutputCtx.statements.length <= 0) {
_createStub(ngFactoryOutputCtx);
}
if (jitSummaryOutputCtx.statements.length <= 0) {
_createStub(jitSummaryOutputCtx);
}
// Note: we are creating stub ngfactory/ngsummary for all source files,
// as the real calculation requires almost the same logic as producing the real content for
// them. Our pipeline will filter out empty ones at the end. Because of this filter, however,
// stub references to the reference type needs to be generated even if the user cannot
// refer to type from the `.d.ts` file to prevent the file being elided from the emit.
generatedFiles.push(this._codegenSourceModule(srcFileUrl, ngFactoryOutputCtx));
if (this._enableSummariesForJit) {
generatedFiles.push(this._codegenSourceModule(srcFileUrl, jitSummaryOutputCtx));
}
return generatedFiles;
emitAllImpls(analyzeResult: NgAnalyzedModules): GeneratedFile[] {
const {ngModuleByPipeOrDirective, files} = analyzeResult;
const sourceModules = files.map(
file => this._compileImplFile(
file.fileName, ngModuleByPipeOrDirective, file.directives, file.pipes, file.ngModules,
file.injectables));
return flatten(sourceModules);
}
private _compileImplFile(
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[],
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: CompileNgModuleMetadata[],
injectables: StaticSymbol[]): GeneratedFile[] {
const fileSuffix = splitTypescriptSuffix(srcFileUrl, true)[1];
const generatedFiles: GeneratedFile[] = [];
@ -191,7 +309,7 @@ export class AotCompiler {
...this._createSummary(srcFileUrl, directives, pipes, ngModules, injectables, outputCtx));
// compile all ng modules
ngModules.forEach((ngModuleType) => this._compileModule(outputCtx, ngModuleType));
ngModules.forEach((ngModuleMeta) => this._compileModule(outputCtx, ngModuleMeta));
// compile components
directives.forEach((dirType) => {
@ -228,7 +346,7 @@ export class AotCompiler {
private _createSummary(
srcFileName: string, directives: StaticSymbol[], pipes: StaticSymbol[],
ngModules: StaticSymbol[], injectables: StaticSymbol[],
ngModules: CompileNgModuleMetadata[], injectables: StaticSymbol[],
ngFactoryCtx: OutputContext): GeneratedFile[] {
const symbolSummaries = this._symbolResolver.getSymbolsOf(srcFileName)
.map(symbol => this._symbolResolver.resolveSymbol(symbol));
@ -238,10 +356,11 @@ export class AotCompiler {
CompileTypeMetadata
}[] =
[
...ngModules.map(ref => ({
summary: this._metadataResolver.getNgModuleSummary(ref) !,
metadata: this._metadataResolver.getNgModuleMetadata(ref) !
})),
...ngModules.map(
meta => ({
summary: this._metadataResolver.getNgModuleSummary(meta.type.reference) !,
metadata: this._metadataResolver.getNgModuleMetadata(meta.type.reference) !
})),
...directives.map(ref => ({
summary: this._metadataResolver.getDirectiveSummary(ref) !,
metadata: this._metadataResolver.getDirectiveMetadata(ref) !
@ -273,8 +392,7 @@ export class AotCompiler {
return [summaryJson];
}
private _compileModule(outputCtx: OutputContext, ngModuleType: StaticSymbol): void {
const ngModule = this._metadataResolver.getNgModuleMetadata(ngModuleType) !;
private _compileModule(outputCtx: OutputContext, ngModule: CompileNgModuleMetadata): void {
const providers: CompileProviderMetadata[] = [];
if (this._localeId) {
@ -298,10 +416,7 @@ export class AotCompiler {
private _compileComponentFactory(
outputCtx: OutputContext, compMeta: CompileDirectiveMetadata,
ngModule: CompileNgModuleMetadata, fileSuffix: string): void {
const hostType = this._metadataResolver.getHostComponentType(compMeta.type.reference);
const hostMeta = createHostComponentMeta(
hostType, compMeta, this._metadataResolver.getHostComponentViewClass(hostType),
this._htmlParser);
const hostMeta = this._metadataResolver.getHostComponentMetadata(compMeta);
const hostViewFactoryVar =
this._compileComponent(outputCtx, hostMeta, ngModule, [compMeta.type], null, fileSuffix)
.viewClassVar;
@ -336,26 +451,6 @@ export class AotCompiler {
[o.StmtModifier.Final, o.StmtModifier.Exported]));
}
private _parseTemplate(
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
directiveIdentifiers: CompileIdentifierMetadata[]):
{template: TemplateAst[], pipes: CompilePipeSummary[]} {
let result = this._templateAstCache.get(compMeta.type.reference);
if (result) {
return result;
}
const preserveWhitespaces = compMeta !.template !.preserveWhitespaces;
const directives =
directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
const pipes = ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
result = this._templateParser.parse(
compMeta, compMeta.template !.htmlAst !, directives, pipes, ngModule.schemas,
templateSourceUrl(ngModule.type, compMeta, compMeta.template !), preserveWhitespaces);
this._templateAstCache.set(compMeta.type.reference, result);
return result;
}
private _compileComponent(
outputCtx: OutputContext, compMeta: CompileDirectiveMetadata,
ngModule: CompileNgModuleMetadata, directiveIdentifiers: CompileIdentifierMetadata[],
@ -373,12 +468,23 @@ export class AotCompiler {
return viewResult;
}
private _compileComponentTypeCheckBlock(
outputCtx: OutputContext, compMeta: CompileDirectiveMetadata,
ngModule: CompileNgModuleMetadata, directiveIdentifiers: CompileIdentifierMetadata[]) {
const {template: parsedTemplate, pipes: usedPipes} =
this._parseTemplate(compMeta, ngModule, directiveIdentifiers);
this._typeCheckCompiler.compileComponent(outputCtx, compMeta, parsedTemplate, usedPipes);
private _parseTemplate(
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
directiveIdentifiers: CompileIdentifierMetadata[]):
{template: TemplateAst[], pipes: CompilePipeSummary[]} {
if (this._templateAstCache.has(compMeta.type.reference)) {
return this._templateAstCache.get(compMeta.type.reference) !;
}
const preserveWhitespaces = compMeta !.template !.preserveWhitespaces;
const directives =
directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
const pipes = ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
const result = this._templateParser.parse(
compMeta, compMeta.template !.htmlAst !, directives, pipes, ngModule.schemas,
templateSourceUrl(ngModule.type, compMeta, compMeta.template !), preserveWhitespaces);
this._templateAstCache.set(compMeta.type.reference, result);
return result;
}
private _createOutputContext(genFilePath: string): OutputContext {
@ -433,13 +539,14 @@ export class AotCompiler {
}
}
function _createStub(outputCtx: OutputContext) {
function _createEmptyStub(outputCtx: OutputContext) {
// Note: We need to produce at least one import statement so that
// TypeScript knows that the file is an es6 module. Otherwise our generated
// exports / imports won't be emitted properly by TypeScript.
outputCtx.statements.push(o.importExpr(Identifiers.ComponentFactory).toStmt());
}
function _resolveStyleStatements(
symbolResolver: StaticSymbolResolver, compileResult: CompiledStylesheet, needsShim: boolean,
fileSuffix: string): void {
@ -456,180 +563,164 @@ function _stylesModuleUrl(stylesheetUrl: string, shim: boolean, suffix: string):
export interface NgAnalyzedModules {
ngModules: CompileNgModuleMetadata[];
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
files: Array<{
srcUrl: string,
directives: StaticSymbol[],
pipes: StaticSymbol[],
ngModules: StaticSymbol[],
injectables: StaticSymbol[]
}>;
files: NgAnalyzedFile[];
symbolsMissingModule?: StaticSymbol[];
}
export interface NgAnalyzedFile {
fileName: string;
directives: StaticSymbol[];
pipes: StaticSymbol[];
ngModules: CompileNgModuleMetadata[];
injectables: StaticSymbol[];
exportsNonSourceFiles: boolean;
}
export interface NgAnalyzeModulesHost { isSourceFile(filePath: string): boolean; }
// Returns all the source files and a mapping from modules to directives
export function analyzeNgModules(
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
staticSymbolResolver: StaticSymbolResolver,
fileNames: string[], host: NgAnalyzeModulesHost, staticSymbolResolver: StaticSymbolResolver,
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const programStaticSymbolsWithDecorators = programStaticSymbols.filter(
symbol => !symbol.filePath.endsWith('.d.ts') ||
staticSymbolResolver.hasDecorators(symbol.filePath));
const {ngModules, symbolsMissingModule} =
_createNgModules(programStaticSymbolsWithDecorators, host, metadataResolver);
return _analyzeNgModules(
programStaticSymbols, programStaticSymbolsWithDecorators, ngModules, symbolsMissingModule,
metadataResolver);
const files = _analyzeFilesIncludingNonProgramFiles(
fileNames, host, staticSymbolResolver, metadataResolver);
return mergeAnalyzedFiles(files);
}
export function analyzeAndValidateNgModules(
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
staticSymbolResolver: StaticSymbolResolver,
fileNames: string[], host: NgAnalyzeModulesHost, staticSymbolResolver: StaticSymbolResolver,
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const result =
analyzeNgModules(programStaticSymbols, host, staticSymbolResolver, metadataResolver);
if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
const messages = result.symbolsMissingModule.map(
return validateAnalyzedModules(
analyzeNgModules(fileNames, host, staticSymbolResolver, metadataResolver));
}
function validateAnalyzedModules(analyzedModules: NgAnalyzedModules): NgAnalyzedModules {
if (analyzedModules.symbolsMissingModule && analyzedModules.symbolsMissingModule.length) {
const messages = analyzedModules.symbolsMissingModule.map(
s =>
`Cannot determine the module for class ${s.name} in ${s.filePath}! Add ${s.name} to the NgModule to fix it.`);
throw syntaxError(messages.join('\n'));
}
return result;
return analyzedModules;
}
function _analyzeNgModules(
programSymbols: StaticSymbol[], programSymbolsWithDecorators: StaticSymbol[],
ngModuleMetas: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[],
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
const ngModulesByFile = new Map<string, StaticSymbol[]>();
const ngDirectivesByFile = new Map<string, StaticSymbol[]>();
const ngPipesByFile = new Map<string, StaticSymbol[]>();
const ngInjectablesByFile = new Map<string, StaticSymbol[]>();
const filePaths = new Set<string>();
// Analyzes all of the program files,
// including files that are not part of the program
// but are referenced by an NgModule.
function _analyzeFilesIncludingNonProgramFiles(
fileNames: string[], host: NgAnalyzeModulesHost, staticSymbolResolver: StaticSymbolResolver,
metadataResolver: CompileMetadataResolver): NgAnalyzedFile[] {
const seenFiles = new Set<string>();
const files: NgAnalyzedFile[] = [];
// Make sure we produce an analyzed file for each input file
programSymbols.forEach((symbol) => {
const filePath = symbol.filePath;
filePaths.add(filePath);
});
programSymbolsWithDecorators.forEach((symbol) => {
if (metadataResolver.isInjectable(symbol)) {
const filePath = symbol.filePath;
ngInjectablesByFile.set(filePath, (ngInjectablesByFile.get(filePath) || []).concat(symbol));
const visitFile = (fileName: string) => {
if (seenFiles.has(fileName) || !host.isSourceFile(fileName)) {
return false;
}
});
// Looping over all modules to construct:
// - a map from file to modules `ngModulesByFile`,
// - a map from file to directives `ngDirectivesByFile`,
// - a map from file to pipes `ngPipesByFile`,
// - a map from directive/pipe to module `ngModuleByPipeOrDirective`.
ngModuleMetas.forEach((ngModuleMeta) => {
const srcFileUrl = ngModuleMeta.type.reference.filePath;
filePaths.add(srcFileUrl);
ngModulesByFile.set(
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
const fileUrl = dirIdentifier.reference.filePath;
filePaths.add(fileUrl);
ngDirectivesByFile.set(
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference));
ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta);
seenFiles.add(fileName);
const analyzedFile = analyzeFile(host, staticSymbolResolver, metadataResolver, fileName);
files.push(analyzedFile);
analyzedFile.ngModules.forEach(ngModule => {
ngModule.transitiveModule.modules.forEach(modMeta => visitFile(modMeta.reference.filePath));
});
ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => {
const fileUrl = pipeIdentifier.reference.filePath;
filePaths.add(fileUrl);
ngPipesByFile.set(
fileUrl, (ngPipesByFile.get(fileUrl) || []).concat(pipeIdentifier.reference));
ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta);
});
});
const files: {
srcUrl: string,
directives: StaticSymbol[],
pipes: StaticSymbol[],
ngModules: StaticSymbol[],
injectables: StaticSymbol[]
}[] = [];
filePaths.forEach((srcUrl) => {
const directives = ngDirectivesByFile.get(srcUrl) || [];
const pipes = ngPipesByFile.get(srcUrl) || [];
const ngModules = ngModulesByFile.get(srcUrl) || [];
const injectables = ngInjectablesByFile.get(srcUrl) || [];
files.push({srcUrl, directives, pipes, ngModules, injectables});
});
return {
// map directive/pipe to module
ngModuleByPipeOrDirective,
// list modules and directives for every source file
files,
ngModules: ngModuleMetas, symbolsMissingModule
};
fileNames.forEach((fileName) => visitFile(fileName));
return files;
}
export function extractProgramSymbols(
staticSymbolResolver: StaticSymbolResolver, files: string[],
host: NgAnalyzeModulesHost): StaticSymbol[] {
const staticSymbols: StaticSymbol[] = [];
files.filter(fileName => host.isSourceFile(fileName)).forEach(sourceFile => {
staticSymbolResolver.getSymbolsOf(sourceFile).forEach((symbol) => {
export function analyzeFile(
host: NgAnalyzeModulesHost, staticSymbolResolver: StaticSymbolResolver,
metadataResolver: CompileMetadataResolver, fileName: string): NgAnalyzedFile {
const directives: StaticSymbol[] = [];
const pipes: StaticSymbol[] = [];
const injectables: StaticSymbol[] = [];
const ngModules: CompileNgModuleMetadata[] = [];
const hasDecorators = staticSymbolResolver.hasDecorators(fileName);
let exportsNonSourceFiles = false;
// Don't analyze .d.ts files that have no decorators as a shortcut
// to speed up the analysis. This prevents us from
// resolving the references in these files.
// Note: exportsNonSourceFiles is only needed when compiling with summaries,
// which is not the case when .d.ts files are treated as input files.
if (!fileName.endsWith('.d.ts') || hasDecorators) {
staticSymbolResolver.getSymbolsOf(fileName).forEach((symbol) => {
const resolvedSymbol = staticSymbolResolver.resolveSymbol(symbol);
const symbolMeta = resolvedSymbol.metadata;
if (symbolMeta) {
if (symbolMeta.__symbolic != 'error') {
// Ignore symbols that are only included to record error information.
staticSymbols.push(resolvedSymbol.symbol);
if (!symbolMeta || symbolMeta.__symbolic === 'error') {
return;
}
exportsNonSourceFiles =
exportsNonSourceFiles || isValueExportingNonSourceFile(host, symbolMeta);
if (symbolMeta.__symbolic === 'class') {
if (metadataResolver.isDirective(symbol)) {
directives.push(symbol);
} else if (metadataResolver.isPipe(symbol)) {
pipes.push(symbol);
} else if (metadataResolver.isInjectable(symbol)) {
injectables.push(symbol);
} else {
const ngModule = metadataResolver.getNgModuleMetadata(symbol, false);
if (ngModule) {
ngModules.push(ngModule);
}
}
}
});
});
return staticSymbols;
}
// Load the NgModules and check
// that all directives / pipes that are present in the program
// are also declared by a module.
function _createNgModules(
programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost,
metadataResolver: CompileMetadataResolver):
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
const ngModules = new Map<any, CompileNgModuleMetadata>();
const programPipesAndDirectives: StaticSymbol[] = [];
const ngModulePipesAndDirective = new Set<StaticSymbol>();
const addNgModule = (staticSymbol: any) => {
if (ngModules.has(staticSymbol) || !host.isSourceFile(staticSymbol.filePath)) {
return false;
}
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);
if (ngModule) {
ngModules.set(ngModule.type.reference, ngModule);
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
// For every input module add the list of transitively included modules
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.reference));
}
return !!ngModule;
}
return {
fileName, directives, pipes, ngModules, injectables, exportsNonSourceFiles,
};
programStaticSymbols.forEach((staticSymbol) => {
if (!addNgModule(staticSymbol) &&
(metadataResolver.isDirective(staticSymbol) || metadataResolver.isPipe(staticSymbol))) {
programPipesAndDirectives.push(staticSymbol);
}
function isValueExportingNonSourceFile(host: NgAnalyzeModulesHost, metadata: any): boolean {
let exportsNonSourceFiles = false;
class Visitor implements ValueVisitor {
visitArray(arr: any[], context: any): any { arr.forEach(v => visitValue(v, this, context)); }
visitStringMap(map: {[key: string]: any}, context: any): any {
Object.keys(map).forEach((key) => visitValue(map[key], this, context));
}
visitPrimitive(value: any, context: any): any {}
visitOther(value: any, context: any): any {
if (value instanceof StaticSymbol && !host.isSourceFile(value.filePath)) {
exportsNonSourceFiles = true;
}
}
}
visitValue(metadata, new Visitor(), null);
return exportsNonSourceFiles;
}
export function mergeAnalyzedFiles(analyzedFiles: NgAnalyzedFile[]): NgAnalyzedModules {
const allNgModules: CompileNgModuleMetadata[] = [];
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
const allPipesAndDirectives = new Set<StaticSymbol>();
analyzedFiles.forEach(af => {
af.ngModules.forEach(ngModule => {
allNgModules.push(ngModule);
ngModule.declaredDirectives.forEach(
d => ngModuleByPipeOrDirective.set(d.reference, ngModule));
ngModule.declaredPipes.forEach(p => ngModuleByPipeOrDirective.set(p.reference, ngModule));
});
af.directives.forEach(d => allPipesAndDirectives.add(d));
af.pipes.forEach(p => allPipesAndDirectives.add(p));
});
// Throw an error if any of the program pipe or directives is not declared by a module
const symbolsMissingModule =
programPipesAndDirectives.filter(s => !ngModulePipesAndDirective.has(s));
return {ngModules: Array.from(ngModules.values()), symbolsMissingModule};
const symbolsMissingModule: StaticSymbol[] = [];
allPipesAndDirectives.forEach(ref => {
if (!ngModuleByPipeOrDirective.has(ref)) {
symbolsMissingModule.push(ref);
}
});
return {
ngModules: allNgModules,
ngModuleByPipeOrDirective,
symbolsMissingModule,
files: analyzedFiles
};
}
function mergeAndValidateNgFiles(files: NgAnalyzedFile[]): NgAnalyzedModules {
return validateAnalyzedModules(mergeAnalyzedFiles(files));
}

View File

@ -77,17 +77,16 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
const tmplParser = new TemplateParser(
config, staticReflector, expressionParser, elementSchemaRegistry, htmlParser, console, []);
const resolver = new CompileMetadataResolver(
config, new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
console, symbolCache, staticReflector);
config, htmlParser, new NgModuleResolver(staticReflector),
new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver,
elementSchemaRegistry, normalizer, console, symbolCache, staticReflector);
// TODO(vicb): do not pass options.i18nFormat here
const viewCompiler = new ViewCompiler(config, staticReflector, elementSchemaRegistry);
const typeCheckCompiler = new TypeCheckCompiler(options, staticReflector);
const compiler = new AotCompiler(
config, compilerHost, staticReflector, resolver, htmlParser, tmplParser,
new StyleCompiler(urlResolver), viewCompiler, typeCheckCompiler,
new NgModuleCompiler(staticReflector), new TypeScriptEmitter(), summaryResolver,
options.locale || null, options.i18nFormat || null, options.enableSummariesForJit || null,
symbolResolver);
config, compilerHost, staticReflector, resolver, tmplParser, new StyleCompiler(urlResolver),
viewCompiler, typeCheckCompiler, new NgModuleCompiler(staticReflector),
new TypeScriptEmitter(), summaryResolver, options.locale || null, options.i18nFormat || null,
options.enableSummariesForJit || null, symbolResolver);
return {compiler, reflector: staticReflector};
}

View File

@ -252,7 +252,7 @@ export class StaticSymbolResolver {
/**
* hasDecorators checks a file's metadata for the presense of decorators without evalutating the
* metada.
* metadata.
*
* @param filePath the absolute path to examine for decorators.
* @returns true if any class in the file has a decorator.
@ -435,7 +435,8 @@ export class StaticSymbolResolver {
ResolvedStaticSymbol {
sourceSymbol.assertNoMembers();
targetSymbol.assertNoMembers();
if (this.summaryResolver.isLibraryFile(sourceSymbol.filePath)) {
if (this.summaryResolver.isLibraryFile(sourceSymbol.filePath) &&
this.summaryResolver.isLibraryFile(targetSymbol.filePath)) {
// This case is for an ng library importing symbols from a plain ts library
// transitively.
// Note: We rely on the fact that we discover symbols in the direction

View File

@ -9,10 +9,7 @@
import {StaticSymbol} from './aot/static_symbol';
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from './core';
import {LifecycleHooks} from './lifecycle_reflector';
import * as html from './ml_parser/ast';
import {HtmlParser} from './ml_parser/html_parser';
import {ParseTreeResult as HtmlParseTreeResult} from './ml_parser/parser';
import {CssSelector} from './selector';
import {splitAtColon, stringify} from './util';
@ -505,51 +502,6 @@ export class CompileDirectiveMetadata {
}
}
/**
* Construct {@link CompileDirectiveMetadata} from {@link ComponentTypeMetadata} and a selector.
*/
export function createHostComponentMeta(
hostTypeReference: any, compMeta: CompileDirectiveMetadata,
hostViewType: StaticSymbol | ProxyClass, htmlParser: HtmlParser): CompileDirectiveMetadata {
const template = CssSelector.parse(compMeta.selector !)[0].getMatchingElementTemplate();
const templateUrl = '';
const htmlAst = htmlParser.parse(template, templateUrl);
return CompileDirectiveMetadata.create({
isHost: true,
type: {reference: hostTypeReference, diDeps: [], lifecycleHooks: []},
template: new CompileTemplateMetadata({
encapsulation: ViewEncapsulation.None,
template,
templateUrl,
htmlAst,
styles: [],
styleUrls: [],
ngContentSelectors: [],
animations: [],
isInline: true,
externalStylesheets: [],
interpolation: null,
preserveWhitespaces: false,
}),
exportAs: null,
changeDetection: ChangeDetectionStrategy.Default,
inputs: [],
outputs: [],
host: {},
isComponent: true,
selector: '*',
providers: [],
viewProviders: [],
queries: [],
viewQueries: [],
componentViewType: hostViewType,
rendererType:
{id: '__Host__', encapsulation: ViewEncapsulation.None, styles: [], data: {}} as object,
entryComponents: [],
componentFactory: null
});
}
export interface CompilePipeSummary extends CompileTypeSummary {
type: CompileTypeMetadata;
name: string;

View File

@ -63,7 +63,7 @@ export * from './ml_parser/html_tags';
export * from './ml_parser/interpolation_config';
export * from './ml_parser/tags';
export {NgModuleCompiler} from './ng_module_compiler';
export {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement} from './output/output_ast';
export {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, BuiltinVar, CastExpr, ClassStmt, CommaExpr, CommentStmt, ConditionalExpr, DeclareFunctionStmt, DeclareVarStmt, ExpressionStatement, ExpressionVisitor, ExternalExpr, ExternalReference, FunctionExpr, IfStmt, InstantiateExpr, InvokeFunctionExpr, InvokeMethodExpr, LiteralArrayExpr, LiteralExpr, LiteralMapExpr, NotExpr, ReadKeyExpr, ReadPropExpr, ReadVarExpr, ReturnStatement, StatementVisitor, ThrowStmt, TryCatchStmt, WriteKeyExpr, WritePropExpr, WriteVarExpr, StmtModifier, Statement, collectExternalReferences} from './output/output_ast';
export {EmitterVisitorContext} from './output/abstract_emitter';
export * from './output/ts_emitter';
export * from './parse_util';

View File

@ -12,6 +12,7 @@ import {ViewEncapsulation} from './core';
import * as html from './ml_parser/ast';
import {HtmlParser} from './ml_parser/html_parser';
import {InterpolationConfig} from './ml_parser/interpolation_config';
import {ParseTreeResult as HtmlParseTreeResult} from './ml_parser/parser';
import {ResourceLoader} from './resource_loader';
import {extractStyleUrls, isStyleUrlResolvable} from './style_url_resolver';
import {PreparsedElementType, preparseElement} from './template_parser/template_preparser';
@ -88,12 +89,12 @@ export class DirectiveNormalizer {
}
return SyncAsync.then(
this.normalizeTemplateOnly(prenormData),
(result: CompileTemplateMetadata) => this.normalizeExternalStylesheets(result));
this._preParseTemplate(prenormData),
(preparsedTemplate) => this._normalizeTemplateMetadata(prenormData, preparsedTemplate));
}
normalizeTemplateOnly(prenomData: PrenormalizedTemplateMetadata):
SyncAsync<CompileTemplateMetadata> {
private _preParseTemplate(prenomData: PrenormalizedTemplateMetadata):
SyncAsync<PreparsedTemplate> {
let template: SyncAsync<string>;
let templateUrl: string;
if (prenomData.template != null) {
@ -104,12 +105,12 @@ export class DirectiveNormalizer {
template = this._fetch(templateUrl);
}
return SyncAsync.then(
template, (template) => this.normalizeLoadedTemplate(prenomData, template, templateUrl));
template, (template) => this._preparseLoadedTemplate(prenomData, template, templateUrl));
}
normalizeLoadedTemplate(
private _preparseLoadedTemplate(
prenormData: PrenormalizedTemplateMetadata, template: string,
templateAbsUrl: string): CompileTemplateMetadata {
templateAbsUrl: string): PreparsedTemplate {
const isInline = !!prenormData.template;
const interpolationConfig = InterpolationConfig.fromArray(prenormData.interpolation !);
const rootNodesAndErrors = this._htmlParser.parse(
@ -123,69 +124,98 @@ export class DirectiveNormalizer {
throw syntaxError(`Template parse errors:\n${errorString}`);
}
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
styles: prenormData.styles,
styleUrls: prenormData.styleUrls,
moduleUrl: prenormData.moduleUrl
}));
const templateMetadataStyles = this._normalizeStylesheet(new CompileStylesheetMetadata(
{styles: prenormData.styles, moduleUrl: prenormData.moduleUrl}));
const visitor = new TemplatePreparseVisitor();
html.visitAll(visitor, rootNodesAndErrors.rootNodes);
const templateStyles = this.normalizeStylesheet(new CompileStylesheetMetadata(
const templateStyles = this._normalizeStylesheet(new CompileStylesheetMetadata(
{styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl}));
const styles = templateMetadataStyles.styles.concat(templateStyles.styles);
const inlineStyleUrls = templateMetadataStyles.styleUrls.concat(templateStyles.styleUrls);
const styleUrls = this
._normalizeStylesheet(new CompileStylesheetMetadata(
{styleUrls: prenormData.styleUrls, moduleUrl: prenormData.moduleUrl}))
.styleUrls;
return {
template,
templateUrl: templateAbsUrl, isInline,
htmlAst: rootNodesAndErrors, styles, inlineStyleUrls, styleUrls,
ngContentSelectors: visitor.ngContentSelectors,
};
}
private _normalizeTemplateMetadata(
prenormData: PrenormalizedTemplateMetadata,
preparsedTemplate: PreparsedTemplate): SyncAsync<CompileTemplateMetadata> {
return SyncAsync.then(
this._loadMissingExternalStylesheets(
preparsedTemplate.styleUrls.concat(preparsedTemplate.inlineStyleUrls)),
(externalStylesheets) => this._normalizeLoadedTemplateMetadata(
prenormData, preparsedTemplate, externalStylesheets));
}
private _normalizeLoadedTemplateMetadata(
prenormData: PrenormalizedTemplateMetadata, preparsedTemplate: PreparsedTemplate,
stylesheets: Map<string, CompileStylesheetMetadata>): CompileTemplateMetadata {
// Algorithm:
// - produce exactly 1 entry per original styleUrl in
// CompileTemplateMetadata.externalStylesheets whith all styles inlined
// - inline all styles that are referenced by the template into CompileTemplateMetadata.styles.
// Reason: be able to determine how many stylesheets there are even without loading
// the template nor the stylesheets, so we can create a stub for TypeScript always synchronously
// (as resouce loading may be async)
const styles = [...preparsedTemplate.styles];
this._inlineStyles(preparsedTemplate.inlineStyleUrls, stylesheets, styles);
const styleUrls = preparsedTemplate.styleUrls;
const externalStylesheets = styleUrls.map(styleUrl => {
const stylesheet = stylesheets.get(styleUrl) !;
const styles = [...stylesheet.styles];
this._inlineStyles(stylesheet.styleUrls, stylesheets, styles);
return new CompileStylesheetMetadata({moduleUrl: styleUrl, styles: styles});
});
let encapsulation = prenormData.encapsulation;
if (encapsulation == null) {
encapsulation = this._config.defaultEncapsulation;
}
const styles = templateMetadataStyles.styles.concat(templateStyles.styles);
const styleUrls = templateMetadataStyles.styleUrls.concat(templateStyles.styleUrls);
if (encapsulation === ViewEncapsulation.Emulated && styles.length === 0 &&
styleUrls.length === 0) {
encapsulation = ViewEncapsulation.None;
}
return new CompileTemplateMetadata({
encapsulation,
template,
templateUrl: templateAbsUrl,
htmlAst: rootNodesAndErrors, styles, styleUrls,
ngContentSelectors: visitor.ngContentSelectors,
template: preparsedTemplate.template,
templateUrl: preparsedTemplate.templateUrl,
htmlAst: preparsedTemplate.htmlAst, styles, styleUrls,
ngContentSelectors: preparsedTemplate.ngContentSelectors,
animations: prenormData.animations,
interpolation: prenormData.interpolation, isInline,
externalStylesheets: [],
interpolation: prenormData.interpolation,
isInline: preparsedTemplate.isInline, externalStylesheets,
preserveWhitespaces: preserveWhitespacesDefault(
prenormData.preserveWhitespaces, this._config.preserveWhitespaces),
});
}
normalizeExternalStylesheets(templateMeta: CompileTemplateMetadata):
SyncAsync<CompileTemplateMetadata> {
return SyncAsync.then(
this._loadMissingExternalStylesheets(templateMeta.styleUrls),
(externalStylesheets) => new CompileTemplateMetadata({
encapsulation: templateMeta.encapsulation,
template: templateMeta.template,
templateUrl: templateMeta.templateUrl,
htmlAst: templateMeta.htmlAst,
styles: templateMeta.styles,
styleUrls: templateMeta.styleUrls,
externalStylesheets: externalStylesheets,
ngContentSelectors: templateMeta.ngContentSelectors,
animations: templateMeta.animations,
interpolation: templateMeta.interpolation,
isInline: templateMeta.isInline,
preserveWhitespaces: templateMeta.preserveWhitespaces,
}));
private _inlineStyles(
styleUrls: string[], stylesheets: Map<string, CompileStylesheetMetadata>,
targetStyles: string[]) {
styleUrls.forEach(styleUrl => {
const stylesheet = stylesheets.get(styleUrl) !;
stylesheet.styles.forEach(style => targetStyles.push(style));
this._inlineStyles(stylesheet.styleUrls, stylesheets, targetStyles);
});
}
private _loadMissingExternalStylesheets(
styleUrls: string[],
loadedStylesheets:
Map<string, CompileStylesheetMetadata> = new Map<string, CompileStylesheetMetadata>()):
SyncAsync<CompileStylesheetMetadata[]> {
SyncAsync<Map<string, CompileStylesheetMetadata>> {
return SyncAsync.then(
SyncAsync.all(styleUrls.filter((styleUrl) => !loadedStylesheets.has(styleUrl))
.map(
@ -193,16 +223,16 @@ export class DirectiveNormalizer {
this._fetch(styleUrl),
(loadedStyle) => {
const stylesheet =
this.normalizeStylesheet(new CompileStylesheetMetadata(
this._normalizeStylesheet(new CompileStylesheetMetadata(
{styles: [loadedStyle], moduleUrl: styleUrl}));
loadedStylesheets.set(styleUrl, stylesheet);
return this._loadMissingExternalStylesheets(
stylesheet.styleUrls, loadedStylesheets);
}))),
(_) => Array.from(loadedStylesheets.values()));
(_) => loadedStylesheets);
}
normalizeStylesheet(stylesheet: CompileStylesheetMetadata): CompileStylesheetMetadata {
private _normalizeStylesheet(stylesheet: CompileStylesheetMetadata): CompileStylesheetMetadata {
const moduleUrl = stylesheet.moduleUrl !;
const allStyleUrls = stylesheet.styleUrls.filter(isStyleUrlResolvable)
.map(url => this._urlResolver.resolve(moduleUrl, url));
@ -218,6 +248,17 @@ export class DirectiveNormalizer {
}
}
interface PreparsedTemplate {
template: string;
templateUrl: string;
isInline: boolean;
htmlAst: HtmlParseTreeResult;
styles: string[];
inlineStyleUrls: string[];
styleUrls: string[];
ngContentSelectors: string[];
}
class TemplatePreparseVisitor implements html.Visitor {
ngContentSelectors: string[] = [];
styles: string[] = [];

View File

@ -10,7 +10,7 @@
/**
* Extract i18n messages from source code
*/
import {analyzeAndValidateNgModules, extractProgramSymbols} from '../aot/compiler';
import {analyzeAndValidateNgModules} from '../aot/compiler';
import {createAotUrlResolver} from '../aot/compiler_factory';
import {StaticReflector} from '../aot/static_reflector';
import {StaticSymbolCache} from '../aot/static_symbol';
@ -56,9 +56,8 @@ export class Extractor {
private messageBundle: MessageBundle, private metadataResolver: CompileMetadataResolver) {}
extract(rootFiles: string[]): Promise<MessageBundle> {
const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host);
const {files, ngModules} = analyzeAndValidateNgModules(
programSymbols, this.host, this.staticSymbolResolver, this.metadataResolver);
rootFiles, this.host, this.staticSymbolResolver, this.metadataResolver);
return Promise
.all(ngModules.map(
ngModule => this.metadataResolver.loadNgModuleDirectiveAndPipeMetadata(
@ -79,7 +78,7 @@ export class Extractor {
const interpolationConfig =
InterpolationConfig.fromArray(compMeta.template !.interpolation);
errors.push(...this.messageBundle.updateFromTemplate(
html, file.srcUrl, interpolationConfig) !);
html, file.fileName, interpolationConfig) !);
});
});
@ -108,9 +107,9 @@ export class Extractor {
{get: (url: string) => host.loadResource(url)}, urlResolver, htmlParser, config);
const elementSchemaRegistry = new DomElementSchemaRegistry();
const resolver = new CompileMetadataResolver(
config, new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
console, symbolCache, staticReflector);
config, htmlParser, new NgModuleResolver(staticReflector),
new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver,
elementSchemaRegistry, normalizer, console, symbolCache, staticReflector);
// TODO(vicb): implicit tags & attributes
const messageBundle = new MessageBundle(htmlParser, [], {}, locale);

View File

@ -6,18 +6,18 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileStylesheetMetadata, CompileTypeSummary, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName, ngModuleJitUrl, sharedStylesheetJitUrl, templateJitUrl, templateSourceUrl} from '../compile_metadata';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeSummary, CompileProviderMetadata, CompileStylesheetMetadata, CompileTypeSummary, ProviderMeta, ProxyClass, identifierName, ngModuleJitUrl, sharedStylesheetJitUrl, templateJitUrl, templateSourceUrl} from '../compile_metadata';
import {CompileReflector} from '../compile_reflector';
import {CompilerConfig} from '../config';
import {Type} from '../core';
import {CompileMetadataResolver} from '../metadata_resolver';
import {HtmlParser} from '../ml_parser/html_parser';
import {NgModuleCompiler} from '../ng_module_compiler';
import * as ir from '../output/output_ast';
import {interpretStatements} from '../output/output_interpreter';
import {jitStatements} from '../output/output_jit';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {SummaryResolver} from '../summary_resolver';
import {TemplateAst} from '../template_parser/template_ast';
import {TemplateParser} from '../template_parser/template_parser';
import {Console, OutputContext, SyncAsync, stringify} from '../util';
import {ViewCompiler} from '../view_compiler/view_compiler';
@ -44,11 +44,11 @@ export class JitCompiler {
private _sharedStylesheetCount = 0;
constructor(
private _metadataResolver: CompileMetadataResolver, private _htmlParser: HtmlParser,
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler,
private _summaryResolver: SummaryResolver<Type>, private _reflector: CompileReflector,
private _compilerConfig: CompilerConfig, private _console: Console,
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
private _ngModuleCompiler: NgModuleCompiler, private _summaryResolver: SummaryResolver<Type>,
private _reflector: CompileReflector, private _compilerConfig: CompilerConfig,
private _console: Console,
private getExtraNgModuleProviders: (ngModule: any) => CompileProviderMetadata[]) {}
compileModuleSync(moduleType: Type): object {
@ -227,9 +227,8 @@ export class JitCompiler {
const compMeta = this._metadataResolver.getDirectiveMetadata(compType);
assertComponent(compMeta);
const hostClass = this._metadataResolver.getHostComponentType(compType);
const hostMeta = createHostComponentMeta(
hostClass, compMeta, (compMeta.componentFactory as any).viewDefFactory, this._htmlParser);
const hostMeta = this._metadataResolver.getHostComponentMetadata(
compMeta, (compMeta.componentFactory as any).viewDefFactory);
compiledTemplate =
new CompiledTemplate(true, compMeta.type, hostMeta, ngModule, [compMeta.type]);
this._compiledHostTemplateCache.set(compType, compiledTemplate);
@ -257,21 +256,16 @@ export class JitCompiler {
const externalStylesheetsByModuleUrl = new Map<string, CompiledStylesheet>();
const outputContext = createOutputContext();
const componentStylesheet = this._styleCompiler.compileComponent(outputContext, compMeta);
const preserveWhitespaces = compMeta !.template !.preserveWhitespaces;
compMeta.template !.externalStylesheets.forEach((stylesheetMeta) => {
const compiledStylesheet =
this._styleCompiler.compileStyles(createOutputContext(), compMeta, stylesheetMeta);
externalStylesheetsByModuleUrl.set(stylesheetMeta.moduleUrl !, compiledStylesheet);
});
this._resolveStylesCompileResult(componentStylesheet, externalStylesheetsByModuleUrl);
const directives =
template.directives.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
const pipes = template.ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
const {template: parsedTemplate, pipes: usedPipes} = this._templateParser.parse(
compMeta, compMeta.template !.template !, directives, pipes, template.ngModule.schemas,
templateSourceUrl(template.ngModule.type, template.compMeta, template.compMeta.template !),
preserveWhitespaces);
const {template: parsedTemplate, pipes: usedPipes} =
this._parseTemplate(compMeta, template.ngModule, template.directives);
const compileResult = this._viewCompiler.compileComponent(
outputContext, compMeta, parsedTemplate, ir.variable(componentStylesheet.stylesVar),
usedPipes);
@ -282,6 +276,21 @@ export class JitCompiler {
template.compiled(viewClass, rendererType);
}
private _parseTemplate(
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
directiveIdentifiers: CompileIdentifierMetadata[]):
{template: TemplateAst[], pipes: CompilePipeSummary[]} {
// Note: ! is ok here as components always have a template.
const preserveWhitespaces = compMeta.template !.preserveWhitespaces;
const directives =
directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveSummary(dir.reference));
const pipes = ngModule.transitiveModule.pipes.map(
pipe => this._metadataResolver.getPipeSummary(pipe.reference));
return this._templateParser.parse(
compMeta, compMeta.template !.htmlAst !, directives, pipes, ngModule.schemas,
templateSourceUrl(ngModule.type, compMeta, compMeta.template !), preserveWhitespaces);
}
private _resolveStylesCompileResult(
result: CompiledStylesheet, externalStylesheetsByModuleUrl: Map<string, CompiledStylesheet>) {
result.dependencies.forEach((dep, i) => {

View File

@ -12,14 +12,16 @@ import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
import * as cpl from './compile_metadata';
import {CompileReflector} from './compile_reflector';
import {CompilerConfig} from './config';
import {ChangeDetectionStrategy, Component, Directive, ModuleWithProviders, Provider, Query, SchemaMetadata, Type, createAttribute, createComponent, createHost, createInject, createInjectable, createInjectionToken, createOptional, createSelf, createSkipSelf} from './core';
import {ChangeDetectionStrategy, Component, Directive, ModuleWithProviders, Provider, Query, SchemaMetadata, Type, ViewEncapsulation, createAttribute, createComponent, createHost, createInject, createInjectable, createInjectionToken, createOptional, createSelf, createSkipSelf} from './core';
import {DirectiveNormalizer} from './directive_normalizer';
import {DirectiveResolver} from './directive_resolver';
import {Identifiers} from './identifiers';
import {getAllLifecycleHooks} from './lifecycle_reflector';
import {HtmlParser} from './ml_parser/html_parser';
import {NgModuleResolver} from './ng_module_resolver';
import {PipeResolver} from './pipe_resolver';
import {ElementSchemaRegistry} from './schema/element_schema_registry';
import {CssSelector} from './selector';
import {SummaryResolver} from './summary_resolver';
import {Console, SyncAsync, ValueTransformer, isPromise, noUndefined, resolveForwardRef, stringify, syntaxError, visitValue} from './util';
@ -44,9 +46,9 @@ export class CompileMetadataResolver {
private _ngModuleOfTypes = new Map<Type, Type>();
constructor(
private _config: CompilerConfig, private _ngModuleResolver: NgModuleResolver,
private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver,
private _summaryResolver: SummaryResolver<any>,
private _config: CompilerConfig, private _htmlParser: HtmlParser,
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver<any>,
private _schemaRegistry: ElementSchemaRegistry,
private _directiveNormalizer: DirectiveNormalizer, private _console: Console,
private _staticSymbolCache: StaticSymbolCache, private _reflector: CompileReflector,
@ -167,6 +169,54 @@ export class CompileMetadataResolver {
return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null;
}
getHostComponentMetadata(
compMeta: cpl.CompileDirectiveMetadata,
hostViewType?: StaticSymbol|cpl.ProxyClass): cpl.CompileDirectiveMetadata {
const hostType = this.getHostComponentType(compMeta.type.reference);
if (!hostViewType) {
hostViewType = this.getHostComponentViewClass(hostType);
}
// Note: ! is ok here as this method should only be called with normalized directive
// metadata, which always fills in the selector.
const template = CssSelector.parse(compMeta.selector !)[0].getMatchingElementTemplate();
const templateUrl = '';
const htmlAst = this._htmlParser.parse(template, templateUrl);
return cpl.CompileDirectiveMetadata.create({
isHost: true,
type: {reference: hostType, diDeps: [], lifecycleHooks: []},
template: new cpl.CompileTemplateMetadata({
encapsulation: ViewEncapsulation.None,
template,
templateUrl,
htmlAst,
styles: [],
styleUrls: [],
ngContentSelectors: [],
animations: [],
isInline: true,
externalStylesheets: [],
interpolation: null,
preserveWhitespaces: false,
}),
exportAs: null,
changeDetection: ChangeDetectionStrategy.Default,
inputs: [],
outputs: [],
host: {},
isComponent: true,
selector: '*',
providers: [],
viewProviders: [],
queries: [],
viewQueries: [],
componentViewType: hostViewType,
rendererType:
{id: '__Host__', encapsulation: ViewEncapsulation.None, styles: [], data: {}} as object,
entryComponents: [],
componentFactory: null
});
}
loadDirectiveMetadata(ngModuleType: any, directiveType: any, isSync: boolean): SyncAsync<null> {
if (this._directiveCache.has(directiveType)) {
return null;

View File

@ -924,85 +924,109 @@ export class AstTransformer implements StatementVisitor, ExpressionVisitor {
export class RecursiveAstVisitor implements StatementVisitor, ExpressionVisitor {
visitReadVarExpr(ast: ReadVarExpr, context: any): any { return ast; }
visitWriteVarExpr(expr: WriteVarExpr, context: any): any {
expr.value.visitExpression(this, context);
return expr;
visitType(ast: Type, context: any): any { return ast; }
visitExpression(ast: Expression, context: any): any {
if (ast.type) {
ast.type.visitType(this, context);
}
return ast;
}
visitWriteKeyExpr(expr: WriteKeyExpr, context: any): any {
expr.receiver.visitExpression(this, context);
expr.index.visitExpression(this, context);
expr.value.visitExpression(this, context);
return expr;
visitBuiltintType(type: BuiltinType, context: any): any { return this.visitType(type, context); }
visitExpressionType(type: ExpressionType, context: any): any {
type.value.visitExpression(this, context);
return this.visitType(type, context);
}
visitWritePropExpr(expr: WritePropExpr, context: any): any {
expr.receiver.visitExpression(this, context);
expr.value.visitExpression(this, context);
return expr;
visitArrayType(type: ArrayType, context: any): any { return this.visitType(type, context); }
visitMapType(type: MapType, context: any): any { return this.visitType(type, context); }
visitReadVarExpr(ast: ReadVarExpr, context: any): any {
return this.visitExpression(ast, context);
}
visitWriteVarExpr(ast: WriteVarExpr, context: any): any {
ast.value.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitWriteKeyExpr(ast: WriteKeyExpr, context: any): any {
ast.receiver.visitExpression(this, context);
ast.index.visitExpression(this, context);
ast.value.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitWritePropExpr(ast: WritePropExpr, context: any): any {
ast.receiver.visitExpression(this, context);
ast.value.visitExpression(this, context);
return this.visitExpression(ast, context);
}
visitInvokeMethodExpr(ast: InvokeMethodExpr, context: any): any {
ast.receiver.visitExpression(this, context);
this.visitAllExpressions(ast.args, context);
return ast;
return this.visitExpression(ast, context);
}
visitInvokeFunctionExpr(ast: InvokeFunctionExpr, context: any): any {
ast.fn.visitExpression(this, context);
this.visitAllExpressions(ast.args, context);
return ast;
return this.visitExpression(ast, context);
}
visitInstantiateExpr(ast: InstantiateExpr, context: any): any {
ast.classExpr.visitExpression(this, context);
this.visitAllExpressions(ast.args, context);
return ast;
return this.visitExpression(ast, context);
}
visitLiteralExpr(ast: LiteralExpr, context: any): any {
return this.visitExpression(ast, context);
}
visitExternalExpr(ast: ExternalExpr, context: any): any {
if (ast.typeParams) {
ast.typeParams.forEach(type => type.visitType(this, context));
}
return this.visitExpression(ast, context);
}
visitLiteralExpr(ast: LiteralExpr, context: any): any { return ast; }
visitExternalExpr(ast: ExternalExpr, context: any): any { return ast; }
visitConditionalExpr(ast: ConditionalExpr, context: any): any {
ast.condition.visitExpression(this, context);
ast.trueCase.visitExpression(this, context);
ast.falseCase !.visitExpression(this, context);
return ast;
return this.visitExpression(ast, context);
}
visitNotExpr(ast: NotExpr, context: any): any {
ast.condition.visitExpression(this, context);
return ast;
return this.visitExpression(ast, context);
}
visitAssertNotNullExpr(ast: AssertNotNull, context: any): any {
ast.condition.visitExpression(this, context);
return ast;
return this.visitExpression(ast, context);
}
visitCastExpr(ast: CastExpr, context: any): any {
ast.value.visitExpression(this, context);
return ast;
return this.visitExpression(ast, context);
}
visitFunctionExpr(ast: FunctionExpr, context: any): any {
this.visitAllStatements(ast.statements, context);
return ast;
return this.visitExpression(ast, context);
}
visitBinaryOperatorExpr(ast: BinaryOperatorExpr, context: any): any {
ast.lhs.visitExpression(this, context);
ast.rhs.visitExpression(this, context);
return ast;
return this.visitExpression(ast, context);
}
visitReadPropExpr(ast: ReadPropExpr, context: any): any {
ast.receiver.visitExpression(this, context);
return ast;
return this.visitExpression(ast, context);
}
visitReadKeyExpr(ast: ReadKeyExpr, context: any): any {
ast.receiver.visitExpression(this, context);
ast.index.visitExpression(this, context);
return ast;
return this.visitExpression(ast, context);
}
visitLiteralArrayExpr(ast: LiteralArrayExpr, context: any): any {
this.visitAllExpressions(ast.entries, context);
return ast;
return this.visitExpression(ast, context);
}
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any {
ast.entries.forEach((entry) => entry.value.visitExpression(this, context));
return ast;
return this.visitExpression(ast, context);
}
visitCommaExpr(ast: CommaExpr, context: any): any {
this.visitAllExpressions(ast.parts, context);
return this.visitExpression(ast, context);
}
visitAllExpressions(exprs: Expression[], context: any): void {
exprs.forEach(expr => expr.visitExpression(this, context));
@ -1010,10 +1034,16 @@ export class RecursiveAstVisitor implements StatementVisitor, ExpressionVisitor
visitDeclareVarStmt(stmt: DeclareVarStmt, context: any): any {
stmt.value.visitExpression(this, context);
if (stmt.type) {
stmt.type.visitType(this, context);
}
return stmt;
}
visitDeclareFunctionStmt(stmt: DeclareFunctionStmt, context: any): any {
this.visitAllStatements(stmt.statements, context);
if (stmt.type) {
stmt.type.visitType(this, context);
}
return stmt;
}
visitExpressionStmt(stmt: ExpressionStatement, context: any): any {
@ -1078,6 +1108,20 @@ class _ReadVarVisitor extends RecursiveAstVisitor {
}
}
export function collectExternalReferences(stmts: Statement[]): ExternalReference[] {
const visitor = new _FindExternalReferencesVisitor();
visitor.visitAllStatements(stmts, null);
return visitor.externalReferences;
}
class _FindExternalReferencesVisitor extends RecursiveAstVisitor {
externalReferences: ExternalReference[] = [];
visitExternalExpr(e: ExternalExpr, context: any) {
this.externalReferences.push(e.value);
return super.visitExternalExpr(e, context);
}
}
export function applySourceSpanToStatementIfNeeded(
stmt: Statement, sourceSpan: ParseSourceSpan | null): Statement {
if (!sourceSpan) {

View File

@ -17,8 +17,6 @@ import * as o from '../output/output_ast';
import {convertValueToOutputAst} from '../output/value_util';
import {ParseSourceSpan} from '../parse_util';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
import {OutputContext} from '../util';
/**
* Generates code that is used to type check templates.
@ -26,23 +24,31 @@ import {OutputContext} from '../util';
export class TypeCheckCompiler {
constructor(private options: AotCompilerOptions, private reflector: StaticReflector) {}
/**
* Important notes:
* - This must not produce new `import` statements, but only refer to types outside
* of the file via the variables provided via externalReferenceVars.
* This allows Typescript to reuse the old program's structure as no imports have changed.
* - This must not produce any exports, as this would pollute the .d.ts file
* and also violate the point above.
*/
compileComponent(
outputCtx: OutputContext, component: CompileDirectiveMetadata, template: TemplateAst[],
usedPipes: CompilePipeSummary[]): void {
component: CompileDirectiveMetadata, template: TemplateAst[], usedPipes: CompilePipeSummary[],
externalReferenceVars: Map<StaticSymbol, string>): o.Statement[] {
const pipes = new Map<string, StaticSymbol>();
usedPipes.forEach(p => pipes.set(p.name, p.type.reference));
let embeddedViewCount = 0;
const viewBuilderFactory = (parent: ViewBuilder | null): ViewBuilder => {
const embeddedViewIndex = embeddedViewCount++;
return new ViewBuilder(
this.options, this.reflector, outputCtx, parent, component.type.reference,
embeddedViewIndex, pipes, viewBuilderFactory);
this.options, this.reflector, externalReferenceVars, parent, component.type.reference,
component.isHost, embeddedViewIndex, pipes, viewBuilderFactory);
};
const visitor = viewBuilderFactory(null);
visitor.visitAll([], template);
outputCtx.statements.push(...visitor.build());
return visitor.build();
}
}
@ -60,9 +66,9 @@ interface Expression {
value: AST;
}
const DYNAMIC_VAR_NAME = '_any';
class ViewBuilder implements TemplateAstVisitor, LocalResolver {
private outputVarTypes = new Map<string, OutputVarType>();
private outputVarNames = new Map<OutputVarType, string>();
private refOutputVars = new Map<string, OutputVarType>();
private variables: VariableAst[] = [];
private children: ViewBuilder[] = [];
@ -71,16 +77,23 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
constructor(
private options: AotCompilerOptions, private reflector: StaticReflector,
private outputCtx: OutputContext, private parent: ViewBuilder|null,
private component: StaticSymbol, private embeddedViewIndex: number,
private pipes: Map<string, StaticSymbol>, private viewBuilderFactory: ViewBuilderFactory) {}
private externalReferenceVars: Map<StaticSymbol, string>, private parent: ViewBuilder|null,
private component: StaticSymbol, private isHostComponent: boolean,
private embeddedViewIndex: number, private pipes: Map<string, StaticSymbol>,
private viewBuilderFactory: ViewBuilderFactory) {}
private getOrAddOutputVar(type: o.BuiltinTypeName|StaticSymbol): string {
let varName = this.outputVarNames.get(type);
private getOutputVar(type: o.BuiltinTypeName|StaticSymbol): string {
let varName: string|undefined;
if (type === this.component && this.isHostComponent) {
varName = DYNAMIC_VAR_NAME;
} else if (type instanceof StaticSymbol) {
varName = this.externalReferenceVars.get(type);
} else {
varName = DYNAMIC_VAR_NAME;
}
if (!varName) {
varName = `_v${this.outputVarNames.size}`;
this.outputVarNames.set(type, varName);
this.outputVarTypes.set(varName, type);
throw new Error(
`Illegal State: referring to a type without a variable ${JSON.stringify(type)}`);
}
return varName;
}
@ -92,15 +105,15 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
build(targetStatements: o.Statement[] = []): o.Statement[] {
this.children.forEach((child) => child.build(targetStatements));
const viewStmts: o.Statement[] = [];
const viewStmts: o.Statement[] =
[o.variable(DYNAMIC_VAR_NAME).set(o.NULL_EXPR).toDeclStmt(o.DYNAMIC_TYPE)];
let bindingCount = 0;
this.updates.forEach((expression) => {
const {sourceSpan, context, value} = this.preprocessUpdateExpression(expression);
const bindingId = `${bindingCount++}`;
const nameResolver = context === this.component ? this : null;
const {stmts, currValExpr} = convertPropertyBinding(
nameResolver, o.variable(this.getOrAddOutputVar(context)), value, bindingId);
nameResolver, o.variable(this.getOutputVar(context)), value, bindingId);
stmts.push(new o.ExpressionStatement(currValExpr));
viewStmts.push(...stmts.map(
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
@ -110,21 +123,13 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
const bindingId = `${bindingCount++}`;
const nameResolver = context === this.component ? this : null;
const {stmts} = convertActionBinding(
nameResolver, o.variable(this.getOrAddOutputVar(context)), value, bindingId);
nameResolver, o.variable(this.getOutputVar(context)), value, bindingId);
viewStmts.push(...stmts.map(
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
});
const viewName = `_View_${this.component.name}_${this.embeddedViewIndex}`;
const params: o.FnParam[] = [];
this.outputVarNames.forEach((varName, varType) => {
const outputType = varType instanceof StaticSymbol ?
o.expressionType(this.outputCtx.importExpr(varType)) :
new o.BuiltinType(varType);
params.push(new o.FnParam(varName, outputType));
});
const viewFactory = new o.DeclareFunctionStmt(viewName, params, viewStmts);
const viewFactory = new o.DeclareFunctionStmt(viewName, [], viewStmts);
targetStatements.push(viewFactory);
return targetStatements;
}
@ -211,7 +216,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
getLocal(name: string): o.Expression|null {
if (name == EventHandlerVars.event.name) {
return o.variable(this.getOrAddOutputVar(o.BuiltinTypeName.Dynamic));
return o.variable(this.getOutputVar(o.BuiltinTypeName.Dynamic));
}
for (let currBuilder: ViewBuilder|null = this; currBuilder; currBuilder = currBuilder.parent) {
let outputVarType: OutputVarType|undefined;
@ -225,7 +230,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
}
}
if (outputVarType != null) {
return o.variable(this.getOrAddOutputVar(outputVarType));
return o.variable(this.getOutputVar(outputVarType));
}
}
return null;
@ -237,7 +242,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
throw new Error(
`Illegal State: Could not find pipe ${name} in template of ${this.component}`);
}
return this.getOrAddOutputVar(pipe);
return this.getOutputVar(pipe);
}
private preprocessUpdateExpression(expression: Expression): Expression {
@ -264,7 +269,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
if (this.options.fullTemplateTypeCheck) {
return o.variable(this.pipeOutputVar(name)).callMethod('transform', args);
} else {
return o.variable(this.getOrAddOutputVar(o.BuiltinTypeName.Dynamic));
return o.variable(this.getOutputVar(o.BuiltinTypeName.Dynamic));
}
},
},

View File

@ -1,72 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {MockDirectory, compile, expectNoDiagnostics, setup, toMockFileArray} from './test_util';
describe('aot stubs', () => {
let angularFiles = setup();
it('should create empty .ngfactory and .ngsummary files for every source file', () => {
const appDir = {'app.ts': `export const x = 1;`};
const rootDir = {'app': appDir};
const {genFiles} = compile(
[rootDir, angularFiles],
{postCompile: expectNoDiagnostics, stubsOnly: true, enableSummariesForJit: true});
expect(genFiles.find((f) => f.genFileUrl === '/app/app.ngfactory.ts')).toBeTruthy();
expect(genFiles.find((f) => f.genFileUrl === '/app/app.ngsummary.ts')).toBeTruthy();
});
it('should create empty .ngstyle files for imported css files', () => {
const appDir = {
'app.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
template: '',
styleUrls: ['./style.css']
})
export class MyComp {}
@NgModule({
declarations: [MyComp]
})
export class MyModule {}
export const x = 1;
`,
'style.css': ''
};
const rootDir = {'app': appDir};
const {genFiles} =
compile([rootDir, angularFiles], {postCompile: expectNoDiagnostics, stubsOnly: true});
expect(genFiles.find((f) => f.genFileUrl === '/app/style.css.shim.ngstyle.ts')).toBeTruthy();
});
it('should create stub exports for NgModules of the right type', () => {
const appDir = {
'app.module.ts': `
import { NgModule } from '@angular/core';
@NgModule()
export class MyModule {}
`,
'app.boot.ts': `
import {NgModuleFactory} from '@angular/core';
import {MyModuleNgFactory} from './app.module.ngfactory';
import {MyModuleNgSummary} from './app.module.ngsummary';
import {MyModule} from './app.module';
export const factory: NgModuleFactory<MyModule> = MyModuleNgFactory;
export const summary: () => any[] = MyModuleNgSummary;
`
};
const rootDir = {'app': appDir};
compile(
[rootDir, angularFiles],
{postCompile: expectNoDiagnostics, stubsOnly: true, enableSummariesForJit: true});
});
});

View File

@ -633,7 +633,6 @@ export function compile(
useSummaries?: boolean,
preCompile?: (program: ts.Program) => void,
postCompile?: (program: ts.Program) => void,
stubsOnly?: boolean,
}& AotCompilerOptions = {},
tsOptions: ts.CompilerOptions = {}): {genFiles: GeneratedFile[], outDir: MockDirectory} {
// when using summaries, always emit so the next step can use the results.
@ -656,8 +655,7 @@ export function compile(
const {compiler, reflector} = createAotCompiler(aotHost, options);
const analyzedModules =
compiler.analyzeModulesSync(program.getSourceFiles().map(sf => sf.fileName));
const genFiles = options.stubsOnly ? compiler.emitAllStubs(analyzedModules) :
compiler.emitAllImpls(analyzedModules);
const genFiles = compiler.emitAllImpls(analyzedModules);
genFiles.forEach((file) => {
const source = file.source || toTypeScript(file);
if (isSource(file.genFileUrl)) {

View File

@ -10,23 +10,18 @@ import {CompileStylesheetMetadata, CompileTemplateMetadata} from '@angular/compi
import {CompilerConfig, preserveWhitespacesDefault} from '@angular/compiler/src/config';
import {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer';
import {ResourceLoader} from '@angular/compiler/src/resource_loader';
import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock';
import {ViewEncapsulation} from '@angular/core/src/metadata/view';
import {TestBed} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
import {TestBed, inject} from '@angular/core/testing';
import {noUndefined} from '../src/util';
import {SpyResourceLoader} from './spies';
import {TEST_COMPILER_PROVIDERS} from './test_bindings';
const SOME_MODULE_URL = 'package:some/module/a.js';
const SOME_HTTP_MODULE_URL = 'http://some/module/a.js';
function normalizeTemplate(normalizer: DirectiveNormalizer, o: {
ngModuleType?: any; componentType?: any; moduleUrl?: string; template?: string | null;
templateUrl?: string | null;
styles?: string[];
moduleUrl?: string; template?: string | null; templateUrl?: string | null; styles?: string[];
styleUrls?: string[];
interpolation?: [string, string] | null;
encapsulation?: ViewEncapsulation | null;
@ -34,9 +29,9 @@ function normalizeTemplate(normalizer: DirectiveNormalizer, o: {
preserveWhitespaces?: boolean | null;
}) {
return normalizer.normalizeTemplate({
ngModuleType: noUndefined(o.ngModuleType),
componentType: noUndefined(o.componentType),
moduleUrl: noUndefined(o.moduleUrl),
ngModuleType: null,
componentType: SomeComp,
moduleUrl: noUndefined(o.moduleUrl || SOME_MODULE_URL),
template: noUndefined(o.template),
templateUrl: noUndefined(o.templateUrl),
styles: noUndefined(o.styles),
@ -48,131 +43,39 @@ function normalizeTemplate(normalizer: DirectiveNormalizer, o: {
});
}
function normalizeTemplateOnly(normalizer: DirectiveNormalizer, o: {
ngModuleType?: any; componentType?: any; moduleUrl?: string; template?: string | null;
templateUrl?: string | null;
styles?: string[];
styleUrls?: string[];
interpolation?: [string, string] | null;
encapsulation?: ViewEncapsulation | null;
animations?: CompileAnimationEntryMetadata[];
preserveWhitespaces?: boolean | null;
}) {
return normalizer.normalizeTemplateOnly({
ngModuleType: noUndefined(o.ngModuleType),
componentType: noUndefined(o.componentType),
moduleUrl: noUndefined(o.moduleUrl),
template: noUndefined(o.template),
templateUrl: noUndefined(o.templateUrl),
styles: noUndefined(o.styles),
styleUrls: noUndefined(o.styleUrls),
interpolation: noUndefined(o.interpolation),
encapsulation: noUndefined(o.encapsulation),
animations: noUndefined(o.animations),
preserveWhitespaces: noUndefined(o.preserveWhitespaces),
});
}
function compileTemplateMetadata({encapsulation, template, templateUrl, styles, styleUrls,
externalStylesheets, animations, ngContentSelectors,
interpolation, isInline, preserveWhitespaces}: {
encapsulation?: ViewEncapsulation | null,
template?: string | null,
templateUrl?: string | null,
styles?: string[],
styleUrls?: string[],
externalStylesheets?: CompileStylesheetMetadata[],
ngContentSelectors?: string[],
animations?: any[],
interpolation?: [string, string] | null,
isInline?: boolean,
preserveWhitespaces?: boolean | null
}): CompileTemplateMetadata {
return new CompileTemplateMetadata({
encapsulation: encapsulation || null,
template: template || null,
templateUrl: templateUrl || null,
htmlAst: null,
styles: styles || [],
styleUrls: styleUrls || [],
externalStylesheets: externalStylesheets || [],
ngContentSelectors: ngContentSelectors || [],
animations: animations || [],
interpolation: interpolation || null,
isInline: !!isInline,
preserveWhitespaces: preserveWhitespacesDefault(noUndefined(preserveWhitespaces)),
});
}
function normalizeLoadedTemplate(
normalizer: DirectiveNormalizer, o: {
ngModuleType?: any; componentType?: any; moduleUrl?: string; template?: string | null;
templateUrl?: string | null;
styles?: string[];
styleUrls?: string[];
interpolation?: [string, string] | null;
encapsulation?: ViewEncapsulation | null;
animations?: CompileAnimationEntryMetadata[];
preserveWhitespaces?: boolean;
},
template: string, templateAbsUrl: string) {
return normalizer.normalizeLoadedTemplate(
{
ngModuleType: o.ngModuleType || null,
componentType: o.componentType || null,
moduleUrl: o.moduleUrl || '',
template: o.template || null,
templateUrl: o.templateUrl || null,
styles: o.styles || [],
styleUrls: o.styleUrls || [],
interpolation: o.interpolation || null,
encapsulation: o.encapsulation || null,
animations: o.animations || [],
preserveWhitespaces: noUndefined(o.preserveWhitespaces),
},
template, templateAbsUrl);
}
export function main() {
describe('DirectiveNormalizer', () => {
beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
let resourceLoaderSpy: jasmine.Spy;
describe('normalizeDirective', () => {
beforeEach(() => {
resourceLoaderSpy =
jasmine.createSpy('get').and.callFake((url: string) => `resource(${url})`);
const resourceLoader = {get: resourceLoaderSpy};
TestBed.configureCompiler({
providers:
[...TEST_COMPILER_PROVIDERS, {provide: ResourceLoader, useValue: resourceLoader}]
});
});
describe('normalizeTemplate', () => {
it('should throw if no template was specified',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
expect(() => normalizeTemplate(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
}))
expect(() => normalizeTemplate(normalizer, {}))
.toThrowError('No template specified for component SomeComp');
}));
it('should throw if template is not a string',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
expect(() => normalizeTemplate(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
template: <any>{}
}))
expect(() => normalizeTemplate(normalizer, {template: <any>{}}))
.toThrowError('The template specified for component SomeComp is not a string');
}));
it('should throw if templateUrl is not a string',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
expect(() => normalizeTemplate(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
templateUrl: <any>{}
}))
expect(() => normalizeTemplate(normalizer, {templateUrl: <any>{}}))
.toThrowError('The templateUrl specified for component SomeComp is not a string');
}));
it('should throw if both template and templateUrl are defined',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
expect(() => normalizeTemplate(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
template: '',
templateUrl: '',
}))
@ -181,29 +84,21 @@ export function main() {
it('should throw if preserveWhitespaces is not a boolean',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
expect(() => normalizeTemplate(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
template: '',
preserveWhitespaces: <any>'WRONG',
}))
.toThrowError(
'The preserveWhitespaces option for component SomeComp must be a boolean');
}));
});
describe('normalizeTemplateOnly sync', () => {
describe('inline template', () => {
it('should store the template',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template: 'a',
templateUrl: null,
styles: [],
styleUrls: []
});
expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('package:some/module/a.js');
@ -212,46 +107,26 @@ export function main() {
it('should resolve styles on the annotation against the moduleUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: '',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
});
const template = <CompileTemplateMetadata>normalizeTemplate(
normalizer, {template: '', styleUrls: ['test.css']});
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
}));
it('should resolve styles in the template against the moduleUrl',
it('should resolve styles in the template against the moduleUrl and add them to the styles',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: '<style>@import test.css</style>',
templateUrl: null,
styles: [],
styleUrls: []
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template: '<style>template @import test.css</style>',
styles: ['direct'],
});
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
expect(template.styles).toEqual([
'direct', 'template ', 'resource(package:some/module/test.css)'
]);
}));
it('should use ViewEncapsulation.Emulated by default',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: '',
templateUrl: null,
styles: [],
styleUrls: ['test.css']
});
const template = <CompileTemplateMetadata>normalizeTemplate(
normalizer, {template: '', styleUrls: ['test.css']});
expect(template.encapsulation).toEqual(ViewEncapsulation.Emulated);
}));
@ -260,195 +135,105 @@ export function main() {
[CompilerConfig, DirectiveNormalizer],
(config: CompilerConfig, normalizer: DirectiveNormalizer) => {
config.defaultEncapsulation = ViewEncapsulation.None;
const template = <CompileTemplateMetadata>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: undefined,
template: '',
templateUrl: undefined,
styles: [],
styleUrls: ['test.css']
});
const template = <CompileTemplateMetadata>normalizeTemplate(
normalizer, {template: '', styleUrls: ['test.css']});
expect(template.encapsulation).toEqual(ViewEncapsulation.None);
}));
});
describe('templateUrl', () => {
it('should load a template from a url that is resolved against moduleUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = <CompileTemplateMetadata>normalizeTemplate(
normalizer, {templateUrl: 'sometplurl.html', styleUrls: ['test.css']});
expect(template.template).toEqual('resource(package:some/module/sometplurl.html)');
expect(template.templateUrl).toEqual('package:some/module/sometplurl.html');
expect(template.isInline).toBe(false);
}));
it('should load a template from a url that is resolved against moduleUrl',
inject(
[AsyncTestCompleter, DirectiveNormalizer, ResourceLoader],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: MockResourceLoader) => {
resourceLoader.expect('package:some/module/sometplurl.html', 'a');
(<Promise<CompileTemplateMetadata>>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: null,
templateUrl: 'sometplurl.html',
styles: [],
styleUrls: ['test.css']
})).then((template) => {
expect(template.template).toEqual('a');
expect(template.templateUrl).toEqual('package:some/module/sometplurl.html');
expect(template.isInline).toBe(false);
async.done();
});
resourceLoader.flush();
}));
it('should resolve styles on the annotation against the moduleUrl',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = <CompileTemplateMetadata>normalizeTemplate(
normalizer, {templateUrl: 'tpl/sometplurl.html', styleUrls: ['test.css']});
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
}));
it('should resolve styles on the annotation against the moduleUrl',
inject(
[AsyncTestCompleter, DirectiveNormalizer, ResourceLoader],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: MockResourceLoader) => {
resourceLoader.expect('package:some/module/tpl/sometplurl.html', '');
(<Promise<CompileTemplateMetadata>>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: null,
templateUrl: 'tpl/sometplurl.html',
styles: [],
styleUrls: ['test.css']
})).then((template) => {
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
async.done();
});
resourceLoader.flush();
}));
it('should resolve styles in the template against the templateUrl and add them to the styles',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
resourceLoaderSpy.and.callFake((url: string) => {
switch (url) {
case 'package:some/module/tpl/sometplurl.html':
return '<style>template @import test.css</style>';
default:
return `resource(${url})`;
}
});
const template = <CompileTemplateMetadata>normalizeTemplate(
normalizer, {templateUrl: 'tpl/sometplurl.html', styles: ['direct']});
expect(template.styles).toEqual([
'direct', 'template ', 'resource(package:some/module/tpl/test.css)'
]);
}));
it('should resolve styles in the template against the templateUrl',
inject(
[AsyncTestCompleter, DirectiveNormalizer, ResourceLoader],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: MockResourceLoader) => {
resourceLoader.expect(
'package:some/module/tpl/sometplurl.html', '<style>@import test.css</style>');
(<Promise<CompileTemplateMetadata>>normalizeTemplateOnly(normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
template: null,
templateUrl: 'tpl/sometplurl.html',
styles: [],
styleUrls: []
})).then((template) => {
expect(template.styleUrls).toEqual(['package:some/module/tpl/test.css']);
async.done();
});
resourceLoader.flush();
}));
});
describe('normalizeExternalStylesheets', () => {
beforeEach(() => { TestBed.configureCompiler({providers: [SpyResourceLoader.PROVIDE]}); });
describe('externalStylesheets', () => {
it('should load an external stylesheet',
inject(
[AsyncTestCompleter, DirectiveNormalizer, ResourceLoader],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: SpyResourceLoader) => {
programResourceLoaderSpy(resourceLoader, {'package:some/module/test.css': 'a'});
(<Promise<CompileTemplateMetadata>>normalizer.normalizeExternalStylesheets(
compileTemplateMetadata({
template: '',
templateUrl: '',
styleUrls: ['package:some/module/test.css']
})))
.then((template) => {
expect(template.externalStylesheets.length).toBe(1);
expect(template.externalStylesheets[0]).toEqual(new CompileStylesheetMetadata({
moduleUrl: 'package:some/module/test.css',
styles: ['a'],
styleUrls: []
}));
async.done();
});
}));
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = <CompileTemplateMetadata>normalizeTemplate(
normalizer, {template: '', styleUrls: ['package:some/module/test.css']});
expect(template.externalStylesheets.length).toBe(1);
expect(template.externalStylesheets[0]).toEqual(new CompileStylesheetMetadata({
moduleUrl: 'package:some/module/test.css',
styles: ['resource(package:some/module/test.css)'],
}));
}));
it('should load stylesheets referenced by external stylesheets',
inject(
[AsyncTestCompleter, DirectiveNormalizer, ResourceLoader],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: SpyResourceLoader) => {
programResourceLoaderSpy(resourceLoader, {
'package:some/module/test.css': 'a@import "test2.css"',
'package:some/module/test2.css': 'b'
});
(<Promise<CompileTemplateMetadata>>normalizer.normalizeExternalStylesheets(
compileTemplateMetadata({
template: '',
templateUrl: '',
styleUrls: ['package:some/module/test.css']
})))
.then((template) => {
expect(template.externalStylesheets.length).toBe(2);
expect(template.externalStylesheets[0]).toEqual(new CompileStylesheetMetadata({
moduleUrl: 'package:some/module/test.css',
styles: ['a'],
styleUrls: ['package:some/module/test2.css']
}));
expect(template.externalStylesheets[1]).toEqual(new CompileStylesheetMetadata({
moduleUrl: 'package:some/module/test2.css',
styles: ['b'],
styleUrls: []
}));
async.done();
});
}));
it('should load stylesheets referenced by external stylesheets and inline them',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
resourceLoaderSpy.and.callFake((url: string) => {
switch (url) {
case 'package:some/module/test.css':
return 'a@import "test2.css"';
case 'package:some/module/test2.css':
return 'b';
default:
throw new Error(`Unexpected url ${url}`);
}
});
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template: '',
styleUrls: ['package:some/module/test.css'],
});
expect(template.externalStylesheets.length).toBe(1);
expect(template.externalStylesheets[0])
.toEqual(new CompileStylesheetMetadata(
{moduleUrl: 'package:some/module/test.css', styles: ['a', 'b'], styleUrls: []}));
}));
});
describe('caching', () => {
it('should work for templateUrl',
inject(
[AsyncTestCompleter, DirectiveNormalizer, ResourceLoader],
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
resourceLoader: MockResourceLoader) => {
resourceLoader.expect('package:some/module/cmp.html', 'a');
const prenormMeta = {
ngModuleType: null as any,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
templateUrl: 'cmp.html',
};
Promise
.all([
normalizeTemplateOnly(normalizer, prenormMeta),
normalizeTemplateOnly(normalizer, prenormMeta)
])
.then((templates: CompileTemplateMetadata[]) => {
expect(templates[0].template).toEqual('a');
expect(templates[1].template).toEqual('a');
async.done();
});
resourceLoader.flush();
}));
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const prenormMeta = {
templateUrl: 'cmp.html',
};
const template1 = <CompileTemplateMetadata>normalizeTemplate(normalizer, prenormMeta);
const template2 = <CompileTemplateMetadata>normalizeTemplate(normalizer, prenormMeta);
expect(template1.template).toEqual('resource(package:some/module/cmp.html)');
expect(template2.template).toEqual('resource(package:some/module/cmp.html)');
expect(resourceLoaderSpy).toHaveBeenCalledTimes(1);
}));
});
describe('normalizeLoadedTemplate', () => {
it('should store the viewEncapsulation in the result',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const viewEncapsulation = ViewEncapsulation.Native;
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: viewEncapsulation,
styles: [],
styleUrls: []
},
'', 'package:some/module/');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
encapsulation: viewEncapsulation,
template: '',
});
expect(template.encapsulation).toBe(viewEncapsulation);
}));
@ -456,281 +241,166 @@ export function main() {
inject(
[DirectiveNormalizer, CompilerConfig],
(normalizer: DirectiveNormalizer, config: CompilerConfig) => {
const template = normalizeLoadedTemplate(normalizer, {}, '', '');
const template =
<CompileTemplateMetadata>normalizeTemplate(normalizer, {template: ''});
expect(template.preserveWhitespaces).toBe(config.preserveWhitespaces);
}));
it('should store the preserveWhitespaces=false in the result',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template =
normalizeLoadedTemplate(normalizer, {preserveWhitespaces: false}, '', '');
const template = <CompileTemplateMetadata>normalizeTemplate(
normalizer, {preserveWhitespaces: false, template: ''});
expect(template.preserveWhitespaces).toBe(false);
}));
it('should store the preserveWhitespaces=true in the result',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template =
normalizeLoadedTemplate(normalizer, {preserveWhitespaces: true}, '', '');
const template = <CompileTemplateMetadata>normalizeTemplate(
normalizer, {preserveWhitespaces: true, template: ''});
expect(template.preserveWhitespaces).toBe(true);
}));
it('should keep the template as html',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'a', 'package:some/module/');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template: 'a',
});
expect(template.template).toEqual('a');
}));
it('should collect ngContent',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<ng-content select="a"></ng-content>', 'package:some/module/');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template: '<ng-content select="a"></ng-content>',
});
expect(template.ngContentSelectors).toEqual(['a']);
}));
it('should normalize ngContent wildcard selector',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<ng-content></ng-content><ng-content select></ng-content><ng-content select="*"></ng-content>',
'package:some/module/');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template:
'<ng-content></ng-content><ng-content select></ng-content><ng-content select="*"></ng-content>',
});
expect(template.ngContentSelectors).toEqual(['*', '*', '*']);
}));
it('should collect top level styles in the template',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<style>a</style>', 'package:some/module/');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template: '<style>a</style>',
});
expect(template.styles).toEqual(['a']);
}));
it('should collect styles inside in elements',
it('should collect styles inside elements',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<div><style>a</style></div>', 'package:some/module/');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template: '<div><style>a</style></div>',
});
expect(template.styles).toEqual(['a']);
}));
it('should collect styleUrls in the template',
it('should collect styleUrls in the template and add them to the styles',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<link rel="stylesheet" href="aUrl">', 'package:some/module/');
expect(template.styleUrls).toEqual(['package:some/module/aUrl']);
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template: '<link rel="stylesheet" href="aUrl">',
});
expect(template.styles).toEqual(['resource(package:some/module/aUrl)']);
expect(template.styleUrls).toEqual([]);
}));
it('should collect styleUrls in elements',
it('should collect styleUrls in elements and add them to the styles',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<div><link rel="stylesheet" href="aUrl"></div>', 'package:some/module/');
expect(template.styleUrls).toEqual(['package:some/module/aUrl']);
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template: '<div><link rel="stylesheet" href="aUrl"></div>',
});
expect(template.styles).toEqual(['resource(package:some/module/aUrl)']);
expect(template.styleUrls).toEqual([]);
}));
it('should ignore link elements with non stylesheet rel attribute',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<link href="b" rel="a">', 'package:some/module/');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template: '<link href="b" rel="a">',
});
expect(template.styleUrls).toEqual([]);
}));
it('should ignore link elements with absolute urls but non package: scheme',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<link href="http://some/external.css" rel="stylesheet">', 'package:some/module/');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template: '<link href="http://some/external.css" rel="stylesheet">',
});
expect(template.styleUrls).toEqual([]);
}));
it('should extract @import style urls into styleAbsUrl',
it('should extract @import style urls and add them to the styles',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: ['@import "test.css";'],
styleUrls: []
},
'', 'package:some/module/id');
expect(template.styles).toEqual(['']);
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
styles: ['@import "test.css";'],
template: '',
});
expect(template.styles).toEqual(['', 'resource(package:some/module/test.css)']);
expect(template.styleUrls).toEqual([]);
}));
it('should not resolve relative urls in inline styles',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: ['.foo{background-image: url(\'double.jpg\');'],
styleUrls: []
},
'', 'package:some/module/id');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
styles: ['.foo{background-image: url(\'double.jpg\');'],
template: '',
});
expect(template.styles).toEqual(['.foo{background-image: url(\'double.jpg\');']);
}));
it('should resolve relative style urls in styleUrls',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: ['test.css']
},
'', 'package:some/module/id');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
styleUrls: ['test.css'],
template: '',
});
expect(template.styles).toEqual([]);
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
}));
it('should resolve relative style urls in styleUrls with http directive url',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_HTTP_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: ['test.css']
},
'', 'http://some/module/id');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
moduleUrl: SOME_HTTP_MODULE_URL,
styleUrls: ['test.css'],
template: '',
});
expect(template.styles).toEqual([]);
expect(template.styleUrls).toEqual(['http://some/module/test.css']);
}));
it('should normalize ViewEncapsulation.Emulated to ViewEncapsulation.None if there are no styles nor stylesheets',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: ViewEncapsulation.Emulated,
styles: [],
styleUrls: []
},
'', 'package:some/module/id');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
encapsulation: ViewEncapsulation.Emulated,
template: '',
});
expect(template.encapsulation).toEqual(ViewEncapsulation.None);
}));
it('should ignore ng-content in elements with ngNonBindable',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<div ngNonBindable><ng-content select="a"></ng-content></div>',
'package:some/module/');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template: '<div ngNonBindable><ng-content select="a"></ng-content></div>',
});
expect(template.ngContentSelectors).toEqual([]);
}));
it('should still collect <style> in elements with ngNonBindable',
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
const template = normalizeLoadedTemplate(
normalizer, {
ngModuleType: null,
componentType: SomeComp,
moduleUrl: SOME_MODULE_URL,
encapsulation: null,
styles: [],
styleUrls: []
},
'<div ngNonBindable><style>div {color:red}</style></div>', 'package:some/module/');
const template = <CompileTemplateMetadata>normalizeTemplate(normalizer, {
template: '<div ngNonBindable><style>div {color:red}</style></div>',
});
expect(template.styles).toEqual(['div {color:red}']);
}));
});
});
}
function programResourceLoaderSpy(spy: SpyResourceLoader, results: {[key: string]: string}) {
spy.spy('get').and.callFake((url: string): Promise<any> => {
const result = results[url];
if (result) {
return Promise.resolve(result);
} else {
return Promise.reject(`Unknown mock url ${url}`);
}
});
}
class SomeComp {}

View File

@ -0,0 +1,25 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as o from '../../src/output/output_ast';
export function main() {
describe('OutputAst', () => {
describe('collectExternalReferences', () => {
it('should find expressions of variable types', () => {
const ref1 = new o.ExternalReference('aModule', 'name1');
const ref2 = new o.ExternalReference('aModule', 'name2');
const stmt =
o.variable('test').set(o.NULL_EXPR).toDeclStmt(o.importType(ref1, [o.importType(
ref2) !]));
expect(o.collectExternalReferences([stmt])).toEqual([ref1, ref2]);
});
});
});
}