refactor(compiler): never create CompileDirectiveMetadata with not loaded resources (#12788)
Part of #12787
This commit is contained in:
parent
c3c0e2e2a2
commit
79383ce150
|
@ -129,10 +129,10 @@ export class CodeGenerator {
|
|||
const resolver = new compiler.CompileMetadataResolver(
|
||||
new compiler.NgModuleResolver(staticReflector),
|
||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
||||
elementSchemaRegistry, staticReflector);
|
||||
elementSchemaRegistry, normalizer, staticReflector);
|
||||
// TODO(vicb): do not pass cliOptions.i18nFormat here
|
||||
const offlineCompiler = new compiler.OfflineCompiler(
|
||||
resolver, normalizer, tmplParser, new compiler.StyleCompiler(urlResolver),
|
||||
resolver, tmplParser, new compiler.StyleCompiler(urlResolver),
|
||||
new compiler.ViewCompiler(config, elementSchemaRegistry),
|
||||
new compiler.DirectiveWrapperCompiler(
|
||||
config, expressionParser, elementSchemaRegistry, console),
|
||||
|
|
|
@ -28,50 +28,40 @@ export class Extractor {
|
|||
private options: tsc.AngularCompilerOptions, private program: ts.Program,
|
||||
public host: ts.CompilerHost, private staticReflector: StaticReflector,
|
||||
private messageBundle: compiler.MessageBundle, private reflectorHost: ReflectorHost,
|
||||
private metadataResolver: compiler.CompileMetadataResolver,
|
||||
private directiveNormalizer: compiler.DirectiveNormalizer) {}
|
||||
private metadataResolver: compiler.CompileMetadataResolver) {}
|
||||
|
||||
extract(): Promise<compiler.MessageBundle> {
|
||||
const programSymbols: StaticSymbol[] =
|
||||
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
|
||||
|
||||
const files =
|
||||
compiler.analyzeNgModules(programSymbols, {transitiveModules: true}, this.metadataResolver)
|
||||
.files;
|
||||
const errors: compiler.ParseError[] = [];
|
||||
const filePromises: Promise<any>[] = [];
|
||||
return compiler
|
||||
.analyzeNgModules(programSymbols, {transitiveModules: true}, this.metadataResolver)
|
||||
.then(({files}) => {
|
||||
const errors: compiler.ParseError[] = [];
|
||||
|
||||
files.forEach(file => {
|
||||
const cmpPromises: Promise<compiler.CompileDirectiveMetadata>[] = [];
|
||||
file.directives.forEach(directiveType => {
|
||||
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
|
||||
if (dirMeta.isComponent) {
|
||||
cmpPromises.push(this.directiveNormalizer.normalizeDirective(dirMeta).asyncResult);
|
||||
}
|
||||
});
|
||||
|
||||
if (cmpPromises.length) {
|
||||
const done =
|
||||
Promise.all(cmpPromises).then((compMetas: compiler.CompileDirectiveMetadata[]) => {
|
||||
compMetas.forEach(compMeta => {
|
||||
const html = compMeta.template.template;
|
||||
const interpolationConfig =
|
||||
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
|
||||
errors.push(...this.messageBundle.updateFromTemplate(
|
||||
html, file.srcUrl, interpolationConfig));
|
||||
});
|
||||
files.forEach(file => {
|
||||
const compMetas: compiler.CompileDirectiveMetadata[] = [];
|
||||
file.directives.forEach(directiveType => {
|
||||
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
|
||||
if (dirMeta && dirMeta.isComponent) {
|
||||
compMetas.push(dirMeta);
|
||||
}
|
||||
});
|
||||
compMetas.forEach(compMeta => {
|
||||
const html = compMeta.template.template;
|
||||
const interpolationConfig =
|
||||
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
|
||||
errors.push(
|
||||
...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig));
|
||||
});
|
||||
});
|
||||
|
||||
filePromises.push(done);
|
||||
}
|
||||
});
|
||||
if (errors.length) {
|
||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||
}
|
||||
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(errors.map(e => e.toString()).join('\n'));
|
||||
}
|
||||
|
||||
return Promise.all(filePromises).then(_ => this.messageBundle);
|
||||
return this.messageBundle;
|
||||
});
|
||||
}
|
||||
|
||||
static create(
|
||||
|
@ -98,13 +88,12 @@ export class Extractor {
|
|||
const resolver = new compiler.CompileMetadataResolver(
|
||||
new compiler.NgModuleResolver(staticReflector),
|
||||
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
||||
elementSchemaRegistry, staticReflector);
|
||||
elementSchemaRegistry, normalizer, staticReflector);
|
||||
|
||||
// TODO(vicb): implicit tags & attributes
|
||||
let messageBundle = new compiler.MessageBundle(htmlParser, [], {});
|
||||
|
||||
return new Extractor(
|
||||
options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver,
|
||||
normalizer);
|
||||
options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver);
|
||||
}
|
||||
}
|
|
@ -326,9 +326,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
|||
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
|
||||
queries?: CompileQueryMetadata[],
|
||||
viewQueries?: CompileQueryMetadata[],
|
||||
entryComponents?: CompileTypeMetadata[],
|
||||
viewDirectives?: CompileTypeMetadata[],
|
||||
viewPipes?: CompileTypeMetadata[],
|
||||
entryComponents?: CompileIdentifierMetadata[],
|
||||
template?: CompileTemplateMetadata
|
||||
} = {}): CompileDirectiveMetadata {
|
||||
var hostListeners: {[key: string]: string} = {};
|
||||
|
@ -397,7 +395,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
|||
queries: CompileQueryMetadata[];
|
||||
viewQueries: CompileQueryMetadata[];
|
||||
// Note: Need to keep types here to prevent cycles!
|
||||
entryComponents: CompileTypeMetadata[];
|
||||
entryComponents: CompileIdentifierMetadata[];
|
||||
|
||||
template: CompileTemplateMetadata;
|
||||
|
||||
|
@ -421,9 +419,7 @@ export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
|
|||
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
|
||||
queries?: CompileQueryMetadata[],
|
||||
viewQueries?: CompileQueryMetadata[],
|
||||
entryComponents?: CompileTypeMetadata[],
|
||||
viewDirectives?: CompileTypeMetadata[],
|
||||
viewPipes?: CompileTypeMetadata[],
|
||||
entryComponents?: CompileIdentifierMetadata[],
|
||||
template?: CompileTemplateMetadata,
|
||||
} = {}) {
|
||||
this.type = type;
|
||||
|
@ -506,13 +502,13 @@ export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
|
|||
*/
|
||||
export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
||||
type: CompileTypeMetadata;
|
||||
declaredDirectives: CompileDirectiveMetadata[];
|
||||
exportedDirectives: CompileDirectiveMetadata[];
|
||||
declaredPipes: CompilePipeMetadata[];
|
||||
exportedPipes: CompilePipeMetadata[];
|
||||
declaredDirectives: CompileIdentifierMetadata[];
|
||||
exportedDirectives: CompileIdentifierMetadata[];
|
||||
declaredPipes: CompileIdentifierMetadata[];
|
||||
exportedPipes: CompileIdentifierMetadata[];
|
||||
// Note: See CompileDirectiveMetadata.entryComponents why this has to be a type.
|
||||
entryComponents: CompileTypeMetadata[];
|
||||
bootstrapComponents: CompileTypeMetadata[];
|
||||
entryComponents: CompileIdentifierMetadata[];
|
||||
bootstrapComponents: CompileIdentifierMetadata[];
|
||||
providers: CompileProviderMetadata[];
|
||||
|
||||
importedModules: CompileNgModuleMetadata[];
|
||||
|
@ -529,12 +525,12 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
|||
type?: CompileTypeMetadata,
|
||||
providers?:
|
||||
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
|
||||
declaredDirectives?: CompileDirectiveMetadata[],
|
||||
exportedDirectives?: CompileDirectiveMetadata[],
|
||||
declaredPipes?: CompilePipeMetadata[],
|
||||
exportedPipes?: CompilePipeMetadata[],
|
||||
entryComponents?: CompileTypeMetadata[],
|
||||
bootstrapComponents?: CompileTypeMetadata[],
|
||||
declaredDirectives?: CompileIdentifierMetadata[],
|
||||
exportedDirectives?: CompileIdentifierMetadata[],
|
||||
declaredPipes?: CompileIdentifierMetadata[],
|
||||
exportedPipes?: CompileIdentifierMetadata[],
|
||||
entryComponents?: CompileIdentifierMetadata[],
|
||||
bootstrapComponents?: CompileIdentifierMetadata[],
|
||||
importedModules?: CompileNgModuleMetadata[],
|
||||
exportedModules?: CompileNgModuleMetadata[],
|
||||
transitiveModule?: TransitiveCompileNgModuleMetadata,
|
||||
|
@ -560,15 +556,16 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
|
|||
}
|
||||
|
||||
export class TransitiveCompileNgModuleMetadata {
|
||||
directivesSet = new Set<Type<any>>();
|
||||
pipesSet = new Set<Type<any>>();
|
||||
directivesSet = new Set<any>();
|
||||
pipesSet = new Set<any>();
|
||||
|
||||
constructor(
|
||||
public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[],
|
||||
public entryComponents: CompileTypeMetadata[], public directives: CompileDirectiveMetadata[],
|
||||
public pipes: CompilePipeMetadata[]) {
|
||||
directives.forEach(dir => this.directivesSet.add(dir.type.reference));
|
||||
pipes.forEach(pipe => this.pipesSet.add(pipe.type.reference));
|
||||
public entryComponents: CompileIdentifierMetadata[],
|
||||
public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[],
|
||||
public loadingPromises: Promise<any>[]) {
|
||||
directives.forEach(dir => this.directivesSet.add(dir.reference));
|
||||
pipes.forEach(pipe => this.pipesSet.add(pipe.reference));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injectable, ViewEncapsulation} from '@angular/core';
|
||||
import {Component, Injectable, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata';
|
||||
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata';
|
||||
import {CompilerConfig} from './config';
|
||||
import {isBlank, isPresent} from './facade/lang';
|
||||
import {isBlank, isPresent, stringify} from './facade/lang';
|
||||
import * as html from './ml_parser/ast';
|
||||
import {HtmlParser} from './ml_parser/html_parser';
|
||||
import {InterpolationConfig} from './ml_parser/interpolation_config';
|
||||
|
@ -20,6 +20,18 @@ import {PreparsedElementType, preparseElement} from './template_parser/template_
|
|||
import {UrlResolver} from './url_resolver';
|
||||
import {SyncAsyncResult} from './util';
|
||||
|
||||
export interface PrenormalizedTemplateMetadata {
|
||||
componentType: any;
|
||||
moduleUrl: string;
|
||||
template?: string;
|
||||
templateUrl?: string;
|
||||
styles?: string[];
|
||||
styleUrls?: string[];
|
||||
interpolation?: [string, string];
|
||||
encapsulation?: ViewEncapsulation;
|
||||
animations?: CompileAnimationEntryMetadata[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DirectiveNormalizer {
|
||||
private _resourceLoaderCache = new Map<string, Promise<string>>();
|
||||
|
@ -48,65 +60,56 @@ export class DirectiveNormalizer {
|
|||
return result;
|
||||
}
|
||||
|
||||
normalizeDirective(directive: CompileDirectiveMetadata):
|
||||
SyncAsyncResult<CompileDirectiveMetadata> {
|
||||
if (!directive.isComponent) {
|
||||
// For non components there is nothing to be normalized yet.
|
||||
return new SyncAsyncResult(directive, Promise.resolve(directive));
|
||||
}
|
||||
normalizeTemplate(prenormData: PrenormalizedTemplateMetadata):
|
||||
SyncAsyncResult<CompileTemplateMetadata> {
|
||||
let normalizedTemplateSync: CompileTemplateMetadata = null;
|
||||
let normalizedTemplateAsync: Promise<CompileTemplateMetadata>;
|
||||
if (isPresent(directive.template.template)) {
|
||||
normalizedTemplateSync = this.normalizeTemplateSync(directive.type, directive.template);
|
||||
if (isPresent(prenormData.template)) {
|
||||
normalizedTemplateSync = this.normalizeTemplateSync(prenormData);
|
||||
normalizedTemplateAsync = Promise.resolve(normalizedTemplateSync);
|
||||
} else if (directive.template.templateUrl) {
|
||||
normalizedTemplateAsync = this.normalizeTemplateAsync(directive.type, directive.template);
|
||||
} else if (prenormData.templateUrl) {
|
||||
normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
|
||||
} else {
|
||||
throw new Error(`No template specified for component ${directive.type.name}`);
|
||||
throw new Error(
|
||||
`No template specified for component ${stringify(prenormData.componentType)}`);
|
||||
}
|
||||
|
||||
if (normalizedTemplateSync && normalizedTemplateSync.styleUrls.length === 0) {
|
||||
// sync case
|
||||
let normalizedDirective = _cloneDirectiveWithTemplate(directive, normalizedTemplateSync);
|
||||
return new SyncAsyncResult(normalizedDirective, Promise.resolve(normalizedDirective));
|
||||
return new SyncAsyncResult(normalizedTemplateSync);
|
||||
} else {
|
||||
// async case
|
||||
return new SyncAsyncResult(
|
||||
null,
|
||||
normalizedTemplateAsync
|
||||
.then((normalizedTemplate) => this.normalizeExternalStylesheets(normalizedTemplate))
|
||||
.then(
|
||||
(normalizedTemplate) =>
|
||||
_cloneDirectiveWithTemplate(directive, normalizedTemplate)));
|
||||
null, normalizedTemplateAsync.then(
|
||||
(normalizedTemplate) => this.normalizeExternalStylesheets(normalizedTemplate)));
|
||||
}
|
||||
}
|
||||
|
||||
normalizeTemplateSync(directiveType: CompileTypeMetadata, template: CompileTemplateMetadata):
|
||||
CompileTemplateMetadata {
|
||||
return this.normalizeLoadedTemplate(
|
||||
directiveType, template, template.template, directiveType.moduleUrl);
|
||||
normalizeTemplateSync(prenomData: PrenormalizedTemplateMetadata): CompileTemplateMetadata {
|
||||
return this.normalizeLoadedTemplate(prenomData, prenomData.template, prenomData.moduleUrl);
|
||||
}
|
||||
|
||||
normalizeTemplateAsync(directiveType: CompileTypeMetadata, template: CompileTemplateMetadata):
|
||||
normalizeTemplateAsync(prenomData: PrenormalizedTemplateMetadata):
|
||||
Promise<CompileTemplateMetadata> {
|
||||
let templateUrl = this._urlResolver.resolve(directiveType.moduleUrl, template.templateUrl);
|
||||
let templateUrl = this._urlResolver.resolve(prenomData.moduleUrl, prenomData.templateUrl);
|
||||
return this._fetch(templateUrl)
|
||||
.then((value) => this.normalizeLoadedTemplate(directiveType, template, value, templateUrl));
|
||||
.then((value) => this.normalizeLoadedTemplate(prenomData, value, templateUrl));
|
||||
}
|
||||
|
||||
normalizeLoadedTemplate(
|
||||
directiveType: CompileTypeMetadata, templateMeta: CompileTemplateMetadata, template: string,
|
||||
prenomData: PrenormalizedTemplateMetadata, template: string,
|
||||
templateAbsUrl: string): CompileTemplateMetadata {
|
||||
const interpolationConfig = InterpolationConfig.fromArray(templateMeta.interpolation);
|
||||
const rootNodesAndErrors =
|
||||
this._htmlParser.parse(template, directiveType.name, false, interpolationConfig);
|
||||
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
|
||||
const rootNodesAndErrors = this._htmlParser.parse(
|
||||
template, stringify(prenomData.componentType), false, interpolationConfig);
|
||||
if (rootNodesAndErrors.errors.length > 0) {
|
||||
const errorString = rootNodesAndErrors.errors.join('\n');
|
||||
throw new Error(`Template parse errors:\n${errorString}`);
|
||||
}
|
||||
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
|
||||
styles: templateMeta.styles,
|
||||
styleUrls: templateMeta.styleUrls,
|
||||
moduleUrl: directiveType.moduleUrl
|
||||
styles: prenomData.styles,
|
||||
styleUrls: prenomData.styleUrls,
|
||||
moduleUrl: prenomData.moduleUrl
|
||||
}));
|
||||
|
||||
const visitor = new TemplatePreparseVisitor();
|
||||
|
@ -114,7 +117,7 @@ export class DirectiveNormalizer {
|
|||
const templateStyles = this.normalizeStylesheet(new CompileStylesheetMetadata(
|
||||
{styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl}));
|
||||
|
||||
let encapsulation = templateMeta.encapsulation;
|
||||
let encapsulation = prenomData.encapsulation;
|
||||
if (isBlank(encapsulation)) {
|
||||
encapsulation = this._config.defaultEncapsulation;
|
||||
}
|
||||
|
@ -131,10 +134,9 @@ export class DirectiveNormalizer {
|
|||
encapsulation,
|
||||
template,
|
||||
templateUrl: templateAbsUrl, styles, styleUrls,
|
||||
externalStylesheets: templateMeta.externalStylesheets,
|
||||
ngContentSelectors: visitor.ngContentSelectors,
|
||||
animations: templateMeta.animations,
|
||||
interpolation: templateMeta.interpolation,
|
||||
animations: prenomData.animations,
|
||||
interpolation: prenomData.interpolation,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -231,25 +233,3 @@ class TemplatePreparseVisitor implements html.Visitor {
|
|||
visitExpansion(ast: html.Expansion, context: any): any { return null; }
|
||||
visitExpansionCase(ast: html.ExpansionCase, context: any): any { return null; }
|
||||
}
|
||||
|
||||
function _cloneDirectiveWithTemplate(
|
||||
directive: CompileDirectiveMetadata,
|
||||
template: CompileTemplateMetadata): CompileDirectiveMetadata {
|
||||
return new CompileDirectiveMetadata({
|
||||
type: directive.type,
|
||||
isComponent: directive.isComponent,
|
||||
selector: directive.selector,
|
||||
exportAs: directive.exportAs,
|
||||
changeDetection: directive.changeDetection,
|
||||
inputs: directive.inputs,
|
||||
outputs: directive.outputs,
|
||||
hostListeners: directive.hostListeners,
|
||||
hostProperties: directive.hostProperties,
|
||||
hostAttributes: directive.hostAttributes,
|
||||
providers: directive.providers,
|
||||
viewProviders: directive.viewProviders,
|
||||
queries: directive.queries,
|
||||
viewQueries: directive.viewQueries,
|
||||
entryComponents: directive.entryComponents, template,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -24,6 +24,11 @@ import {splitAtColon} from './util';
|
|||
export class DirectiveResolver {
|
||||
constructor(private _reflector: ReflectorReader = reflector) {}
|
||||
|
||||
isDirective(type: Type<any>) {
|
||||
const typeMetadata = this._reflector.annotations(resolveForwardRef(type));
|
||||
return typeMetadata && typeMetadata.some(isDirectiveMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@link Directive} for a given `Type`.
|
||||
*/
|
||||
|
|
|
@ -10,17 +10,27 @@ import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata
|
|||
|
||||
import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
|
||||
import * as cpl from './compile_metadata';
|
||||
import {DirectiveNormalizer} from './directive_normalizer';
|
||||
import {DirectiveResolver} from './directive_resolver';
|
||||
import {ListWrapper} from './facade/collection';
|
||||
import {isBlank, isPresent, stringify} from './facade/lang';
|
||||
import {Identifiers, resolveIdentifierToken} from './identifiers';
|
||||
import {hasLifecycleHook} from './lifecycle_reflector';
|
||||
import {NgModuleResolver} from './ng_module_resolver';
|
||||
import {PipeResolver} from './pipe_resolver';
|
||||
import {LIFECYCLE_HOOKS_VALUES, ReflectorReader, reflector} from './private_import_core';
|
||||
import {ComponentStillLoadingError, LIFECYCLE_HOOKS_VALUES, ReflectorReader, reflector} from './private_import_core';
|
||||
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
||||
import {getUrlScheme} from './url_resolver';
|
||||
import {MODULE_SUFFIX, ValueTransformer, sanitizeIdentifier, visitValue} from './util';
|
||||
import {MODULE_SUFFIX, SyncAsyncResult, ValueTransformer, sanitizeIdentifier, visitValue} from './util';
|
||||
|
||||
|
||||
// Design notes:
|
||||
// - don't lazily create metadata:
|
||||
// For some metadata, we need to do async work sometimes,
|
||||
// so the user has to kick off this loading.
|
||||
// But we want to report errors even when the async work is
|
||||
// not required to check that the user would have been able
|
||||
// to wait correctly.
|
||||
@Injectable()
|
||||
export class CompileMetadataResolver {
|
||||
private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>();
|
||||
|
@ -33,6 +43,7 @@ export class CompileMetadataResolver {
|
|||
constructor(
|
||||
private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver,
|
||||
private _pipeResolver: PipeResolver, private _schemaRegistry: ElementSchemaRegistry,
|
||||
private _directiveNormalizer: DirectiveNormalizer,
|
||||
private _reflector: ReflectorReader = reflector) {}
|
||||
|
||||
private sanitizeTokenName(token: any): string {
|
||||
|
@ -50,11 +61,15 @@ export class CompileMetadataResolver {
|
|||
}
|
||||
|
||||
clearCacheFor(type: Type<any>) {
|
||||
const dirMeta = this._directiveCache.get(type);
|
||||
this._directiveCache.delete(type);
|
||||
this._pipeCache.delete(type);
|
||||
this._ngModuleOfTypes.delete(type);
|
||||
// Clear all of the NgModule as they contain transitive information!
|
||||
this._ngModuleCache.clear();
|
||||
if (dirMeta) {
|
||||
this._directiveNormalizer.clearCacheFor(dirMeta);
|
||||
}
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
|
@ -62,50 +77,53 @@ export class CompileMetadataResolver {
|
|||
this._pipeCache.clear();
|
||||
this._ngModuleCache.clear();
|
||||
this._ngModuleOfTypes.clear();
|
||||
this._directiveNormalizer.clearCache();
|
||||
}
|
||||
|
||||
getAnimationEntryMetadata(entry: AnimationEntryMetadata): cpl.CompileAnimationEntryMetadata {
|
||||
const defs = entry.definitions.map(def => this.getAnimationStateMetadata(def));
|
||||
const defs = entry.definitions.map(def => this._getAnimationStateMetadata(def));
|
||||
return new cpl.CompileAnimationEntryMetadata(entry.name, defs);
|
||||
}
|
||||
|
||||
getAnimationStateMetadata(value: AnimationStateMetadata): cpl.CompileAnimationStateMetadata {
|
||||
private _getAnimationStateMetadata(value: AnimationStateMetadata):
|
||||
cpl.CompileAnimationStateMetadata {
|
||||
if (value instanceof AnimationStateDeclarationMetadata) {
|
||||
const styles = this.getAnimationStyleMetadata(value.styles);
|
||||
const styles = this._getAnimationStyleMetadata(value.styles);
|
||||
return new cpl.CompileAnimationStateDeclarationMetadata(value.stateNameExpr, styles);
|
||||
}
|
||||
|
||||
if (value instanceof AnimationStateTransitionMetadata) {
|
||||
return new cpl.CompileAnimationStateTransitionMetadata(
|
||||
value.stateChangeExpr, this.getAnimationMetadata(value.steps));
|
||||
value.stateChangeExpr, this._getAnimationMetadata(value.steps));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
getAnimationStyleMetadata(value: AnimationStyleMetadata): cpl.CompileAnimationStyleMetadata {
|
||||
private _getAnimationStyleMetadata(value: AnimationStyleMetadata):
|
||||
cpl.CompileAnimationStyleMetadata {
|
||||
return new cpl.CompileAnimationStyleMetadata(value.offset, value.styles);
|
||||
}
|
||||
|
||||
getAnimationMetadata(value: AnimationMetadata): cpl.CompileAnimationMetadata {
|
||||
private _getAnimationMetadata(value: AnimationMetadata): cpl.CompileAnimationMetadata {
|
||||
if (value instanceof AnimationStyleMetadata) {
|
||||
return this.getAnimationStyleMetadata(value);
|
||||
return this._getAnimationStyleMetadata(value);
|
||||
}
|
||||
|
||||
if (value instanceof AnimationKeyframesSequenceMetadata) {
|
||||
return new cpl.CompileAnimationKeyframesSequenceMetadata(
|
||||
value.steps.map(entry => this.getAnimationStyleMetadata(entry)));
|
||||
value.steps.map(entry => this._getAnimationStyleMetadata(entry)));
|
||||
}
|
||||
|
||||
if (value instanceof AnimationAnimateMetadata) {
|
||||
const animateData =
|
||||
<cpl.CompileAnimationStyleMetadata|cpl.CompileAnimationKeyframesSequenceMetadata>this
|
||||
.getAnimationMetadata(value.styles);
|
||||
._getAnimationMetadata(value.styles);
|
||||
return new cpl.CompileAnimationAnimateMetadata(value.timings, animateData);
|
||||
}
|
||||
|
||||
if (value instanceof AnimationWithStepsMetadata) {
|
||||
const steps = value.steps.map(step => this.getAnimationMetadata(step));
|
||||
const steps = value.steps.map(step => this._getAnimationMetadata(step));
|
||||
|
||||
if (value instanceof AnimationGroupMetadata) {
|
||||
return new cpl.CompileAnimationGroupMetadata(steps);
|
||||
|
@ -116,52 +134,35 @@ export class CompileMetadataResolver {
|
|||
return null;
|
||||
}
|
||||
|
||||
getDirectiveMetadata(directiveType: any, throwIfNotFound = true): cpl.CompileDirectiveMetadata {
|
||||
private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise<any> {
|
||||
if (this._directiveCache.has(directiveType)) {
|
||||
return;
|
||||
}
|
||||
directiveType = resolveForwardRef(directiveType);
|
||||
let meta = this._directiveCache.get(directiveType);
|
||||
if (!meta) {
|
||||
const dirMeta = this._directiveResolver.resolve(directiveType, throwIfNotFound);
|
||||
if (!dirMeta) {
|
||||
return null;
|
||||
}
|
||||
let templateMeta: cpl.CompileTemplateMetadata = null;
|
||||
const dirMeta = this._directiveResolver.resolve(directiveType);
|
||||
if (!dirMeta) {
|
||||
return null;
|
||||
}
|
||||
let moduleUrl = staticTypeModuleUrl(directiveType);
|
||||
|
||||
const createDirectiveMetadata = (templateMeta: cpl.CompileTemplateMetadata) => {
|
||||
let changeDetectionStrategy: ChangeDetectionStrategy = null;
|
||||
let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
let moduleUrl = staticTypeModuleUrl(directiveType);
|
||||
let entryComponentMetadata: cpl.CompileTypeMetadata[] = [];
|
||||
let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
|
||||
let selector = dirMeta.selector;
|
||||
|
||||
if (dirMeta instanceof Component) {
|
||||
// Component
|
||||
assertArrayOfStrings('styles', dirMeta.styles);
|
||||
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
|
||||
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
|
||||
|
||||
const animations = dirMeta.animations ?
|
||||
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
|
||||
null;
|
||||
|
||||
templateMeta = new cpl.CompileTemplateMetadata({
|
||||
encapsulation: dirMeta.encapsulation,
|
||||
template: dirMeta.template,
|
||||
templateUrl: dirMeta.templateUrl,
|
||||
styles: dirMeta.styles,
|
||||
styleUrls: dirMeta.styleUrls,
|
||||
animations: animations,
|
||||
interpolation: dirMeta.interpolation
|
||||
});
|
||||
|
||||
changeDetectionStrategy = dirMeta.changeDetection;
|
||||
if (dirMeta.viewProviders) {
|
||||
viewProviders = this.getProvidersMetadata(
|
||||
viewProviders = this._getProvidersMetadata(
|
||||
dirMeta.viewProviders, entryComponentMetadata,
|
||||
`viewProviders for "${stringify(directiveType)}"`);
|
||||
}
|
||||
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
|
||||
if (dirMeta.entryComponents) {
|
||||
entryComponentMetadata =
|
||||
flattenAndDedupeArray(dirMeta.entryComponents)
|
||||
.map((type) => this.getTypeMetadata(type, staticTypeModuleUrl(type)))
|
||||
.map((type) => this._getIdentifierMetadata(type, staticTypeModuleUrl(type)))
|
||||
.concat(entryComponentMetadata);
|
||||
}
|
||||
if (!selector) {
|
||||
|
@ -176,22 +177,22 @@ export class CompileMetadataResolver {
|
|||
|
||||
let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
if (isPresent(dirMeta.providers)) {
|
||||
providers = this.getProvidersMetadata(
|
||||
providers = this._getProvidersMetadata(
|
||||
dirMeta.providers, entryComponentMetadata,
|
||||
`providers for "${stringify(directiveType)}"`);
|
||||
}
|
||||
let queries: cpl.CompileQueryMetadata[] = [];
|
||||
let viewQueries: cpl.CompileQueryMetadata[] = [];
|
||||
if (isPresent(dirMeta.queries)) {
|
||||
queries = this.getQueriesMetadata(dirMeta.queries, false, directiveType);
|
||||
viewQueries = this.getQueriesMetadata(dirMeta.queries, true, directiveType);
|
||||
queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType);
|
||||
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
|
||||
}
|
||||
|
||||
meta = cpl.CompileDirectiveMetadata.create({
|
||||
const meta = cpl.CompileDirectiveMetadata.create({
|
||||
selector: selector,
|
||||
exportAs: dirMeta.exportAs,
|
||||
isComponent: !!templateMeta,
|
||||
type: this.getTypeMetadata(directiveType, moduleUrl),
|
||||
type: this._getTypeMetadata(directiveType, moduleUrl),
|
||||
template: templateMeta,
|
||||
changeDetection: changeDetectionStrategy,
|
||||
inputs: dirMeta.inputs,
|
||||
|
@ -204,189 +205,278 @@ export class CompileMetadataResolver {
|
|||
entryComponents: entryComponentMetadata
|
||||
});
|
||||
this._directiveCache.set(directiveType, meta);
|
||||
return meta;
|
||||
};
|
||||
|
||||
if (dirMeta instanceof Component) {
|
||||
// component
|
||||
moduleUrl = componentModuleUrl(this._reflector, directiveType, dirMeta);
|
||||
assertArrayOfStrings('styles', dirMeta.styles);
|
||||
assertArrayOfStrings('styleUrls', dirMeta.styleUrls);
|
||||
assertInterpolationSymbols('interpolation', dirMeta.interpolation);
|
||||
|
||||
const animations = dirMeta.animations ?
|
||||
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
|
||||
null;
|
||||
|
||||
const templateMeta = this._directiveNormalizer.normalizeTemplate({
|
||||
componentType: directiveType,
|
||||
moduleUrl: moduleUrl,
|
||||
encapsulation: dirMeta.encapsulation,
|
||||
template: dirMeta.template,
|
||||
templateUrl: dirMeta.templateUrl,
|
||||
styles: dirMeta.styles,
|
||||
styleUrls: dirMeta.styleUrls,
|
||||
animations: animations,
|
||||
interpolation: dirMeta.interpolation
|
||||
});
|
||||
if (templateMeta.syncResult) {
|
||||
createDirectiveMetadata(templateMeta.syncResult);
|
||||
return null;
|
||||
} else {
|
||||
if (isSync) {
|
||||
throw new ComponentStillLoadingError(directiveType);
|
||||
}
|
||||
return templateMeta.asyncResult.then(createDirectiveMetadata);
|
||||
}
|
||||
} else {
|
||||
// directive
|
||||
createDirectiveMetadata(null);
|
||||
return null;
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
getNgModuleMetadata(moduleType: any, throwIfNotFound = true): cpl.CompileNgModuleMetadata {
|
||||
/**
|
||||
* Gets the metadata for the given directive.
|
||||
* This assumes `loadNgModuleMetadata` has been called first.
|
||||
*/
|
||||
getDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
|
||||
const dirMeta = this._directiveCache.get(directiveType);
|
||||
if (!dirMeta) {
|
||||
throw new Error(
|
||||
`Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringify(directiveType)}.`);
|
||||
}
|
||||
return dirMeta;
|
||||
}
|
||||
|
||||
isDirective(type: any) { return this._directiveResolver.isDirective(type); }
|
||||
|
||||
isPipe(type: any) { return this._pipeResolver.isPipe(type); }
|
||||
|
||||
/**
|
||||
* Gets the metadata for the given module.
|
||||
* This assumes `loadNgModuleMetadata` has been called first.
|
||||
*/
|
||||
getNgModuleMetadata(moduleType: any): cpl.CompileNgModuleMetadata {
|
||||
const modMeta = this._ngModuleCache.get(moduleType);
|
||||
if (!modMeta) {
|
||||
throw new Error(
|
||||
`Illegal state: getNgModuleMetadata can only be called after loadNgModuleMetadata. Module ${stringify(moduleType)}.`);
|
||||
}
|
||||
return modMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an NgModule and all of its directives. This includes loading the exported directives of
|
||||
* imported modules,
|
||||
* but not private directives of imported modules.
|
||||
*/
|
||||
loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
|
||||
{ngModule: cpl.CompileNgModuleMetadata, loading: Promise<any>} {
|
||||
const ngModule = this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
|
||||
const loading =
|
||||
ngModule ? Promise.all(ngModule.transitiveModule.loadingPromises) : Promise.resolve(null);
|
||||
return {ngModule, loading};
|
||||
}
|
||||
|
||||
private _loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
|
||||
cpl.CompileNgModuleMetadata {
|
||||
moduleType = resolveForwardRef(moduleType);
|
||||
let compileMeta = this._ngModuleCache.get(moduleType);
|
||||
if (!compileMeta) {
|
||||
const meta = this._ngModuleResolver.resolve(moduleType, throwIfNotFound);
|
||||
if (!meta) {
|
||||
return null;
|
||||
}
|
||||
const declaredDirectives: cpl.CompileDirectiveMetadata[] = [];
|
||||
const exportedDirectives: cpl.CompileDirectiveMetadata[] = [];
|
||||
const declaredPipes: cpl.CompilePipeMetadata[] = [];
|
||||
const exportedPipes: cpl.CompilePipeMetadata[] = [];
|
||||
const importedModules: cpl.CompileNgModuleMetadata[] = [];
|
||||
const exportedModules: cpl.CompileNgModuleMetadata[] = [];
|
||||
const providers: any[] = [];
|
||||
const entryComponents: cpl.CompileTypeMetadata[] = [];
|
||||
const bootstrapComponents: cpl.CompileTypeMetadata[] = [];
|
||||
const schemas: SchemaMetadata[] = [];
|
||||
|
||||
if (meta.imports) {
|
||||
flattenAndDedupeArray(meta.imports).forEach((importedType) => {
|
||||
let importedModuleType: Type<any>;
|
||||
if (isValidType(importedType)) {
|
||||
importedModuleType = importedType;
|
||||
} else if (importedType && importedType.ngModule) {
|
||||
const moduleWithProviders: ModuleWithProviders = importedType;
|
||||
importedModuleType = moduleWithProviders.ngModule;
|
||||
if (moduleWithProviders.providers) {
|
||||
providers.push(...this.getProvidersMetadata(
|
||||
moduleWithProviders.providers, entryComponents,
|
||||
`provider for the NgModule '${stringify(importedModuleType)}'`));
|
||||
}
|
||||
}
|
||||
|
||||
if (importedModuleType) {
|
||||
const importedMeta = this.getNgModuleMetadata(importedModuleType, false);
|
||||
if (importedMeta === null) {
|
||||
throw new Error(
|
||||
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`);
|
||||
}
|
||||
importedModules.push(importedMeta);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (meta.exports) {
|
||||
flattenAndDedupeArray(meta.exports).forEach((exportedType) => {
|
||||
if (!isValidType(exportedType)) {
|
||||
throw new Error(
|
||||
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`);
|
||||
}
|
||||
let exportedDirMeta: cpl.CompileDirectiveMetadata;
|
||||
let exportedPipeMeta: cpl.CompilePipeMetadata;
|
||||
let exportedModuleMeta: cpl.CompileNgModuleMetadata;
|
||||
if (exportedDirMeta = this.getDirectiveMetadata(exportedType, false)) {
|
||||
exportedDirectives.push(exportedDirMeta);
|
||||
} else if (exportedPipeMeta = this.getPipeMetadata(exportedType, false)) {
|
||||
exportedPipes.push(exportedPipeMeta);
|
||||
} else if (exportedModuleMeta = this.getNgModuleMetadata(exportedType, false)) {
|
||||
exportedModules.push(exportedModuleMeta);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected ${this._getTypeDescriptor(exportedType)} '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Note: This will be modified later, so we rely on
|
||||
// getting a new instance every time!
|
||||
const transitiveModule =
|
||||
this._getTransitiveNgModuleMetadata(importedModules, exportedModules);
|
||||
if (meta.declarations) {
|
||||
flattenAndDedupeArray(meta.declarations).forEach((declaredType) => {
|
||||
if (!isValidType(declaredType)) {
|
||||
throw new Error(
|
||||
`Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`);
|
||||
}
|
||||
let declaredDirMeta: cpl.CompileDirectiveMetadata;
|
||||
let declaredPipeMeta: cpl.CompilePipeMetadata;
|
||||
if (declaredDirMeta = this.getDirectiveMetadata(declaredType, false)) {
|
||||
this._addDirectiveToModule(
|
||||
declaredDirMeta, moduleType, transitiveModule, declaredDirectives, true);
|
||||
} else if (declaredPipeMeta = this.getPipeMetadata(declaredType, false)) {
|
||||
this._addPipeToModule(
|
||||
declaredPipeMeta, moduleType, transitiveModule, declaredPipes, true);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The providers of the module have to go last
|
||||
// so that they overwrite any other provider we already added.
|
||||
if (meta.providers) {
|
||||
providers.push(...this.getProvidersMetadata(
|
||||
meta.providers, entryComponents,
|
||||
`provider for the NgModule '${stringify(moduleType)}'`));
|
||||
}
|
||||
|
||||
if (meta.entryComponents) {
|
||||
entryComponents.push(
|
||||
...flattenAndDedupeArray(meta.entryComponents)
|
||||
.map(type => this.getTypeMetadata(type, staticTypeModuleUrl(type))));
|
||||
}
|
||||
|
||||
if (meta.bootstrap) {
|
||||
const typeMetadata = flattenAndDedupeArray(meta.bootstrap).map(type => {
|
||||
if (!isValidType(type)) {
|
||||
throw new Error(
|
||||
`Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`);
|
||||
}
|
||||
return this.getTypeMetadata(type, staticTypeModuleUrl(type));
|
||||
});
|
||||
bootstrapComponents.push(...typeMetadata);
|
||||
}
|
||||
|
||||
entryComponents.push(...bootstrapComponents);
|
||||
|
||||
if (meta.schemas) {
|
||||
schemas.push(...flattenAndDedupeArray(meta.schemas));
|
||||
}
|
||||
|
||||
transitiveModule.entryComponents.push(...entryComponents);
|
||||
transitiveModule.providers.push(...providers);
|
||||
|
||||
compileMeta = new cpl.CompileNgModuleMetadata({
|
||||
type: this.getTypeMetadata(moduleType, staticTypeModuleUrl(moduleType)),
|
||||
providers,
|
||||
entryComponents,
|
||||
bootstrapComponents,
|
||||
schemas,
|
||||
declaredDirectives,
|
||||
exportedDirectives,
|
||||
declaredPipes,
|
||||
exportedPipes,
|
||||
importedModules,
|
||||
exportedModules,
|
||||
transitiveModule,
|
||||
id: meta.id,
|
||||
});
|
||||
|
||||
transitiveModule.modules.push(compileMeta);
|
||||
this._verifyModule(compileMeta);
|
||||
this._ngModuleCache.set(moduleType, compileMeta);
|
||||
if (compileMeta) {
|
||||
return compileMeta;
|
||||
}
|
||||
const meta = this._ngModuleResolver.resolve(moduleType, throwIfNotFound);
|
||||
if (!meta) {
|
||||
return null;
|
||||
}
|
||||
const declaredDirectives: cpl.CompileIdentifierMetadata[] = [];
|
||||
const exportedDirectives: cpl.CompileIdentifierMetadata[] = [];
|
||||
const declaredPipes: cpl.CompileIdentifierMetadata[] = [];
|
||||
const exportedPipes: cpl.CompileIdentifierMetadata[] = [];
|
||||
const importedModules: cpl.CompileNgModuleMetadata[] = [];
|
||||
const exportedModules: cpl.CompileNgModuleMetadata[] = [];
|
||||
const providers: any[] = [];
|
||||
const entryComponents: cpl.CompileIdentifierMetadata[] = [];
|
||||
const bootstrapComponents: cpl.CompileIdentifierMetadata[] = [];
|
||||
const schemas: SchemaMetadata[] = [];
|
||||
|
||||
if (meta.imports) {
|
||||
flattenAndDedupeArray(meta.imports).forEach((importedType) => {
|
||||
let importedModuleType: Type<any>;
|
||||
if (isValidType(importedType)) {
|
||||
importedModuleType = importedType;
|
||||
} else if (importedType && importedType.ngModule) {
|
||||
const moduleWithProviders: ModuleWithProviders = importedType;
|
||||
importedModuleType = moduleWithProviders.ngModule;
|
||||
if (moduleWithProviders.providers) {
|
||||
providers.push(...this._getProvidersMetadata(
|
||||
moduleWithProviders.providers, entryComponents,
|
||||
`provider for the NgModule '${stringify(importedModuleType)}'`));
|
||||
}
|
||||
}
|
||||
|
||||
if (importedModuleType) {
|
||||
const importedMeta = this._loadNgModuleMetadata(importedModuleType, isSync, false);
|
||||
if (importedMeta === null) {
|
||||
throw new Error(
|
||||
`Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`);
|
||||
}
|
||||
importedModules.push(importedMeta);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (meta.exports) {
|
||||
flattenAndDedupeArray(meta.exports).forEach((exportedType) => {
|
||||
if (!isValidType(exportedType)) {
|
||||
throw new Error(
|
||||
`Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`);
|
||||
}
|
||||
let identifier =
|
||||
this._getIdentifierMetadata(exportedType, staticTypeModuleUrl(exportedType));
|
||||
let exportedModuleMeta: cpl.CompileNgModuleMetadata;
|
||||
if (this._directiveResolver.isDirective(exportedType)) {
|
||||
exportedDirectives.push(identifier);
|
||||
} else if (this._pipeResolver.isPipe(exportedType)) {
|
||||
exportedPipes.push(identifier);
|
||||
} else if (exportedModuleMeta = this._loadNgModuleMetadata(exportedType, isSync, false)) {
|
||||
exportedModules.push(exportedModuleMeta);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected ${this._getTypeDescriptor(exportedType)} '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Note: This will be modified later, so we rely on
|
||||
// getting a new instance every time!
|
||||
const transitiveModule = this._getTransitiveNgModuleMetadata(importedModules, exportedModules);
|
||||
if (meta.declarations) {
|
||||
flattenAndDedupeArray(meta.declarations).forEach((declaredType) => {
|
||||
if (!isValidType(declaredType)) {
|
||||
throw new Error(
|
||||
`Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`);
|
||||
}
|
||||
const declaredIdentifier =
|
||||
this._getIdentifierMetadata(declaredType, staticTypeModuleUrl(declaredType));
|
||||
if (this._directiveResolver.isDirective(declaredType)) {
|
||||
transitiveModule.directivesSet.add(declaredType);
|
||||
transitiveModule.directives.push(declaredIdentifier);
|
||||
declaredDirectives.push(declaredIdentifier);
|
||||
this._addTypeToModule(declaredType, moduleType);
|
||||
const loadingPromise = this._loadDirectiveMetadata(declaredType, isSync);
|
||||
if (loadingPromise) {
|
||||
transitiveModule.loadingPromises.push(loadingPromise);
|
||||
}
|
||||
} else if (this._pipeResolver.isPipe(declaredType)) {
|
||||
transitiveModule.pipesSet.add(declaredType);
|
||||
transitiveModule.pipes.push(declaredIdentifier);
|
||||
declaredPipes.push(declaredIdentifier);
|
||||
this._addTypeToModule(declaredType, moduleType);
|
||||
this._loadPipeMetadata(declaredType);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected ${this._getTypeDescriptor(declaredType)} '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The providers of the module have to go last
|
||||
// so that they overwrite any other provider we already added.
|
||||
if (meta.providers) {
|
||||
providers.push(...this._getProvidersMetadata(
|
||||
meta.providers, entryComponents, `provider for the NgModule '${stringify(moduleType)}'`));
|
||||
}
|
||||
|
||||
if (meta.entryComponents) {
|
||||
entryComponents.push(
|
||||
...flattenAndDedupeArray(meta.entryComponents)
|
||||
.map(type => this._getTypeMetadata(type, staticTypeModuleUrl(type))));
|
||||
}
|
||||
|
||||
if (meta.bootstrap) {
|
||||
const typeMetadata = flattenAndDedupeArray(meta.bootstrap).map(type => {
|
||||
if (!isValidType(type)) {
|
||||
throw new Error(
|
||||
`Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`);
|
||||
}
|
||||
return this._getTypeMetadata(type, staticTypeModuleUrl(type));
|
||||
});
|
||||
bootstrapComponents.push(...typeMetadata);
|
||||
}
|
||||
|
||||
entryComponents.push(...bootstrapComponents);
|
||||
|
||||
if (meta.schemas) {
|
||||
schemas.push(...flattenAndDedupeArray(meta.schemas));
|
||||
}
|
||||
|
||||
transitiveModule.entryComponents.push(...entryComponents);
|
||||
transitiveModule.providers.push(...providers);
|
||||
|
||||
compileMeta = new cpl.CompileNgModuleMetadata({
|
||||
type: this._getTypeMetadata(moduleType, staticTypeModuleUrl(moduleType)),
|
||||
providers,
|
||||
entryComponents,
|
||||
bootstrapComponents,
|
||||
schemas,
|
||||
declaredDirectives,
|
||||
exportedDirectives,
|
||||
declaredPipes,
|
||||
exportedPipes,
|
||||
importedModules,
|
||||
exportedModules,
|
||||
transitiveModule,
|
||||
id: meta.id,
|
||||
});
|
||||
|
||||
transitiveModule.modules.push(compileMeta);
|
||||
this._verifyModule(compileMeta);
|
||||
this._ngModuleCache.set(moduleType, compileMeta);
|
||||
return compileMeta;
|
||||
}
|
||||
|
||||
|
||||
private _verifyModule(moduleMeta: cpl.CompileNgModuleMetadata) {
|
||||
moduleMeta.exportedDirectives.forEach((dirMeta) => {
|
||||
if (!moduleMeta.transitiveModule.directivesSet.has(dirMeta.type.reference)) {
|
||||
moduleMeta.exportedDirectives.forEach((dirIdentifier) => {
|
||||
if (!moduleMeta.transitiveModule.directivesSet.has(dirIdentifier.reference)) {
|
||||
throw new Error(
|
||||
`Can't export directive ${stringify(dirMeta.type.reference)} from ${stringify(moduleMeta.type.reference)} as it was neither declared nor imported!`);
|
||||
`Can't export directive ${stringify(dirIdentifier.reference)} from ${stringify(moduleMeta.type.reference)} as it was neither declared nor imported!`);
|
||||
}
|
||||
});
|
||||
|
||||
moduleMeta.exportedPipes.forEach((pipeMeta) => {
|
||||
if (!moduleMeta.transitiveModule.pipesSet.has(pipeMeta.type.reference)) {
|
||||
moduleMeta.exportedPipes.forEach((pipeIdentifier) => {
|
||||
if (!moduleMeta.transitiveModule.pipesSet.has(pipeIdentifier.reference)) {
|
||||
throw new Error(
|
||||
`Can't export pipe ${stringify(pipeMeta.type.reference)} from ${stringify(moduleMeta.type.reference)} as it was neither declared nor imported!`);
|
||||
`Can't export pipe ${stringify(pipeIdentifier.reference)} from ${stringify(moduleMeta.type.reference)} as it was neither declared nor imported!`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _getTypeDescriptor(type: Type<any>): string {
|
||||
if (this._directiveResolver.resolve(type, false)) {
|
||||
if (this._directiveResolver.isDirective(type)) {
|
||||
return 'directive';
|
||||
}
|
||||
|
||||
if (this._pipeResolver.resolve(type, false)) {
|
||||
if (this._pipeResolver.isPipe(type)) {
|
||||
return 'pipe';
|
||||
}
|
||||
|
||||
if (this._ngModuleResolver.resolve(type, false)) {
|
||||
if (this._ngModuleResolver.isNgModule(type)) {
|
||||
return 'module';
|
||||
}
|
||||
|
||||
|
@ -397,6 +487,7 @@ export class CompileMetadataResolver {
|
|||
return 'value';
|
||||
}
|
||||
|
||||
|
||||
private _addTypeToModule(type: Type<any>, moduleType: Type<any>) {
|
||||
const oldModule = this._ngModuleOfTypes.get(type);
|
||||
if (oldModule && oldModule !== moduleType) {
|
||||
|
@ -421,81 +512,72 @@ export class CompileMetadataResolver {
|
|||
const directives =
|
||||
flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives));
|
||||
const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes));
|
||||
const loadingPromises = ListWrapper.flatten(
|
||||
transitiveExportedModules.map(ngModule => ngModule.transitiveModule.loadingPromises));
|
||||
return new cpl.TransitiveCompileNgModuleMetadata(
|
||||
transitiveModules, providers, entryComponents, directives, pipes);
|
||||
transitiveModules, providers, entryComponents, directives, pipes, loadingPromises);
|
||||
}
|
||||
|
||||
private _addDirectiveToModule(
|
||||
dirMeta: cpl.CompileDirectiveMetadata, moduleType: any,
|
||||
transitiveModule: cpl.TransitiveCompileNgModuleMetadata,
|
||||
declaredDirectives: cpl.CompileDirectiveMetadata[], force: boolean = false): boolean {
|
||||
if (force || !transitiveModule.directivesSet.has(dirMeta.type.reference)) {
|
||||
transitiveModule.directivesSet.add(dirMeta.type.reference);
|
||||
transitiveModule.directives.push(dirMeta);
|
||||
declaredDirectives.push(dirMeta);
|
||||
this._addTypeToModule(dirMeta.type.reference, moduleType);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _addPipeToModule(
|
||||
pipeMeta: cpl.CompilePipeMetadata, moduleType: any,
|
||||
transitiveModule: cpl.TransitiveCompileNgModuleMetadata,
|
||||
declaredPipes: cpl.CompilePipeMetadata[], force: boolean = false): boolean {
|
||||
if (force || !transitiveModule.pipesSet.has(pipeMeta.type.reference)) {
|
||||
transitiveModule.pipesSet.add(pipeMeta.type.reference);
|
||||
transitiveModule.pipes.push(pipeMeta);
|
||||
declaredPipes.push(pipeMeta);
|
||||
this._addTypeToModule(pipeMeta.type.reference, moduleType);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getTypeMetadata(type: Type<any>, moduleUrl: string, dependencies: any[] = null):
|
||||
cpl.CompileTypeMetadata {
|
||||
private _getIdentifierMetadata(type: Type<any>, moduleUrl: string):
|
||||
cpl.CompileIdentifierMetadata {
|
||||
type = resolveForwardRef(type);
|
||||
return new cpl.CompileIdentifierMetadata(
|
||||
{name: this.sanitizeTokenName(type), moduleUrl, reference: type});
|
||||
}
|
||||
|
||||
private _getTypeMetadata(type: Type<any>, moduleUrl: string, dependencies: any[] = null):
|
||||
cpl.CompileTypeMetadata {
|
||||
const identifier = this._getIdentifierMetadata(type, moduleUrl);
|
||||
return new cpl.CompileTypeMetadata({
|
||||
name: this.sanitizeTokenName(type),
|
||||
moduleUrl,
|
||||
reference: type,
|
||||
diDeps: this.getDependenciesMetadata(type, dependencies),
|
||||
lifecycleHooks: LIFECYCLE_HOOKS_VALUES.filter(hook => hasLifecycleHook(hook, type)),
|
||||
name: identifier.name,
|
||||
moduleUrl: identifier.moduleUrl,
|
||||
reference: identifier.reference,
|
||||
diDeps: this._getDependenciesMetadata(identifier.reference, dependencies),
|
||||
lifecycleHooks:
|
||||
LIFECYCLE_HOOKS_VALUES.filter(hook => hasLifecycleHook(hook, identifier.reference)),
|
||||
});
|
||||
}
|
||||
|
||||
getFactoryMetadata(factory: Function, moduleUrl: string, dependencies: any[] = null):
|
||||
private _getFactoryMetadata(factory: Function, moduleUrl: string, dependencies: any[] = null):
|
||||
cpl.CompileFactoryMetadata {
|
||||
factory = resolveForwardRef(factory);
|
||||
return new cpl.CompileFactoryMetadata({
|
||||
name: this.sanitizeTokenName(factory),
|
||||
moduleUrl,
|
||||
reference: factory,
|
||||
diDeps: this.getDependenciesMetadata(factory, dependencies)
|
||||
diDeps: this._getDependenciesMetadata(factory, dependencies)
|
||||
});
|
||||
}
|
||||
|
||||
getPipeMetadata(pipeType: Type<any>, throwIfNotFound = true): cpl.CompilePipeMetadata {
|
||||
pipeType = resolveForwardRef(pipeType);
|
||||
let meta = this._pipeCache.get(pipeType);
|
||||
if (!meta) {
|
||||
const pipeMeta = this._pipeResolver.resolve(pipeType, throwIfNotFound);
|
||||
if (!pipeMeta) {
|
||||
return null;
|
||||
}
|
||||
|
||||
meta = new cpl.CompilePipeMetadata({
|
||||
type: this.getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)),
|
||||
name: pipeMeta.name,
|
||||
pure: pipeMeta.pure
|
||||
});
|
||||
this._pipeCache.set(pipeType, meta);
|
||||
/**
|
||||
* Gets the metadata for the given pipe.
|
||||
* This assumes `loadNgModuleMetadata` has been called first.
|
||||
*/
|
||||
getPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
|
||||
const pipeMeta = this._pipeCache.get(pipeType);
|
||||
if (!pipeMeta) {
|
||||
throw new Error(
|
||||
`Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringify(pipeType)}.`);
|
||||
}
|
||||
return meta;
|
||||
return pipeMeta;
|
||||
}
|
||||
|
||||
getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]):
|
||||
private _loadPipeMetadata(pipeType: Type<any>): void {
|
||||
pipeType = resolveForwardRef(pipeType);
|
||||
const pipeMeta = this._pipeResolver.resolve(pipeType);
|
||||
if (!pipeMeta) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const meta = new cpl.CompilePipeMetadata({
|
||||
type: this._getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)),
|
||||
name: pipeMeta.name,
|
||||
pure: pipeMeta.pure
|
||||
});
|
||||
this._pipeCache.set(pipeType, meta);
|
||||
}
|
||||
|
||||
private _getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]):
|
||||
cpl.CompileDiDependencyMetadata[] {
|
||||
let hasUnknownDeps = false;
|
||||
let params = dependencies || this._reflector.parameters(typeOrFunc) || [];
|
||||
|
@ -540,7 +622,7 @@ export class CompileMetadataResolver {
|
|||
isSelf,
|
||||
isSkipSelf,
|
||||
isOptional,
|
||||
token: this.getTokenMetadata(token)
|
||||
token: this._getTokenMetadata(token)
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -555,7 +637,7 @@ export class CompileMetadataResolver {
|
|||
return dependenciesMetadata;
|
||||
}
|
||||
|
||||
getTokenMetadata(token: any): cpl.CompileTokenMetadata {
|
||||
private _getTokenMetadata(token: any): cpl.CompileTokenMetadata {
|
||||
token = resolveForwardRef(token);
|
||||
let compileToken: cpl.CompileTokenMetadata;
|
||||
if (typeof token === 'string') {
|
||||
|
@ -572,8 +654,8 @@ export class CompileMetadataResolver {
|
|||
return compileToken;
|
||||
}
|
||||
|
||||
getProvidersMetadata(
|
||||
providers: Provider[], targetEntryComponents: cpl.CompileTypeMetadata[],
|
||||
private _getProvidersMetadata(
|
||||
providers: Provider[], targetEntryComponents: cpl.CompileIdentifierMetadata[],
|
||||
debugInfo?: string): Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> {
|
||||
const compileProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
|
||||
providers.forEach((provider: any, providerIdx: number) => {
|
||||
|
@ -583,9 +665,9 @@ export class CompileMetadataResolver {
|
|||
}
|
||||
let compileProvider: cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[];
|
||||
if (Array.isArray(provider)) {
|
||||
compileProvider = this.getProvidersMetadata(provider, targetEntryComponents, debugInfo);
|
||||
compileProvider = this._getProvidersMetadata(provider, targetEntryComponents, debugInfo);
|
||||
} else if (provider instanceof cpl.ProviderMeta) {
|
||||
let tokenMeta = this.getTokenMetadata(provider.token);
|
||||
let tokenMeta = this._getTokenMetadata(provider.token);
|
||||
if (tokenMeta.reference ===
|
||||
resolveIdentifierToken(Identifiers.ANALYZE_FOR_ENTRY_COMPONENTS).reference) {
|
||||
targetEntryComponents.push(...this._getEntryComponentsFromProvider(provider));
|
||||
|
@ -593,7 +675,7 @@ export class CompileMetadataResolver {
|
|||
compileProvider = this.getProviderMetadata(provider);
|
||||
}
|
||||
} else if (isValidType(provider)) {
|
||||
compileProvider = this.getTypeMetadata(provider, staticTypeModuleUrl(provider));
|
||||
compileProvider = this._getTypeMetadata(provider, staticTypeModuleUrl(provider));
|
||||
} else {
|
||||
const providersInfo =
|
||||
(<string[]>providers.reduce(
|
||||
|
@ -620,8 +702,9 @@ export class CompileMetadataResolver {
|
|||
return compileProviders;
|
||||
}
|
||||
|
||||
private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta): cpl.CompileTypeMetadata[] {
|
||||
const components: cpl.CompileTypeMetadata[] = [];
|
||||
private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta):
|
||||
cpl.CompileIdentifierMetadata[] {
|
||||
const components: cpl.CompileIdentifierMetadata[] = [];
|
||||
const collectedIdentifiers: cpl.CompileIdentifierMetadata[] = [];
|
||||
|
||||
if (provider.useFactory || provider.useExisting || provider.useClass) {
|
||||
|
@ -634,9 +717,8 @@ export class CompileMetadataResolver {
|
|||
|
||||
convertToCompileValue(provider.useValue, collectedIdentifiers);
|
||||
collectedIdentifiers.forEach((identifier) => {
|
||||
const dirMeta = this.getDirectiveMetadata(identifier.reference, false);
|
||||
if (dirMeta) {
|
||||
components.push(dirMeta.type);
|
||||
if (this._directiveResolver.isDirective(identifier.reference)) {
|
||||
components.push(identifier);
|
||||
}
|
||||
});
|
||||
return components;
|
||||
|
@ -648,27 +730,27 @@ export class CompileMetadataResolver {
|
|||
let compileFactoryMetadata: cpl.CompileFactoryMetadata = null;
|
||||
|
||||
if (provider.useClass) {
|
||||
compileTypeMetadata = this.getTypeMetadata(
|
||||
compileTypeMetadata = this._getTypeMetadata(
|
||||
provider.useClass, staticTypeModuleUrl(provider.useClass), provider.dependencies);
|
||||
compileDeps = compileTypeMetadata.diDeps;
|
||||
} else if (provider.useFactory) {
|
||||
compileFactoryMetadata = this.getFactoryMetadata(
|
||||
compileFactoryMetadata = this._getFactoryMetadata(
|
||||
provider.useFactory, staticTypeModuleUrl(provider.useFactory), provider.dependencies);
|
||||
compileDeps = compileFactoryMetadata.diDeps;
|
||||
}
|
||||
|
||||
return new cpl.CompileProviderMetadata({
|
||||
token: this.getTokenMetadata(provider.token),
|
||||
token: this._getTokenMetadata(provider.token),
|
||||
useClass: compileTypeMetadata,
|
||||
useValue: convertToCompileValue(provider.useValue, []),
|
||||
useFactory: compileFactoryMetadata,
|
||||
useExisting: provider.useExisting ? this.getTokenMetadata(provider.useExisting) : null,
|
||||
useExisting: provider.useExisting ? this._getTokenMetadata(provider.useExisting) : null,
|
||||
deps: compileDeps,
|
||||
multi: provider.multi
|
||||
});
|
||||
}
|
||||
|
||||
getQueriesMetadata(
|
||||
private _getQueriesMetadata(
|
||||
queries: {[key: string]: Query}, isViewQuery: boolean,
|
||||
directiveType: Type<any>): cpl.CompileQueryMetadata[] {
|
||||
const res: cpl.CompileQueryMetadata[] = [];
|
||||
|
@ -676,7 +758,7 @@ export class CompileMetadataResolver {
|
|||
Object.keys(queries).forEach((propertyName: string) => {
|
||||
const query = queries[propertyName];
|
||||
if (query.isViewQuery === isViewQuery) {
|
||||
res.push(this.getQueryMetadata(query, propertyName, directiveType));
|
||||
res.push(this._getQueryMetadata(query, propertyName, directiveType));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -685,24 +767,25 @@ export class CompileMetadataResolver {
|
|||
|
||||
private _queryVarBindings(selector: any): string[] { return selector.split(/\s*,\s*/); }
|
||||
|
||||
getQueryMetadata(q: Query, propertyName: string, typeOrFunc: Type<any>|Function):
|
||||
private _getQueryMetadata(q: Query, propertyName: string, typeOrFunc: Type<any>|Function):
|
||||
cpl.CompileQueryMetadata {
|
||||
var selectors: cpl.CompileTokenMetadata[];
|
||||
if (typeof q.selector === 'string') {
|
||||
selectors = this._queryVarBindings(q.selector).map(varName => this.getTokenMetadata(varName));
|
||||
selectors =
|
||||
this._queryVarBindings(q.selector).map(varName => this._getTokenMetadata(varName));
|
||||
} else {
|
||||
if (!q.selector) {
|
||||
throw new Error(
|
||||
`Can't construct a query for the property "${propertyName}" of "${stringify(typeOrFunc)}" since the query selector wasn't defined.`);
|
||||
}
|
||||
selectors = [this.getTokenMetadata(q.selector)];
|
||||
selectors = [this._getTokenMetadata(q.selector)];
|
||||
}
|
||||
|
||||
return new cpl.CompileQueryMetadata({
|
||||
selectors,
|
||||
first: q.first,
|
||||
descendants: q.descendants, propertyName,
|
||||
read: q.read ? this.getTokenMetadata(q.read) : null
|
||||
read: q.read ? this._getTokenMetadata(q.read) : null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ function _isNgModuleMetadata(obj: any): obj is NgModule {
|
|||
export class NgModuleResolver {
|
||||
constructor(private _reflector: ReflectorReader = reflector) {}
|
||||
|
||||
isNgModule(type: any) { return this._reflector.annotations(type).some(_isNgModuleMetadata); }
|
||||
|
||||
resolve(type: Type<any>, throwIfNotFound = true): NgModule {
|
||||
const ngModuleMeta: NgModule = this._reflector.annotations(type).find(_isNgModuleMetadata);
|
||||
|
||||
|
|
|
@ -30,28 +30,16 @@ export class SourceModule {
|
|||
// Returns all the source files and a mapping from modules to directives
|
||||
export function analyzeNgModules(
|
||||
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
|
||||
metadataResolver: CompileMetadataResolver): {
|
||||
metadataResolver: CompileMetadataResolver): Promise<{
|
||||
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>
|
||||
} {
|
||||
const {
|
||||
ngModules: programNgModules,
|
||||
pipesAndDirectives: programPipesOrDirectives,
|
||||
} = _extractModulesAndPipesOrDirectives(programStaticSymbols, metadataResolver);
|
||||
}> {
|
||||
return _loadNgModules(programStaticSymbols, options, metadataResolver).then(_analyzeNgModules);
|
||||
}
|
||||
|
||||
function _analyzeNgModules(ngModuleMetas: CompileNgModuleMetadata[]) {
|
||||
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
|
||||
|
||||
programNgModules.forEach(modMeta => {
|
||||
if (options.transitiveModules) {
|
||||
// For every input modules add the list of transitively included modules
|
||||
modMeta.transitiveModule.modules.forEach(
|
||||
modMeta => { moduleMetasByRef.set(modMeta.type.reference, modMeta); });
|
||||
} else {
|
||||
moduleMetasByRef.set(modMeta.type.reference, modMeta);
|
||||
}
|
||||
});
|
||||
|
||||
const ngModuleMetas = Array.from(moduleMetasByRef.values());
|
||||
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[]>();
|
||||
|
@ -67,31 +55,20 @@ export function analyzeNgModules(
|
|||
ngModulesByFile.set(
|
||||
srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference));
|
||||
|
||||
ngModuleMeta.declaredDirectives.forEach((dirMeta: CompileDirectiveMetadata) => {
|
||||
const fileUrl = dirMeta.type.reference.filePath;
|
||||
ngModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
|
||||
const fileUrl = dirIdentifier.reference.filePath;
|
||||
filePaths.add(fileUrl);
|
||||
ngDirectivesByFile.set(
|
||||
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirMeta.type.reference));
|
||||
ngModuleByPipeOrDirective.set(dirMeta.type.reference, ngModuleMeta);
|
||||
fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirIdentifier.reference));
|
||||
ngModuleByPipeOrDirective.set(dirIdentifier.reference, ngModuleMeta);
|
||||
});
|
||||
|
||||
ngModuleMeta.declaredPipes.forEach((pipeMeta: CompilePipeMetadata) => {
|
||||
const fileUrl = pipeMeta.type.reference.filePath;
|
||||
ngModuleMeta.declaredPipes.forEach((pipeIdentifier) => {
|
||||
const fileUrl = pipeIdentifier.reference.filePath;
|
||||
filePaths.add(fileUrl);
|
||||
ngModuleByPipeOrDirective.set(pipeMeta.type.reference, ngModuleMeta);
|
||||
ngModuleByPipeOrDirective.set(pipeIdentifier.reference, ngModuleMeta);
|
||||
});
|
||||
});
|
||||
|
||||
// Throw an error if any of the program pipe or directives is not declared by a module
|
||||
const symbolsMissingModule =
|
||||
programPipesOrDirectives.filter(s => !ngModuleByPipeOrDirective.has(s));
|
||||
|
||||
if (symbolsMissingModule.length) {
|
||||
const messages = symbolsMissingModule.map(
|
||||
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
|
||||
throw new Error(messages.join('\n'));
|
||||
}
|
||||
|
||||
const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = [];
|
||||
|
||||
filePaths.forEach((srcUrl) => {
|
||||
|
@ -112,35 +89,29 @@ export class OfflineCompiler {
|
|||
private _animationCompiler = new AnimationCompiler();
|
||||
|
||||
constructor(
|
||||
private _metadataResolver: CompileMetadataResolver,
|
||||
private _directiveNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser,
|
||||
private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser,
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _dirWrapperCompiler: DirectiveWrapperCompiler,
|
||||
private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter,
|
||||
private _localeId: string, private _translationFormat: string,
|
||||
private _animationParser: AnimationParser) {}
|
||||
|
||||
clearCache() {
|
||||
this._directiveNormalizer.clearCache();
|
||||
this._metadataResolver.clearCache();
|
||||
}
|
||||
clearCache() { this._metadataResolver.clearCache(); }
|
||||
|
||||
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}):
|
||||
Promise<SourceModule[]> {
|
||||
const {ngModuleByPipeOrDirective, files} =
|
||||
analyzeNgModules(staticSymbols, options, this._metadataResolver);
|
||||
|
||||
const sourceModules = files.map(
|
||||
file => this._compileSrcFile(
|
||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
|
||||
|
||||
return Promise.all(sourceModules)
|
||||
.then((modules: SourceModule[][]) => ListWrapper.flatten(modules));
|
||||
return analyzeNgModules(staticSymbols, options, this._metadataResolver)
|
||||
.then(({ngModuleByPipeOrDirective, files}) => {
|
||||
const sourceModules = files.map(
|
||||
file => this._compileSrcFile(
|
||||
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
|
||||
return ListWrapper.flatten(sourceModules);
|
||||
});
|
||||
}
|
||||
|
||||
private _compileSrcFile(
|
||||
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
directives: StaticSymbol[], ngModules: StaticSymbol[]): Promise<SourceModule[]> {
|
||||
directives: StaticSymbol[], ngModules: StaticSymbol[]): SourceModule[] {
|
||||
const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1];
|
||||
const statements: o.Statement[] = [];
|
||||
const exportedVars: string[] = [];
|
||||
|
@ -155,48 +126,38 @@ export class OfflineCompiler {
|
|||
(directiveType) => this._compileDirectiveWrapper(directiveType, statements)));
|
||||
|
||||
// compile components
|
||||
return Promise
|
||||
.all(directives.map((dirType) => {
|
||||
const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType);
|
||||
if (!compMeta.isComponent) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
const ngModule = ngModuleByPipeOrDirective.get(dirType);
|
||||
if (!ngModule) {
|
||||
throw new Error(
|
||||
`Internal Error: cannot determine the module for component ${compMeta.type.name}!`);
|
||||
}
|
||||
directives.forEach((dirType) => {
|
||||
const compMeta = this._metadataResolver.getDirectiveMetadata(<any>dirType);
|
||||
if (!compMeta.isComponent) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
const ngModule = ngModuleByPipeOrDirective.get(dirType);
|
||||
if (!ngModule) {
|
||||
throw new Error(
|
||||
`Internal Error: cannot determine the module for component ${compMeta.type.name}!`);
|
||||
}
|
||||
|
||||
return Promise
|
||||
.all([compMeta, ...ngModule.transitiveModule.directives].map(
|
||||
dirMeta => this._directiveNormalizer.normalizeDirective(dirMeta).asyncResult))
|
||||
.then((normalizedCompWithDirectives) => {
|
||||
const [compMeta, ...dirMetas] = normalizedCompWithDirectives;
|
||||
_assertComponent(compMeta);
|
||||
_assertComponent(compMeta);
|
||||
|
||||
// compile styles
|
||||
const stylesCompileResults = this._styleCompiler.compileComponent(compMeta);
|
||||
stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => {
|
||||
outputSourceModules.push(
|
||||
this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
|
||||
});
|
||||
// compile styles
|
||||
const stylesCompileResults = this._styleCompiler.compileComponent(compMeta);
|
||||
stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => {
|
||||
outputSourceModules.push(this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix));
|
||||
});
|
||||
|
||||
// compile components
|
||||
exportedVars.push(
|
||||
this._compileComponentFactory(compMeta, fileSuffix, statements),
|
||||
this._compileComponent(
|
||||
compMeta, dirMetas, ngModule.transitiveModule.pipes, ngModule.schemas,
|
||||
stylesCompileResults.componentStylesheet, fileSuffix, statements));
|
||||
});
|
||||
}))
|
||||
.then(() => {
|
||||
if (statements.length > 0) {
|
||||
const srcModule = this._codegenSourceModule(
|
||||
srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars);
|
||||
outputSourceModules.unshift(srcModule);
|
||||
}
|
||||
return outputSourceModules;
|
||||
});
|
||||
// compile components
|
||||
exportedVars.push(
|
||||
this._compileComponentFactory(compMeta, ngModule, fileSuffix, statements),
|
||||
this._compileComponent(
|
||||
compMeta, ngModule, ngModule.transitiveModule.directives,
|
||||
stylesCompileResults.componentStylesheet, fileSuffix, statements));
|
||||
});
|
||||
if (statements.length > 0) {
|
||||
const srcModule = this._codegenSourceModule(
|
||||
srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars);
|
||||
outputSourceModules.unshift(srcModule);
|
||||
}
|
||||
return outputSourceModules;
|
||||
}
|
||||
|
||||
private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string {
|
||||
|
@ -238,11 +199,11 @@ export class OfflineCompiler {
|
|||
}
|
||||
|
||||
private _compileComponentFactory(
|
||||
compMeta: CompileDirectiveMetadata, fileSuffix: string,
|
||||
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
|
||||
targetStatements: o.Statement[]): string {
|
||||
const hostMeta = createHostComponentMeta(compMeta);
|
||||
const hostViewFactoryVar =
|
||||
this._compileComponent(hostMeta, [compMeta], [], [], null, fileSuffix, targetStatements);
|
||||
const hostViewFactoryVar = this._compileComponent(
|
||||
hostMeta, ngModule, [compMeta.type], null, fileSuffix, targetStatements);
|
||||
const compFactoryVar = _componentFactoryName(compMeta.type);
|
||||
targetStatements.push(
|
||||
o.variable(compFactoryVar)
|
||||
|
@ -262,12 +223,18 @@ export class OfflineCompiler {
|
|||
}
|
||||
|
||||
private _compileComponent(
|
||||
compMeta: CompileDirectiveMetadata, directives: CompileDirectiveMetadata[],
|
||||
pipes: CompilePipeMetadata[], schemas: SchemaMetadata[], componentStyles: CompiledStylesheet,
|
||||
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata,
|
||||
directiveIdentifiers: CompileIdentifierMetadata[], componentStyles: CompiledStylesheet,
|
||||
fileSuffix: string, targetStatements: o.Statement[]): string {
|
||||
const parsedAnimations = this._animationParser.parseComponent(compMeta);
|
||||
const directives =
|
||||
directiveIdentifiers.map(dir => this._metadataResolver.getDirectiveMetadata(dir.reference));
|
||||
const pipes = ngModule.transitiveModule.pipes.map(
|
||||
pipe => this._metadataResolver.getPipeMetadata(pipe.reference));
|
||||
|
||||
const parsedTemplate = this._templateParser.parse(
|
||||
compMeta, compMeta.template.template, directives, pipes, schemas, compMeta.type.name);
|
||||
compMeta, compMeta.template.template, directives, pipes, ngModule.schemas,
|
||||
compMeta.type.name);
|
||||
const stylesExpr = componentStyles ? o.variable(componentStyles.stylesVar) : o.literalArr([]);
|
||||
const compiledAnimations =
|
||||
this._animationCompiler.compile(compMeta.type.name, parsedAnimations);
|
||||
|
@ -358,27 +325,50 @@ function _splitTypescriptSuffix(path: string): string[] {
|
|||
return [path, ''];
|
||||
}
|
||||
|
||||
// Group the symbols by types:
|
||||
// - NgModules,
|
||||
// - Pipes and Directives.
|
||||
function _extractModulesAndPipesOrDirectives(
|
||||
programStaticSymbols: StaticSymbol[], metadataResolver: CompileMetadataResolver) {
|
||||
const ngModules: CompileNgModuleMetadata[] = [];
|
||||
const pipesAndDirectives: StaticSymbol[] = [];
|
||||
|
||||
programStaticSymbols.forEach(staticSymbol => {
|
||||
const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false);
|
||||
const directive = metadataResolver.getDirectiveMetadata(staticSymbol, false);
|
||||
const pipe = metadataResolver.getPipeMetadata(<any>staticSymbol, false);
|
||||
// Load the NgModules and check
|
||||
// that all directives / pipes that are present in the program
|
||||
// are also declared by a module.
|
||||
function _loadNgModules(
|
||||
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
|
||||
metadataResolver: CompileMetadataResolver): Promise<CompileNgModuleMetadata[]> {
|
||||
const ngModules = new Map<any, CompileNgModuleMetadata>();
|
||||
const programPipesAndDirectives: StaticSymbol[] = [];
|
||||
const ngModulePipesAndDirective = new Set<StaticSymbol>();
|
||||
const loadingPromises: Promise<any>[] = [];
|
||||
|
||||
const addNgModule = (staticSymbol: any) => {
|
||||
if (ngModules.has(staticSymbol)) {
|
||||
return false;
|
||||
}
|
||||
const {ngModule, loading} = metadataResolver.loadNgModuleMetadata(staticSymbol, false, false);
|
||||
if (ngModule) {
|
||||
ngModules.push(ngModule);
|
||||
} else if (directive) {
|
||||
pipesAndDirectives.push(staticSymbol);
|
||||
} else if (pipe) {
|
||||
pipesAndDirectives.push(staticSymbol);
|
||||
ngModules.set(ngModule.type.reference, ngModule);
|
||||
loadingPromises.push(loading);
|
||||
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
|
||||
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
|
||||
if (options.transitiveModules) {
|
||||
// For every input modules add the list of transitively included modules
|
||||
ngModule.transitiveModule.modules.forEach(modMeta => addNgModule(modMeta.type.reference));
|
||||
}
|
||||
}
|
||||
return !!ngModule;
|
||||
};
|
||||
programStaticSymbols.forEach((staticSymbol) => {
|
||||
if (!addNgModule(staticSymbol) &&
|
||||
(metadataResolver.isDirective(staticSymbol) || metadataResolver.isPipe(staticSymbol))) {
|
||||
programPipesAndDirectives.push(staticSymbol);
|
||||
}
|
||||
});
|
||||
|
||||
return {ngModules, pipesAndDirectives};
|
||||
// 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));
|
||||
|
||||
if (symbolsMissingModule.length) {
|
||||
const messages = symbolsMissingModule.map(
|
||||
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
|
||||
throw new Error(messages.join('\n'));
|
||||
}
|
||||
|
||||
return Promise.all(loadingPromises).then(() => Array.from(ngModules.values()));
|
||||
}
|
||||
|
|
|
@ -26,6 +26,11 @@ function _isPipeMetadata(type: any): boolean {
|
|||
export class PipeResolver {
|
||||
constructor(private _reflector: ReflectorReader = reflector) {}
|
||||
|
||||
isPipe(type: Type<any>) {
|
||||
const typeMetadata = this._reflector.annotations(resolveForwardRef(type));
|
||||
return typeMetadata && typeMetadata.some(_isPipeMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@link Pipe} for a given `Type`.
|
||||
*/
|
||||
|
|
|
@ -20,7 +20,6 @@ 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 {ComponentStillLoadingError} from './private_import_core';
|
||||
import {CompiledStylesheet, StyleCompiler} from './style_compiler';
|
||||
import {TemplateParser} from './template_parser/template_parser';
|
||||
import {SyncAsyncResult} from './util';
|
||||
|
@ -47,9 +46,8 @@ export class RuntimeCompiler implements Compiler {
|
|||
|
||||
constructor(
|
||||
private _injector: Injector, private _metadataResolver: CompileMetadataResolver,
|
||||
private _templateNormalizer: DirectiveNormalizer, private _templateParser: TemplateParser,
|
||||
private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler,
|
||||
private _ngModuleCompiler: NgModuleCompiler,
|
||||
private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler,
|
||||
private _viewCompiler: ViewCompiler, private _ngModuleCompiler: NgModuleCompiler,
|
||||
private _directiveWrapperCompiler: DirectiveWrapperCompiler,
|
||||
private _compilerConfig: CompilerConfig, private _animationParser: AnimationParser) {}
|
||||
|
||||
|
@ -74,38 +72,47 @@ export class RuntimeCompiler implements Compiler {
|
|||
|
||||
private _compileModuleAndComponents<T>(moduleType: Type<T>, isSync: boolean):
|
||||
SyncAsyncResult<NgModuleFactory<T>> {
|
||||
const componentPromise = this._compileComponents(moduleType, isSync);
|
||||
const ngModuleFactory = this._compileModule(moduleType);
|
||||
return new SyncAsyncResult(ngModuleFactory, componentPromise.then(() => ngModuleFactory));
|
||||
const loadingPromise = this._loadModules(moduleType, isSync);
|
||||
const createResult = () => {
|
||||
this._compileComponents(moduleType, null);
|
||||
return this._compileModule(moduleType);
|
||||
};
|
||||
if (isSync) {
|
||||
return new SyncAsyncResult(createResult());
|
||||
} else {
|
||||
return new SyncAsyncResult(null, loadingPromise.then(createResult));
|
||||
}
|
||||
}
|
||||
|
||||
private _compileModuleAndAllComponents<T>(moduleType: Type<T>, isSync: boolean):
|
||||
SyncAsyncResult<ModuleWithComponentFactories<T>> {
|
||||
const componentPromise = this._compileComponents(moduleType, isSync);
|
||||
const ngModuleFactory = this._compileModule(moduleType);
|
||||
const moduleMeta = this._metadataResolver.getNgModuleMetadata(moduleType);
|
||||
const componentFactories: ComponentFactory<any>[] = [];
|
||||
const templates = new Set<CompiledTemplate>();
|
||||
moduleMeta.transitiveModule.modules.forEach((localModuleMeta) => {
|
||||
localModuleMeta.declaredDirectives.forEach((dirMeta) => {
|
||||
if (dirMeta.isComponent) {
|
||||
const template =
|
||||
this._createCompiledHostTemplate(dirMeta.type.reference, localModuleMeta);
|
||||
templates.add(template);
|
||||
componentFactories.push(template.proxyComponentFactory);
|
||||
}
|
||||
});
|
||||
});
|
||||
const syncResult = new ModuleWithComponentFactories(ngModuleFactory, componentFactories);
|
||||
// Note: host components themselves can always be compiled synchronously as they have an
|
||||
// inline template. However, we still need to wait for the components that they
|
||||
// reference to be loaded / compiled.
|
||||
const compile = () => {
|
||||
templates.forEach((template) => { this._compileTemplate(template); });
|
||||
return syncResult;
|
||||
const loadingPromise = this._loadModules(moduleType, isSync);
|
||||
const createResult = () => {
|
||||
const componentFactories: ComponentFactory<any>[] = [];
|
||||
this._compileComponents(moduleType, componentFactories);
|
||||
return new ModuleWithComponentFactories(this._compileModule(moduleType), componentFactories);
|
||||
};
|
||||
const asyncResult = isSync ? Promise.resolve(compile()) : componentPromise.then(compile);
|
||||
return new SyncAsyncResult(syncResult, asyncResult);
|
||||
if (isSync) {
|
||||
return new SyncAsyncResult(createResult());
|
||||
} else {
|
||||
return new SyncAsyncResult(null, loadingPromise.then(createResult));
|
||||
}
|
||||
}
|
||||
|
||||
private _loadModules(mainModule: any, isSync: boolean): Promise<any> {
|
||||
var loadingPromises: Promise<any>[] = [];
|
||||
const {ngModule, loading} = this._metadataResolver.loadNgModuleMetadata(mainModule, isSync);
|
||||
loadingPromises.push(loading);
|
||||
// Note: the loadingPromise for a module only includes the loading of the exported directives
|
||||
// of imported modules.
|
||||
// However, for runtime compilation, we want to transitively compile all modules,
|
||||
// so we also need to call loadNgModuleMetadata for all nested modules.
|
||||
ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
|
||||
loadingPromises.push(
|
||||
this._metadataResolver.loadNgModuleMetadata(localModuleMeta.type.reference, isSync)
|
||||
.loading);
|
||||
});
|
||||
return Promise.all(loadingPromises);
|
||||
}
|
||||
|
||||
private _compileModule<T>(moduleType: Type<T>): NgModuleFactory<T> {
|
||||
|
@ -137,23 +144,30 @@ export class RuntimeCompiler implements Compiler {
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
_compileComponents(mainModule: Type<any>, isSync: boolean): Promise<any> {
|
||||
const templates = new Set<CompiledTemplate>();
|
||||
var loadingPromises: Promise<any>[] = [];
|
||||
|
||||
_compileComponents(mainModule: Type<any>, allComponentFactories: ComponentFactory<any>[]) {
|
||||
const ngModule = this._metadataResolver.getNgModuleMetadata(mainModule);
|
||||
const moduleByDirective = new Map<any, CompileNgModuleMetadata>();
|
||||
const templates = new Set<CompiledTemplate>();
|
||||
|
||||
ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
|
||||
localModuleMeta.declaredDirectives.forEach((dirMeta) => {
|
||||
moduleByDirective.set(dirMeta.type.reference, localModuleMeta);
|
||||
localModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
|
||||
moduleByDirective.set(dirIdentifier.reference, localModuleMeta);
|
||||
const dirMeta = this._metadataResolver.getDirectiveMetadata(dirIdentifier.reference);
|
||||
this._compileDirectiveWrapper(dirMeta, localModuleMeta);
|
||||
if (dirMeta.isComponent) {
|
||||
templates.add(this._createCompiledTemplate(dirMeta, localModuleMeta));
|
||||
if (allComponentFactories) {
|
||||
const template =
|
||||
this._createCompiledHostTemplate(dirMeta.type.reference, localModuleMeta);
|
||||
templates.add(template);
|
||||
allComponentFactories.push(template.proxyComponentFactory);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
ngModule.transitiveModule.modules.forEach((localModuleMeta) => {
|
||||
localModuleMeta.declaredDirectives.forEach((dirMeta) => {
|
||||
localModuleMeta.declaredDirectives.forEach((dirIdentifier) => {
|
||||
const dirMeta = this._metadataResolver.getDirectiveMetadata(dirIdentifier.reference);
|
||||
if (dirMeta.isComponent) {
|
||||
dirMeta.entryComponents.forEach((entryComponentType) => {
|
||||
const moduleMeta = moduleByDirective.get(entryComponentType.reference);
|
||||
|
@ -167,24 +181,7 @@ export class RuntimeCompiler implements Compiler {
|
|||
templates.add(this._createCompiledHostTemplate(entryComponentType.reference, moduleMeta));
|
||||
});
|
||||
});
|
||||
|
||||
templates.forEach((template) => {
|
||||
if (template.loading) {
|
||||
if (isSync) {
|
||||
throw new ComponentStillLoadingError(template.compType.reference);
|
||||
} else {
|
||||
loadingPromises.push(template.loading);
|
||||
}
|
||||
}
|
||||
});
|
||||
const compile =
|
||||
() => { templates.forEach((template) => { this._compileTemplate(template); }); };
|
||||
if (isSync) {
|
||||
compile();
|
||||
return Promise.resolve(null);
|
||||
} else {
|
||||
return Promise.all(loadingPromises).then(compile);
|
||||
}
|
||||
templates.forEach((template) => this._compileTemplate(template));
|
||||
}
|
||||
|
||||
clearCacheFor(type: Type<any>) {
|
||||
|
@ -193,7 +190,6 @@ export class RuntimeCompiler implements Compiler {
|
|||
this._compiledHostTemplateCache.delete(type);
|
||||
var compiledTemplate = this._compiledTemplateCache.get(type);
|
||||
if (compiledTemplate) {
|
||||
this._templateNormalizer.clearCacheFor(compiledTemplate.normalizedCompMeta);
|
||||
this._compiledTemplateCache.delete(type);
|
||||
}
|
||||
}
|
||||
|
@ -202,7 +198,6 @@ export class RuntimeCompiler implements Compiler {
|
|||
this._metadataResolver.clearCache();
|
||||
this._compiledTemplateCache.clear();
|
||||
this._compiledHostTemplateCache.clear();
|
||||
this._templateNormalizer.clearCache();
|
||||
this._compiledNgModuleCache.clear();
|
||||
}
|
||||
|
||||
|
@ -218,8 +213,7 @@ export class RuntimeCompiler implements Compiler {
|
|||
assertComponent(compMeta);
|
||||
var hostMeta = createHostComponentMeta(compMeta);
|
||||
compiledTemplate = new CompiledTemplate(
|
||||
true, compMeta.selector, compMeta.type, ngModule, [compMeta],
|
||||
this._templateNormalizer.normalizeDirective(hostMeta));
|
||||
true, compMeta.selector, compMeta.type, hostMeta, ngModule, [compMeta.type]);
|
||||
this._compiledHostTemplateCache.set(compType, compiledTemplate);
|
||||
}
|
||||
return compiledTemplate;
|
||||
|
@ -231,8 +225,8 @@ export class RuntimeCompiler implements Compiler {
|
|||
if (!compiledTemplate) {
|
||||
assertComponent(compMeta);
|
||||
compiledTemplate = new CompiledTemplate(
|
||||
false, compMeta.selector, compMeta.type, ngModule, ngModule.transitiveModule.directives,
|
||||
this._templateNormalizer.normalizeDirective(compMeta));
|
||||
false, compMeta.selector, compMeta.type, compMeta, ngModule,
|
||||
ngModule.transitiveModule.directives);
|
||||
this._compiledTemplateCache.set(compMeta.type.reference, compiledTemplate);
|
||||
}
|
||||
return compiledTemplate;
|
||||
|
@ -243,16 +237,7 @@ export class RuntimeCompiler implements Compiler {
|
|||
this._compiledTemplateCache.get(compType);
|
||||
if (!compiledTemplate) {
|
||||
throw new Error(
|
||||
`Illegal state: Compiled view for component ${stringify(compType)} does not exist!`);
|
||||
}
|
||||
return compiledTemplate;
|
||||
}
|
||||
|
||||
private _assertComponentLoaded(compType: any, isHost: boolean): CompiledTemplate {
|
||||
const compiledTemplate = this._assertComponentKnown(compType, isHost);
|
||||
if (compiledTemplate.loading) {
|
||||
throw new Error(
|
||||
`Illegal state: CompiledTemplate for ${stringify(compType)} (isHost: ${isHost}) is still loading!`);
|
||||
`Illegal state: Compiled view for component ${stringify(compType)} (host: ${isHost}) does not exist!`);
|
||||
}
|
||||
return compiledTemplate;
|
||||
}
|
||||
|
@ -285,34 +270,36 @@ export class RuntimeCompiler implements Compiler {
|
|||
if (template.isCompiled) {
|
||||
return;
|
||||
}
|
||||
const compMeta = template.normalizedCompMeta;
|
||||
const compMeta = template.compMeta;
|
||||
const externalStylesheetsByModuleUrl = new Map<string, CompiledStylesheet>();
|
||||
const stylesCompileResult = this._styleCompiler.compileComponent(compMeta);
|
||||
stylesCompileResult.externalStylesheets.forEach(
|
||||
(r) => { externalStylesheetsByModuleUrl.set(r.meta.moduleUrl, r); });
|
||||
this._resolveStylesCompileResult(
|
||||
stylesCompileResult.componentStylesheet, externalStylesheetsByModuleUrl);
|
||||
const viewCompMetas = template.viewComponentTypes.map(
|
||||
(compType) => this._assertComponentLoaded(compType, false).normalizedCompMeta);
|
||||
const parsedAnimations = this._animationParser.parseComponent(compMeta);
|
||||
const directives =
|
||||
template.directives.map(dir => this._metadataResolver.getDirectiveMetadata(dir.reference));
|
||||
const pipes = template.ngModule.transitiveModule.pipes.map(
|
||||
pipe => this._metadataResolver.getPipeMetadata(pipe.reference));
|
||||
const parsedTemplate = this._templateParser.parse(
|
||||
compMeta, compMeta.template.template, template.viewDirectives.concat(viewCompMetas),
|
||||
template.viewPipes, template.schemas, compMeta.type.name);
|
||||
compMeta, compMeta.template.template, directives, pipes, template.ngModule.schemas,
|
||||
compMeta.type.name);
|
||||
const compiledAnimations =
|
||||
this._animationCompiler.compile(compMeta.type.name, parsedAnimations);
|
||||
const compileResult = this._viewCompiler.compileComponent(
|
||||
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
|
||||
template.viewPipes, compiledAnimations);
|
||||
pipes, compiledAnimations);
|
||||
compileResult.dependencies.forEach((dep) => {
|
||||
let depTemplate: CompiledTemplate;
|
||||
if (dep instanceof ViewClassDependency) {
|
||||
let vfd = <ViewClassDependency>dep;
|
||||
depTemplate = this._assertComponentLoaded(vfd.comp.reference, false);
|
||||
depTemplate = this._assertComponentKnown(vfd.comp.reference, false);
|
||||
vfd.placeholder.reference = depTemplate.proxyViewClass;
|
||||
vfd.placeholder.name = `View_${vfd.comp.name}`;
|
||||
} else if (dep instanceof ComponentFactoryDependency) {
|
||||
let cfd = <ComponentFactoryDependency>dep;
|
||||
depTemplate = this._assertComponentLoaded(cfd.comp.reference, true);
|
||||
depTemplate = this._assertComponentKnown(cfd.comp.reference, true);
|
||||
cfd.placeholder.reference = depTemplate.proxyComponentFactory;
|
||||
cfd.placeholder.name = `compFactory_${cfd.comp.name}`;
|
||||
} else if (dep instanceof DirectiveWrapperDependency) {
|
||||
|
@ -361,29 +348,12 @@ class CompiledTemplate {
|
|||
private _viewClass: Function = null;
|
||||
proxyViewClass: Type<any>;
|
||||
proxyComponentFactory: ComponentFactory<any>;
|
||||
loading: Promise<any> = null;
|
||||
private _normalizedCompMeta: CompileDirectiveMetadata = null;
|
||||
isCompiled = false;
|
||||
isCompiledWithDeps = false;
|
||||
viewComponentTypes: Type<any>[] = [];
|
||||
viewDirectives: CompileDirectiveMetadata[] = [];
|
||||
viewPipes: CompilePipeMetadata[];
|
||||
schemas: SchemaMetadata[];
|
||||
|
||||
constructor(
|
||||
public isHost: boolean, selector: string, public compType: CompileIdentifierMetadata,
|
||||
public ngModule: CompileNgModuleMetadata,
|
||||
viewDirectiveAndComponents: CompileDirectiveMetadata[],
|
||||
_normalizeResult: SyncAsyncResult<CompileDirectiveMetadata>) {
|
||||
this.viewPipes = ngModule.transitiveModule.pipes;
|
||||
this.schemas = ngModule.schemas;
|
||||
viewDirectiveAndComponents.forEach((dirMeta) => {
|
||||
if (dirMeta.isComponent) {
|
||||
this.viewComponentTypes.push(dirMeta.type.reference);
|
||||
} else {
|
||||
this.viewDirectives.push(dirMeta);
|
||||
}
|
||||
});
|
||||
public compMeta: CompileDirectiveMetadata, public ngModule: CompileNgModuleMetadata,
|
||||
public directives: CompileIdentifierMetadata[]) {
|
||||
const self = this;
|
||||
this.proxyViewClass = <any>function() {
|
||||
if (!self._viewClass) {
|
||||
|
@ -395,21 +365,6 @@ class CompiledTemplate {
|
|||
this.proxyComponentFactory = isHost ?
|
||||
new ComponentFactory<any>(selector, this.proxyViewClass, compType.reference) :
|
||||
null;
|
||||
if (_normalizeResult.syncResult) {
|
||||
this._normalizedCompMeta = _normalizeResult.syncResult;
|
||||
} else {
|
||||
this.loading = _normalizeResult.asyncResult.then((normalizedCompMeta) => {
|
||||
this._normalizedCompMeta = normalizedCompMeta;
|
||||
this.loading = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get normalizedCompMeta(): CompileDirectiveMetadata {
|
||||
if (this.loading) {
|
||||
throw new Error(`Template is still loading for ${this.compType.name}!`);
|
||||
}
|
||||
return this._normalizedCompMeta;
|
||||
}
|
||||
|
||||
compiled(viewClass: Function) {
|
||||
|
@ -417,8 +372,6 @@ class CompiledTemplate {
|
|||
this.proxyViewClass.prototype = viewClass.prototype;
|
||||
this.isCompiled = true;
|
||||
}
|
||||
|
||||
depsCompiled() { this.isCompiledWithDeps = true; }
|
||||
}
|
||||
|
||||
function assertComponent(meta: CompileDirectiveMetadata) {
|
||||
|
|
|
@ -18,79 +18,78 @@ import {AsyncTestCompleter, beforeEach, describe, expect, inject, it} from '@ang
|
|||
|
||||
import {SpyResourceLoader} from './spies';
|
||||
|
||||
const SOME_MODULE_URL = 'package:some/module/a.js';
|
||||
const SOME_HTTP_MODULE_URL = 'http://some/module/a.js';
|
||||
|
||||
export function main() {
|
||||
describe('DirectiveNormalizer', () => {
|
||||
var dirType: CompileTypeMetadata;
|
||||
var dirTypeWithHttpUrl: CompileTypeMetadata;
|
||||
|
||||
beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
|
||||
|
||||
beforeEach(() => {
|
||||
dirType = new CompileTypeMetadata({moduleUrl: 'package:some/module/a.js', name: 'SomeComp'});
|
||||
dirTypeWithHttpUrl =
|
||||
new CompileTypeMetadata({moduleUrl: 'http://some/module/a.js', name: 'SomeComp'});
|
||||
});
|
||||
|
||||
describe('normalizeDirective', () => {
|
||||
it('should throw if no template was specified',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
expect(() => normalizer.normalizeDirective(new CompileDirectiveMetadata({
|
||||
type: dirType,
|
||||
isComponent: true,
|
||||
template:
|
||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []})
|
||||
}))).toThrowError('No template specified for component SomeComp');
|
||||
expect(() => normalizer.normalizeTemplate({
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
})).toThrowError('No template specified for component SomeComp');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('normalizeTemplateSync', () => {
|
||||
it('should store the template',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
let template = normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
|
||||
encapsulation: null,
|
||||
template: 'a',
|
||||
templateUrl: null,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
}));
|
||||
let template = normalizer.normalizeTemplateSync({
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
template: 'a',
|
||||
templateUrl: null,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
});
|
||||
expect(template.template).toEqual('a');
|
||||
expect(template.templateUrl).toEqual('package:some/module/a.js');
|
||||
}));
|
||||
|
||||
it('should resolve styles on the annotation against the moduleUrl',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
let template = normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
|
||||
encapsulation: null,
|
||||
template: '',
|
||||
templateUrl: null,
|
||||
styles: [],
|
||||
styleUrls: ['test.css']
|
||||
}));
|
||||
let template = normalizer.normalizeTemplateSync({
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
template: '',
|
||||
templateUrl: null,
|
||||
styles: [],
|
||||
styleUrls: ['test.css']
|
||||
});
|
||||
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
|
||||
}));
|
||||
|
||||
it('should resolve styles in the template against the moduleUrl',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
let template =
|
||||
normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
|
||||
encapsulation: null,
|
||||
template: '<style>@import test.css</style>',
|
||||
templateUrl: null,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
}));
|
||||
let template = normalizer.normalizeTemplateSync({
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
template: '<style>@import test.css</style>',
|
||||
templateUrl: null,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
});
|
||||
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
|
||||
}));
|
||||
|
||||
it('should use ViewEncapsulation.Emulated by default',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
let template = normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
|
||||
encapsulation: null,
|
||||
template: '',
|
||||
templateUrl: null,
|
||||
styles: [],
|
||||
styleUrls: ['test.css']
|
||||
}));
|
||||
let template = normalizer.normalizeTemplateSync({
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
template: '',
|
||||
templateUrl: null,
|
||||
styles: [],
|
||||
styleUrls: ['test.css']
|
||||
});
|
||||
expect(template.encapsulation).toEqual(ViewEncapsulation.Emulated);
|
||||
}));
|
||||
|
||||
|
@ -99,14 +98,15 @@ export function main() {
|
|||
[CompilerConfig, DirectiveNormalizer],
|
||||
(config: CompilerConfig, normalizer: DirectiveNormalizer) => {
|
||||
config.defaultEncapsulation = ViewEncapsulation.None;
|
||||
let template =
|
||||
normalizer.normalizeTemplateSync(dirType, new CompileTemplateMetadata({
|
||||
encapsulation: null,
|
||||
template: '',
|
||||
templateUrl: null,
|
||||
styles: [],
|
||||
styleUrls: ['test.css']
|
||||
}));
|
||||
let template = normalizer.normalizeTemplateSync({
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
template: '',
|
||||
templateUrl: null,
|
||||
styles: [],
|
||||
styleUrls: ['test.css']
|
||||
});
|
||||
expect(template.encapsulation).toEqual(ViewEncapsulation.None);
|
||||
}));
|
||||
});
|
||||
|
@ -120,13 +120,15 @@ export function main() {
|
|||
resourceLoader: MockResourceLoader) => {
|
||||
resourceLoader.expect('package:some/module/sometplurl.html', 'a');
|
||||
normalizer
|
||||
.normalizeTemplateAsync(dirType, new CompileTemplateMetadata({
|
||||
encapsulation: null,
|
||||
template: null,
|
||||
templateUrl: 'sometplurl.html',
|
||||
styles: [],
|
||||
styleUrls: ['test.css']
|
||||
}))
|
||||
.normalizeTemplateAsync({
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
template: null,
|
||||
templateUrl: 'sometplurl.html',
|
||||
styles: [],
|
||||
styleUrls: ['test.css']
|
||||
})
|
||||
.then((template: CompileTemplateMetadata) => {
|
||||
expect(template.template).toEqual('a');
|
||||
expect(template.templateUrl).toEqual('package:some/module/sometplurl.html');
|
||||
|
@ -142,13 +144,15 @@ export function main() {
|
|||
resourceLoader: MockResourceLoader) => {
|
||||
resourceLoader.expect('package:some/module/tpl/sometplurl.html', '');
|
||||
normalizer
|
||||
.normalizeTemplateAsync(dirType, new CompileTemplateMetadata({
|
||||
encapsulation: null,
|
||||
template: null,
|
||||
templateUrl: 'tpl/sometplurl.html',
|
||||
styles: [],
|
||||
styleUrls: ['test.css']
|
||||
}))
|
||||
.normalizeTemplateAsync({
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
template: null,
|
||||
templateUrl: 'tpl/sometplurl.html',
|
||||
styles: [],
|
||||
styleUrls: ['test.css']
|
||||
})
|
||||
.then((template: CompileTemplateMetadata) => {
|
||||
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
|
||||
async.done();
|
||||
|
@ -164,13 +168,15 @@ export function main() {
|
|||
resourceLoader.expect(
|
||||
'package:some/module/tpl/sometplurl.html', '<style>@import test.css</style>');
|
||||
normalizer
|
||||
.normalizeTemplateAsync(dirType, new CompileTemplateMetadata({
|
||||
encapsulation: null,
|
||||
template: null,
|
||||
templateUrl: 'tpl/sometplurl.html',
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
}))
|
||||
.normalizeTemplateAsync({
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
template: null,
|
||||
templateUrl: 'tpl/sometplurl.html',
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
})
|
||||
.then((template: CompileTemplateMetadata) => {
|
||||
expect(template.styleUrls).toEqual(['package:some/module/tpl/test.css']);
|
||||
async.done();
|
||||
|
@ -249,13 +255,15 @@ export function main() {
|
|||
(async: AsyncTestCompleter, normalizer: DirectiveNormalizer,
|
||||
resourceLoader: MockResourceLoader) => {
|
||||
resourceLoader.expect('package:some/module/cmp.html', 'a');
|
||||
var templateMeta = new CompileTemplateMetadata({
|
||||
var prenormMeta = {
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
templateUrl: 'cmp.html',
|
||||
});
|
||||
};
|
||||
Promise
|
||||
.all([
|
||||
normalizer.normalizeTemplateAsync(dirType, templateMeta),
|
||||
normalizer.normalizeTemplateAsync(dirType, templateMeta)
|
||||
normalizer.normalizeTemplateAsync(prenormMeta),
|
||||
normalizer.normalizeTemplateAsync(prenormMeta)
|
||||
])
|
||||
.then((templates: CompileTemplateMetadata[]) => {
|
||||
expect(templates[0].template).toEqual('a');
|
||||
|
@ -273,8 +281,13 @@ export function main() {
|
|||
|
||||
var viewEncapsulation = ViewEncapsulation.Native;
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType, new CompileTemplateMetadata(
|
||||
{encapsulation: viewEncapsulation, styles: [], styleUrls: []}),
|
||||
{
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: viewEncapsulation,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
},
|
||||
'', 'package:some/module/');
|
||||
expect(template.encapsulation).toBe(viewEncapsulation);
|
||||
}));
|
||||
|
@ -282,17 +295,27 @@ export function main() {
|
|||
it('should keep the template as html',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType,
|
||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}), 'a',
|
||||
'package:some/module/');
|
||||
{
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
},
|
||||
'a', 'package:some/module/');
|
||||
expect(template.template).toEqual('a');
|
||||
}));
|
||||
|
||||
it('should collect ngContent',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType,
|
||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
||||
{
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
},
|
||||
'<ng-content select="a"></ng-content>', 'package:some/module/');
|
||||
expect(template.ngContentSelectors).toEqual(['a']);
|
||||
}));
|
||||
|
@ -300,8 +323,13 @@ export function main() {
|
|||
it('should normalize ngContent wildcard selector',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType,
|
||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
||||
{
|
||||
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/');
|
||||
expect(template.ngContentSelectors).toEqual(['*', '*', '*']);
|
||||
|
@ -310,8 +338,13 @@ export function main() {
|
|||
it('should collect top level styles in the template',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType,
|
||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
||||
{
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
},
|
||||
'<style>a</style>', 'package:some/module/');
|
||||
expect(template.styles).toEqual(['a']);
|
||||
}));
|
||||
|
@ -319,8 +352,13 @@ export function main() {
|
|||
it('should collect styles inside in elements',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType,
|
||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
||||
{
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
},
|
||||
'<div><style>a</style></div>', 'package:some/module/');
|
||||
expect(template.styles).toEqual(['a']);
|
||||
}));
|
||||
|
@ -328,8 +366,13 @@ export function main() {
|
|||
it('should collect styleUrls in the template',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType,
|
||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
||||
{
|
||||
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']);
|
||||
}));
|
||||
|
@ -337,8 +380,13 @@ export function main() {
|
|||
it('should collect styleUrls in elements',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType,
|
||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
||||
{
|
||||
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']);
|
||||
}));
|
||||
|
@ -346,8 +394,13 @@ export function main() {
|
|||
it('should ignore link elements with non stylesheet rel attribute',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType,
|
||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
||||
{
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
},
|
||||
'<link href="b" rel="a">', 'package:some/module/');
|
||||
expect(template.styleUrls).toEqual([]);
|
||||
}));
|
||||
|
@ -355,8 +408,13 @@ export function main() {
|
|||
it('should ignore link elements with absolute urls but non package: scheme',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType,
|
||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
||||
{
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
},
|
||||
'<link href="http://some/external.css" rel="stylesheet">', 'package:some/module/');
|
||||
expect(template.styleUrls).toEqual([]);
|
||||
}));
|
||||
|
@ -364,8 +422,13 @@ export function main() {
|
|||
it('should extract @import style urls into styleAbsUrl',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType, new CompileTemplateMetadata(
|
||||
{encapsulation: null, styles: ['@import "test.css";'], styleUrls: []}),
|
||||
{
|
||||
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']);
|
||||
|
@ -374,11 +437,13 @@ export function main() {
|
|||
it('should not resolve relative urls in inline styles',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType, new CompileTemplateMetadata({
|
||||
{
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
styles: ['.foo{background-image: url(\'double.jpg\');'],
|
||||
styleUrls: []
|
||||
}),
|
||||
},
|
||||
'', 'package:some/module/id');
|
||||
expect(template.styles).toEqual(['.foo{background-image: url(\'double.jpg\');']);
|
||||
}));
|
||||
|
@ -386,8 +451,13 @@ export function main() {
|
|||
it('should resolve relative style urls in styleUrls',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType, new CompileTemplateMetadata(
|
||||
{encapsulation: null, styles: [], styleUrls: ['test.css']}),
|
||||
{
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
styles: [],
|
||||
styleUrls: ['test.css']
|
||||
},
|
||||
'', 'package:some/module/id');
|
||||
expect(template.styles).toEqual([]);
|
||||
expect(template.styleUrls).toEqual(['package:some/module/test.css']);
|
||||
|
@ -396,8 +466,13 @@ export function main() {
|
|||
it('should resolve relative style urls in styleUrls with http directive url',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirTypeWithHttpUrl, new CompileTemplateMetadata(
|
||||
{encapsulation: null, styles: [], styleUrls: ['test.css']}),
|
||||
{
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_HTTP_MODULE_URL,
|
||||
encapsulation: null,
|
||||
styles: [],
|
||||
styleUrls: ['test.css']
|
||||
},
|
||||
'', 'http://some/module/id');
|
||||
expect(template.styles).toEqual([]);
|
||||
expect(template.styleUrls).toEqual(['http://some/module/test.css']);
|
||||
|
@ -406,8 +481,13 @@ export function main() {
|
|||
it('should normalize ViewEncapsulation.Emulated to ViewEncapsulation.None if there are no styles nor stylesheets',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType, new CompileTemplateMetadata(
|
||||
{encapsulation: ViewEncapsulation.Emulated, styles: [], styleUrls: []}),
|
||||
{
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: ViewEncapsulation.Emulated,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
},
|
||||
'', 'package:some/module/id');
|
||||
expect(template.encapsulation).toEqual(ViewEncapsulation.None);
|
||||
}));
|
||||
|
@ -415,8 +495,13 @@ export function main() {
|
|||
it('should ignore ng-content in elements with ngNonBindable',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType,
|
||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
||||
{
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
},
|
||||
'<div ngNonBindable><ng-content select="a"></ng-content></div>',
|
||||
'package:some/module/');
|
||||
expect(template.ngContentSelectors).toEqual([]);
|
||||
|
@ -425,8 +510,13 @@ export function main() {
|
|||
it('should still collect <style> in elements with ngNonBindable',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
var template = normalizer.normalizeLoadedTemplate(
|
||||
dirType,
|
||||
new CompileTemplateMetadata({encapsulation: null, styles: [], styleUrls: []}),
|
||||
{
|
||||
componentType: SomeComp,
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
encapsulation: null,
|
||||
styles: [],
|
||||
styleUrls: []
|
||||
},
|
||||
'<div ngNonBindable><style>div {color:red}</style></div>', 'package:some/module/');
|
||||
expect(template.styles).toEqual(['div {color:red}']);
|
||||
}));
|
||||
|
@ -444,3 +534,5 @@ function programResourceLoaderSpy(spy: SpyResourceLoader, results: {[key: string
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
class SomeComp {}
|
|
@ -9,10 +9,12 @@
|
|||
import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/test_bindings';
|
||||
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, Component, DoCheck, Injectable, NgModule, OnChanges, OnDestroy, OnInit, Pipe, SimpleChanges, ViewEncapsulation} from '@angular/core';
|
||||
import {LIFECYCLE_HOOKS_VALUES} from '@angular/core/src/metadata/lifecycle_hooks';
|
||||
import {TestBed, inject} from '@angular/core/testing';
|
||||
import {TestBed, async, inject} from '@angular/core/testing';
|
||||
|
||||
import {stringify} from '../src/facade/lang';
|
||||
import {CompileMetadataResolver} from '../src/metadata_resolver';
|
||||
import {ResourceLoader} from '../src/resource_loader';
|
||||
import {MockResourceLoader} from '../testing/resource_loader_mock';
|
||||
|
||||
import {MalformedStylesComponent} from './metadata_resolver_fixture';
|
||||
|
||||
|
@ -20,186 +22,305 @@ export function main() {
|
|||
describe('CompileMetadataResolver', () => {
|
||||
beforeEach(() => { TestBed.configureCompiler({providers: TEST_COMPILER_PROVIDERS}); });
|
||||
|
||||
describe('getDirectiveMetadata', () => {
|
||||
it('should read metadata',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
const meta = resolver.getDirectiveMetadata(ComponentWithEverything);
|
||||
expect(meta.selector).toEqual('someSelector');
|
||||
expect(meta.exportAs).toEqual('someExportAs');
|
||||
expect(meta.isComponent).toBe(true);
|
||||
expect(meta.type.reference).toBe(ComponentWithEverything);
|
||||
expect(meta.type.name).toEqual(stringify(ComponentWithEverything));
|
||||
expect(meta.type.lifecycleHooks).toEqual(LIFECYCLE_HOOKS_VALUES);
|
||||
expect(meta.changeDetection).toBe(ChangeDetectionStrategy.Default);
|
||||
expect(meta.inputs).toEqual({'someProp': 'someProp'});
|
||||
expect(meta.outputs).toEqual({'someEvent': 'someEvent'});
|
||||
expect(meta.hostListeners).toEqual({'someHostListener': 'someHostListenerExpr'});
|
||||
expect(meta.hostProperties).toEqual({'someHostProp': 'someHostPropExpr'});
|
||||
expect(meta.hostAttributes).toEqual({'someHostAttr': 'someHostAttrValue'});
|
||||
expect(meta.template.encapsulation).toBe(ViewEncapsulation.Emulated);
|
||||
expect(meta.template.styles).toEqual(['someStyle']);
|
||||
expect(meta.template.styleUrls).toEqual(['someStyleUrl']);
|
||||
expect(meta.template.template).toEqual('someTemplate');
|
||||
expect(meta.template.templateUrl).toEqual('someTemplateUrl');
|
||||
expect(meta.template.interpolation).toEqual(['{{', '}}']);
|
||||
}));
|
||||
|
||||
it('should use the moduleUrl from the reflector if none is given',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
const value: string =
|
||||
resolver.getDirectiveMetadata(ComponentWithoutModuleId).type.moduleUrl;
|
||||
const expectedEndValue = './ComponentWithoutModuleId';
|
||||
expect(value.endsWith(expectedEndValue)).toBe(true);
|
||||
}));
|
||||
|
||||
it('should throw when the moduleId is not a string',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidModuleId))
|
||||
.toThrowError(
|
||||
`moduleId should be a string in "ComponentWithInvalidModuleId". See` +
|
||||
` https://goo.gl/wIDDiL for more information.\n` +
|
||||
`If you're using Webpack you should inline the template and the styles, see` +
|
||||
` https://goo.gl/X2J8zc.`);
|
||||
}));
|
||||
|
||||
|
||||
it('should throw when metadata is incorrectly typed',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
expect(() => resolver.getDirectiveMetadata(MalformedStylesComponent))
|
||||
.toThrowError(`Expected 'styles' to be an array of strings.`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when provider token can not be resolved',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
expect(() => resolver.getDirectiveMetadata(MyBrokenComp1))
|
||||
.toThrowError(`Can't resolve all parameters for MyBrokenComp1: (?).`);
|
||||
}));
|
||||
it('should throw with descriptive error message when a directive is passed to imports',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({imports: [ComponentWithoutModuleId]})
|
||||
class ModuleWithImportedComponent {
|
||||
}
|
||||
expect(() => resolver.getNgModuleMetadata(ModuleWithImportedComponent))
|
||||
.toThrowError(
|
||||
`Unexpected directive 'ComponentWithoutModuleId' imported by the module 'ModuleWithImportedComponent'`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when a pipe is passed to imports',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@Pipe({name: 'somePipe'})
|
||||
class SomePipe {
|
||||
}
|
||||
@NgModule({imports: [SomePipe]})
|
||||
class ModuleWithImportedPipe {
|
||||
}
|
||||
expect(() => resolver.getNgModuleMetadata(ModuleWithImportedPipe))
|
||||
.toThrowError(
|
||||
`Unexpected pipe 'SomePipe' imported by the module 'ModuleWithImportedPipe'`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when a module is passed to declarations',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({})
|
||||
class SomeModule {
|
||||
}
|
||||
@NgModule({declarations: [SomeModule]})
|
||||
class ModuleWithDeclaredModule {
|
||||
}
|
||||
expect(() => resolver.getNgModuleMetadata(ModuleWithDeclaredModule))
|
||||
.toThrowError(
|
||||
`Unexpected module 'SomeModule' declared by the module 'ModuleWithDeclaredModule'`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when null is passed to declarations',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [null]})
|
||||
class ModuleWithNullDeclared {
|
||||
}
|
||||
expect(() => resolver.getNgModuleMetadata(ModuleWithNullDeclared))
|
||||
.toThrowError(
|
||||
`Unexpected value 'null' declared by the module 'ModuleWithNullDeclared'`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when null is passed to imports',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({imports: [null]})
|
||||
class ModuleWithNullImported {
|
||||
}
|
||||
expect(() => resolver.getNgModuleMetadata(ModuleWithNullImported))
|
||||
.toThrowError(
|
||||
`Unexpected value 'null' imported by the module 'ModuleWithNullImported'`);
|
||||
}));
|
||||
|
||||
|
||||
it('should throw with descriptive error message when a param token of a dependency is undefined',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
expect(() => resolver.getDirectiveMetadata(MyBrokenComp2))
|
||||
.toThrowError(`Can't resolve all parameters for NonAnnotatedService: (?).`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when one of providers is not present',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
expect(() => resolver.getDirectiveMetadata(MyBrokenComp3))
|
||||
.toThrowError(
|
||||
`Invalid providers for "MyBrokenComp3" - only instances of Provider and Type are allowed, got: [SimpleService, ?null?, ...]`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when one of viewProviders is not present',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
expect(() => resolver.getDirectiveMetadata(MyBrokenComp4))
|
||||
.toThrowError(
|
||||
`Invalid viewProviders for "MyBrokenComp4" - only instances of Provider and Type are allowed, got: [?null?, ...]`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when null or undefined is passed to module bootstrap',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({bootstrap: [null]})
|
||||
class ModuleWithNullBootstrap {
|
||||
}
|
||||
@NgModule({bootstrap: [undefined]})
|
||||
class ModuleWithUndefinedBootstrap {
|
||||
}
|
||||
|
||||
expect(() => resolver.getNgModuleMetadata(ModuleWithNullBootstrap))
|
||||
.toThrowError(
|
||||
`Unexpected value 'null' used in the bootstrap property of module 'ModuleWithNullBootstrap'`);
|
||||
expect(() => resolver.getNgModuleMetadata(ModuleWithUndefinedBootstrap))
|
||||
.toThrowError(
|
||||
`Unexpected value 'undefined' used in the bootstrap property of module 'ModuleWithUndefinedBootstrap'`);
|
||||
}));
|
||||
|
||||
it('should throw an error when the interpolation config has invalid symbols',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation1))
|
||||
.toThrowError(`[' ', ' '] contains unusable interpolation symbol.`);
|
||||
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation2))
|
||||
.toThrowError(`['{', '}'] contains unusable interpolation symbol.`);
|
||||
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation3))
|
||||
.toThrowError(`['<%', '%>'] contains unusable interpolation symbol.`);
|
||||
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation4))
|
||||
.toThrowError(`['&#', '}}'] contains unusable interpolation symbol.`);
|
||||
expect(() => resolver.getDirectiveMetadata(ComponentWithInvalidInterpolation5))
|
||||
.toThrowError(`['{', '}}'] contains unusable interpolation symbol.`);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should dedupe declarations in NgModule',
|
||||
it('should throw on the get... methods if the module has not been loaded yet',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
|
||||
@Component({template: ''})
|
||||
class MyComp {
|
||||
@NgModule({})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComp, MyComp]})
|
||||
class MyModule {
|
||||
@Pipe({name: 'pipe'})
|
||||
class SomePipe {
|
||||
}
|
||||
|
||||
const modMeta = resolver.getNgModuleMetadata(MyModule);
|
||||
expect(modMeta.declaredDirectives.length).toBe(1);
|
||||
expect(modMeta.declaredDirectives[0].type.reference).toBe(MyComp);
|
||||
expect(() => resolver.getNgModuleMetadata(SomeModule)).toThrowError(/Illegal state/);
|
||||
expect(() => resolver.getDirectiveMetadata(ComponentWithEverythingInline))
|
||||
.toThrowError(/Illegal state/);
|
||||
expect(() => resolver.getPipeMetadata(SomePipe)).toThrowError(/Illegal state/);
|
||||
}));
|
||||
|
||||
it('should read metadata in sync for components with inline resources',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [ComponentWithEverythingInline]})
|
||||
class SomeModule {
|
||||
}
|
||||
resolver.loadNgModuleMetadata(SomeModule, true);
|
||||
|
||||
const meta = resolver.getDirectiveMetadata(ComponentWithEverythingInline);
|
||||
expect(meta.selector).toEqual('someSelector');
|
||||
expect(meta.exportAs).toEqual('someExportAs');
|
||||
expect(meta.isComponent).toBe(true);
|
||||
expect(meta.type.reference).toBe(ComponentWithEverythingInline);
|
||||
expect(meta.type.name).toEqual(stringify(ComponentWithEverythingInline));
|
||||
expect(meta.type.lifecycleHooks).toEqual(LIFECYCLE_HOOKS_VALUES);
|
||||
expect(meta.changeDetection).toBe(ChangeDetectionStrategy.Default);
|
||||
expect(meta.inputs).toEqual({'someProp': 'someProp'});
|
||||
expect(meta.outputs).toEqual({'someEvent': 'someEvent'});
|
||||
expect(meta.hostListeners).toEqual({'someHostListener': 'someHostListenerExpr'});
|
||||
expect(meta.hostProperties).toEqual({'someHostProp': 'someHostPropExpr'});
|
||||
expect(meta.hostAttributes).toEqual({'someHostAttr': 'someHostAttrValue'});
|
||||
expect(meta.template.encapsulation).toBe(ViewEncapsulation.Emulated);
|
||||
expect(meta.template.styles).toEqual(['someStyle']);
|
||||
expect(meta.template.template).toEqual('someTemplate');
|
||||
expect(meta.template.interpolation).toEqual(['{{', '}}']);
|
||||
}));
|
||||
|
||||
it('should throw when reading metadata for component with external resources when sync=true is passed',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [ComponentWithExternalResources]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
|
||||
.toThrowError(
|
||||
`Can't compile synchronously as ${stringify(ComponentWithExternalResources)} is still being loaded!`);
|
||||
}));
|
||||
|
||||
it('should read external metadata when sync=false',
|
||||
async(inject(
|
||||
[CompileMetadataResolver, ResourceLoader],
|
||||
(resolver: CompileMetadataResolver, resourceLoader: MockResourceLoader) => {
|
||||
@NgModule({declarations: [ComponentWithExternalResources]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
resourceLoader.when('someTemplateUrl', 'someTemplate');
|
||||
resolver.loadNgModuleMetadata(SomeModule, false).loading.then(() => {
|
||||
const meta = resolver.getDirectiveMetadata(ComponentWithExternalResources);
|
||||
expect(meta.selector).toEqual('someSelector');
|
||||
expect(meta.template.styleUrls).toEqual(['someStyleUrl']);
|
||||
expect(meta.template.templateUrl).toEqual('someTemplateUrl');
|
||||
expect(meta.template.template).toEqual('someTemplate');
|
||||
});
|
||||
resourceLoader.flush();
|
||||
})));
|
||||
|
||||
it('should wait for external resources of imported modules',
|
||||
async(inject(
|
||||
[CompileMetadataResolver, ResourceLoader],
|
||||
(resolver: CompileMetadataResolver, resourceLoader: MockResourceLoader) => {
|
||||
@NgModule({declarations: [ComponentWithExternalResources]})
|
||||
class SomeImportedModule {
|
||||
}
|
||||
|
||||
@NgModule({imports: [SomeImportedModule]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
resourceLoader.when('someTemplateUrl', 'someTemplate');
|
||||
resolver.loadNgModuleMetadata(SomeModule, false).loading.then(() => {
|
||||
const meta = resolver.getDirectiveMetadata(ComponentWithExternalResources);
|
||||
expect(meta.selector).toEqual('someSelector');
|
||||
});
|
||||
resourceLoader.flush();
|
||||
})));
|
||||
|
||||
it('should use the moduleUrl from the reflector if none is given',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [ComponentWithoutModuleId]})
|
||||
class SomeModule {
|
||||
}
|
||||
resolver.loadNgModuleMetadata(SomeModule, true);
|
||||
|
||||
const value: string =
|
||||
resolver.getDirectiveMetadata(ComponentWithoutModuleId).type.moduleUrl;
|
||||
const expectedEndValue = './ComponentWithoutModuleId';
|
||||
expect(value.endsWith(expectedEndValue)).toBe(true);
|
||||
}));
|
||||
|
||||
it('should throw when the moduleId is not a string',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [ComponentWithInvalidModuleId]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
|
||||
.toThrowError(
|
||||
`moduleId should be a string in "ComponentWithInvalidModuleId". See` +
|
||||
` https://goo.gl/wIDDiL for more information.\n` +
|
||||
`If you're using Webpack you should inline the template and the styles, see` +
|
||||
` https://goo.gl/X2J8zc.`);
|
||||
}));
|
||||
|
||||
|
||||
it('should throw when metadata is incorrectly typed',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [MalformedStylesComponent]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
|
||||
.toThrowError(`Expected 'styles' to be an array of strings.`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when provider token can not be resolved',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [MyBrokenComp1]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
|
||||
.toThrowError(`Can't resolve all parameters for MyBrokenComp1: (?).`);
|
||||
}));
|
||||
it('should throw with descriptive error message when a directive is passed to imports',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({imports: [ComponentWithoutModuleId]})
|
||||
class ModuleWithImportedComponent {
|
||||
}
|
||||
expect(() => resolver.loadNgModuleMetadata(ModuleWithImportedComponent, true))
|
||||
.toThrowError(
|
||||
`Unexpected directive 'ComponentWithoutModuleId' imported by the module 'ModuleWithImportedComponent'`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when a pipe is passed to imports',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@Pipe({name: 'somePipe'})
|
||||
class SomePipe {
|
||||
}
|
||||
@NgModule({imports: [SomePipe]})
|
||||
class ModuleWithImportedPipe {
|
||||
}
|
||||
expect(() => resolver.loadNgModuleMetadata(ModuleWithImportedPipe, true))
|
||||
.toThrowError(
|
||||
`Unexpected pipe 'SomePipe' imported by the module 'ModuleWithImportedPipe'`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when a module is passed to declarations',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({})
|
||||
class SomeModule {
|
||||
}
|
||||
@NgModule({declarations: [SomeModule]})
|
||||
class ModuleWithDeclaredModule {
|
||||
}
|
||||
expect(() => resolver.loadNgModuleMetadata(ModuleWithDeclaredModule, true))
|
||||
.toThrowError(
|
||||
`Unexpected module 'SomeModule' declared by the module 'ModuleWithDeclaredModule'`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when null is passed to declarations',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [null]})
|
||||
class ModuleWithNullDeclared {
|
||||
}
|
||||
expect(() => resolver.loadNgModuleMetadata(ModuleWithNullDeclared, true))
|
||||
.toThrowError(
|
||||
`Unexpected value 'null' declared by the module 'ModuleWithNullDeclared'`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when null is passed to imports',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({imports: [null]})
|
||||
class ModuleWithNullImported {
|
||||
}
|
||||
expect(() => resolver.loadNgModuleMetadata(ModuleWithNullImported, true))
|
||||
.toThrowError(
|
||||
`Unexpected value 'null' imported by the module 'ModuleWithNullImported'`);
|
||||
}));
|
||||
|
||||
|
||||
it('should throw with descriptive error message when a param token of a dependency is undefined',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [MyBrokenComp2]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
|
||||
.toThrowError(`Can't resolve all parameters for NonAnnotatedService: (?).`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when one of providers is not present',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [MyBrokenComp3]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
|
||||
.toThrowError(
|
||||
`Invalid providers for "MyBrokenComp3" - only instances of Provider and Type are allowed, got: [SimpleService, ?null?, ...]`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when one of viewProviders is not present',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [MyBrokenComp4]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleMetadata(SomeModule, true))
|
||||
.toThrowError(
|
||||
`Invalid viewProviders for "MyBrokenComp4" - only instances of Provider and Type are allowed, got: [?null?, ...]`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when null or undefined is passed to module bootstrap',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({bootstrap: [null]})
|
||||
class ModuleWithNullBootstrap {
|
||||
}
|
||||
@NgModule({bootstrap: [undefined]})
|
||||
class ModuleWithUndefinedBootstrap {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleMetadata(ModuleWithNullBootstrap, true))
|
||||
.toThrowError(
|
||||
`Unexpected value 'null' used in the bootstrap property of module 'ModuleWithNullBootstrap'`);
|
||||
expect(() => resolver.loadNgModuleMetadata(ModuleWithUndefinedBootstrap, true))
|
||||
.toThrowError(
|
||||
`Unexpected value 'undefined' used in the bootstrap property of module 'ModuleWithUndefinedBootstrap'`);
|
||||
}));
|
||||
|
||||
it('should throw an error when the interpolation config has invalid symbols',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [ComponentWithInvalidInterpolation1]})
|
||||
class Module1 {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleMetadata(Module1, true))
|
||||
.toThrowError(`[' ', ' '] contains unusable interpolation symbol.`);
|
||||
|
||||
@NgModule({declarations: [ComponentWithInvalidInterpolation2]})
|
||||
class Module2 {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleMetadata(Module2, true))
|
||||
.toThrowError(`['{', '}'] contains unusable interpolation symbol.`);
|
||||
|
||||
@NgModule({declarations: [ComponentWithInvalidInterpolation3]})
|
||||
class Module3 {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleMetadata(Module3, true))
|
||||
.toThrowError(`['<%', '%>'] contains unusable interpolation symbol.`);
|
||||
|
||||
@NgModule({declarations: [ComponentWithInvalidInterpolation4]})
|
||||
class Module4 {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleMetadata(Module4, true))
|
||||
.toThrowError(`['&#', '}}'] contains unusable interpolation symbol.`);
|
||||
|
||||
@NgModule({declarations: [ComponentWithInvalidInterpolation5]})
|
||||
class Module5 {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleMetadata(Module5, true))
|
||||
.toThrowError(`['{', '}}'] contains unusable interpolation symbol.`);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should dedupe declarations in NgModule',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
|
||||
@Component({template: ''})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComp, MyComp]})
|
||||
class MyModule {
|
||||
}
|
||||
|
||||
const modMeta = resolver.loadNgModuleMetadata(MyModule, true).ngModule;
|
||||
expect(modMeta.declaredDirectives.length).toBe(1);
|
||||
expect(modMeta.declaredDirectives[0].reference).toBe(MyComp);
|
||||
}));
|
||||
}
|
||||
|
||||
@Component({selector: 'someComponent', template: ''})
|
||||
|
@ -210,6 +331,14 @@ class ComponentWithoutModuleId {
|
|||
class ComponentWithInvalidModuleId {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'someSelector',
|
||||
templateUrl: 'someTemplateUrl',
|
||||
styleUrls: ['someStyleUrl'],
|
||||
})
|
||||
class ComponentWithExternalResources {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'someSelector',
|
||||
inputs: ['someProp'],
|
||||
|
@ -223,13 +352,11 @@ class ComponentWithInvalidModuleId {
|
|||
moduleId: 'someModuleId',
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
template: 'someTemplate',
|
||||
templateUrl: 'someTemplateUrl',
|
||||
encapsulation: ViewEncapsulation.Emulated,
|
||||
styles: ['someStyle'],
|
||||
styleUrls: ['someStyleUrl'],
|
||||
interpolation: ['{{', '}}']
|
||||
})
|
||||
class ComponentWithEverything implements OnChanges,
|
||||
class ComponentWithEverythingInline implements OnChanges,
|
||||
OnInit, DoCheck, OnDestroy, AfterContentInit, AfterContentChecked, AfterViewInit,
|
||||
AfterViewChecked {
|
||||
ngOnChanges(changes: SimpleChanges): void {}
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
|
||||
import {DirectiveResolver, ResourceLoader} from '@angular/compiler';
|
||||
import {Compiler, Component, Injector, NgModule, NgModuleFactory} from '@angular/core';
|
||||
import {TestBed, async, fakeAsync, tick} from '@angular/core/testing';
|
||||
import {beforeEach, describe, inject, it} from '@angular/core/testing/testing_internal';
|
||||
import {TestBed, async, fakeAsync, inject, tick} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
import {stringify} from '../src/facade/lang';
|
||||
|
|
|
@ -11,7 +11,7 @@ import {ApplicationRef, destroyPlatform} from '@angular/core/src/application_ref
|
|||
import {Console} from '@angular/core/src/console';
|
||||
import {ComponentRef} from '@angular/core/src/linker/component_factory';
|
||||
import {Testability, TestabilityRegistry} from '@angular/core/src/testability/testability';
|
||||
import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, describe, inject, it} from '@angular/core/testing/testing_internal';
|
||||
import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, describe, iit, inject, it} from '@angular/core/testing/testing_internal';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
|
@ -149,11 +149,19 @@ export function main() {
|
|||
|
||||
afterEach(destroyPlatform);
|
||||
|
||||
it('should throw if bootstrapped Directive is not a Component', () => {
|
||||
expect(() => bootstrap(HelloRootDirectiveIsNotCmp))
|
||||
.toThrowError(
|
||||
`Could not compile '${stringify(HelloRootDirectiveIsNotCmp)}' because it is not a component.`);
|
||||
});
|
||||
it('should throw if bootstrapped Directive is not a Component',
|
||||
inject([AsyncTestCompleter], (done: AsyncTestCompleter) => {
|
||||
var logger = new MockConsole();
|
||||
var errorHandler = new ErrorHandler(false);
|
||||
errorHandler._console = logger as any;
|
||||
bootstrap(HelloRootDirectiveIsNotCmp, [
|
||||
{provide: ErrorHandler, useValue: errorHandler}
|
||||
]).catch((e) => {
|
||||
expect(e.message).toBe(
|
||||
`Could not compile '${stringify(HelloRootDirectiveIsNotCmp)}' because it is not a component.`);
|
||||
done.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw if no element is found',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
|
|
Loading…
Reference in New Issue