angular/modules/@angular/compiler/src/compile_metadata.ts
Martin Probst ebc8e808a9 feat(router): register NgModuleFactory objects. (#11211)
When lazily loading code, users need to be able to get hold of the
NgModuleFactory. For SystemJS environments, the SystemJS registry serves
this purpose. However other environments, such as modules compiled with
Closure compiler, do not expose exports object or a path based registry.

For these environments, `@NgModule` objects can include an identifier, and
the loading code can then pass `loadModule(id).then(() =>
getNgModule(id))` to the router.
2016-09-01 13:46:08 -07:00

631 lines
20 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
import {ListWrapper, MapWrapper, StringMapWrapper} from './facade/collection';
import {isBlank, isPresent, isStringMap, normalizeBlank, normalizeBool} from './facade/lang';
import {LifecycleHooks, reflector} from './private_import_core';
import {CssSelector} from './selector';
import {getUrlScheme} from './url_resolver';
import {sanitizeIdentifier, splitAtColon} from './util';
function unimplemented(): any {
throw new Error('unimplemented');
}
// group 0: "[prop] or (event) or @trigger"
// group 1: "prop" from "[prop]"
// group 2: "event" from "(event)"
// group 3: "@trigger" from "@trigger"
const HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))|(\@[-\w]+)$/;
const UNDEFINED = new Object();
export abstract class CompileMetadataWithIdentifier {
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
}
export class CompileAnimationEntryMetadata {
constructor(
public name: string = null, public definitions: CompileAnimationStateMetadata[] = null) {}
}
export abstract class CompileAnimationStateMetadata {}
export class CompileAnimationStateDeclarationMetadata extends CompileAnimationStateMetadata {
constructor(public stateNameExpr: string, public styles: CompileAnimationStyleMetadata) {
super();
}
}
export class CompileAnimationStateTransitionMetadata extends CompileAnimationStateMetadata {
constructor(public stateChangeExpr: string, public steps: CompileAnimationMetadata) { super(); }
}
export abstract class CompileAnimationMetadata {}
export class CompileAnimationKeyframesSequenceMetadata extends CompileAnimationMetadata {
constructor(public steps: CompileAnimationStyleMetadata[] = []) { super(); }
}
export class CompileAnimationStyleMetadata extends CompileAnimationMetadata {
constructor(
public offset: number, public styles: Array<string|{[key: string]: string | number}> = null) {
super();
}
}
export class CompileAnimationAnimateMetadata extends CompileAnimationMetadata {
constructor(
public timings: string|number = 0, public styles: CompileAnimationStyleMetadata|
CompileAnimationKeyframesSequenceMetadata = null) {
super();
}
}
export abstract class CompileAnimationWithStepsMetadata extends CompileAnimationMetadata {
constructor(public steps: CompileAnimationMetadata[] = null) { super(); }
}
export class CompileAnimationSequenceMetadata extends CompileAnimationWithStepsMetadata {
constructor(steps: CompileAnimationMetadata[] = null) { super(steps); }
}
export class CompileAnimationGroupMetadata extends CompileAnimationWithStepsMetadata {
constructor(steps: CompileAnimationMetadata[] = null) { super(steps); }
}
export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier {
reference: any;
name: string;
prefix: string;
moduleUrl: string;
value: any;
constructor(
{reference, name, moduleUrl, prefix, value}:
{reference?: any, name?: string, moduleUrl?: string, prefix?: string, value?: any} = {}) {
this.reference = reference;
this.name = name;
this.prefix = prefix;
this.moduleUrl = moduleUrl;
this.value = value;
}
get identifier(): CompileIdentifierMetadata { return this; }
}
export class CompileDiDependencyMetadata {
isAttribute: boolean;
isSelf: boolean;
isHost: boolean;
isSkipSelf: boolean;
isOptional: boolean;
isValue: boolean;
query: CompileQueryMetadata;
viewQuery: CompileQueryMetadata;
token: CompileTokenMetadata;
value: any;
constructor(
{isAttribute, isSelf, isHost, isSkipSelf, isOptional, isValue, query, viewQuery, token,
value}: {
isAttribute?: boolean,
isSelf?: boolean,
isHost?: boolean,
isSkipSelf?: boolean,
isOptional?: boolean,
isValue?: boolean,
query?: CompileQueryMetadata,
viewQuery?: CompileQueryMetadata,
token?: CompileTokenMetadata,
value?: any
} = {}) {
this.isAttribute = normalizeBool(isAttribute);
this.isSelf = normalizeBool(isSelf);
this.isHost = normalizeBool(isHost);
this.isSkipSelf = normalizeBool(isSkipSelf);
this.isOptional = normalizeBool(isOptional);
this.isValue = normalizeBool(isValue);
this.query = query;
this.viewQuery = viewQuery;
this.token = token;
this.value = value;
}
}
export class CompileProviderMetadata {
token: CompileTokenMetadata;
useClass: CompileTypeMetadata;
useValue: any;
useExisting: CompileTokenMetadata;
useFactory: CompileFactoryMetadata;
deps: CompileDiDependencyMetadata[];
multi: boolean;
constructor({token, useClass, useValue, useExisting, useFactory, deps, multi}: {
token?: CompileTokenMetadata,
useClass?: CompileTypeMetadata,
useValue?: any,
useExisting?: CompileTokenMetadata,
useFactory?: CompileFactoryMetadata,
deps?: CompileDiDependencyMetadata[],
multi?: boolean
}) {
this.token = token;
this.useClass = useClass;
this.useValue = useValue;
this.useExisting = useExisting;
this.useFactory = useFactory;
this.deps = normalizeBlank(deps);
this.multi = normalizeBool(multi);
}
}
export class CompileFactoryMetadata extends CompileIdentifierMetadata {
diDeps: CompileDiDependencyMetadata[];
constructor({reference, name, moduleUrl, prefix, diDeps, value}: {
reference?: Function,
name?: string,
prefix?: string,
moduleUrl?: string,
value?: boolean,
diDeps?: CompileDiDependencyMetadata[]
}) {
super({reference: reference, name: name, prefix: prefix, moduleUrl: moduleUrl, value: value});
this.diDeps = _normalizeArray(diDeps);
}
}
export class CompileTokenMetadata implements CompileMetadataWithIdentifier {
value: any;
identifier: CompileIdentifierMetadata;
identifierIsInstance: boolean;
constructor(
{value, identifier, identifierIsInstance}:
{value?: any, identifier?: CompileIdentifierMetadata, identifierIsInstance?: boolean}) {
this.value = value;
this.identifier = identifier;
this.identifierIsInstance = normalizeBool(identifierIsInstance);
}
get reference(): any {
if (isPresent(this.identifier)) {
return this.identifier.reference;
} else {
return this.value;
}
}
get name(): string {
return isPresent(this.value) ? sanitizeIdentifier(this.value) : this.identifier.name;
}
}
/**
* Metadata regarding compilation of a type.
*/
export class CompileTypeMetadata extends CompileIdentifierMetadata {
isHost: boolean;
diDeps: CompileDiDependencyMetadata[];
lifecycleHooks: LifecycleHooks[];
constructor({reference, name, moduleUrl, prefix, isHost, value, diDeps, lifecycleHooks}: {
reference?: Type<any>,
name?: string,
moduleUrl?: string,
prefix?: string,
isHost?: boolean,
value?: any,
diDeps?: CompileDiDependencyMetadata[],
lifecycleHooks?: LifecycleHooks[];
} = {}) {
super({reference: reference, name: name, moduleUrl: moduleUrl, prefix: prefix, value: value});
this.isHost = normalizeBool(isHost);
this.diDeps = _normalizeArray(diDeps);
this.lifecycleHooks = _normalizeArray(lifecycleHooks);
}
}
export class CompileQueryMetadata {
selectors: Array<CompileTokenMetadata>;
descendants: boolean;
first: boolean;
propertyName: string;
read: CompileTokenMetadata;
constructor({selectors, descendants, first, propertyName, read}: {
selectors?: Array<CompileTokenMetadata>,
descendants?: boolean,
first?: boolean,
propertyName?: string,
read?: CompileTokenMetadata
} = {}) {
this.selectors = selectors;
this.descendants = normalizeBool(descendants);
this.first = normalizeBool(first);
this.propertyName = propertyName;
this.read = read;
}
}
/**
* Metadata about a stylesheet
*/
export class CompileStylesheetMetadata {
moduleUrl: string;
styles: string[];
styleUrls: string[];
constructor(
{moduleUrl, styles,
styleUrls}: {moduleUrl?: string, styles?: string[], styleUrls?: string[]} = {}) {
this.moduleUrl = moduleUrl;
this.styles = _normalizeArray(styles);
this.styleUrls = _normalizeArray(styleUrls);
}
}
/**
* Metadata regarding compilation of a template.
*/
export class CompileTemplateMetadata {
encapsulation: ViewEncapsulation;
template: string;
templateUrl: string;
styles: string[];
styleUrls: string[];
externalStylesheets: CompileStylesheetMetadata[];
animations: CompileAnimationEntryMetadata[];
ngContentSelectors: string[];
interpolation: [string, string];
constructor(
{encapsulation, template, templateUrl, styles, styleUrls, externalStylesheets, animations,
ngContentSelectors, interpolation}: {
encapsulation?: ViewEncapsulation,
template?: string,
templateUrl?: string,
styles?: string[],
styleUrls?: string[],
externalStylesheets?: CompileStylesheetMetadata[],
ngContentSelectors?: string[],
animations?: CompileAnimationEntryMetadata[],
interpolation?: [string, string]
} = {}) {
this.encapsulation = encapsulation;
this.template = template;
this.templateUrl = templateUrl;
this.styles = _normalizeArray(styles);
this.styleUrls = _normalizeArray(styleUrls);
this.externalStylesheets = _normalizeArray(externalStylesheets);
this.animations = isPresent(animations) ? ListWrapper.flatten(animations) : [];
this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : [];
if (isPresent(interpolation) && interpolation.length != 2) {
throw new Error(`'interpolation' should have a start and an end symbol.`);
}
this.interpolation = interpolation;
}
}
/**
* Metadata regarding compilation of a directive.
*/
export class CompileDirectiveMetadata implements CompileMetadataWithIdentifier {
static create(
{type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host, providers,
viewProviders, queries, viewQueries, entryComponents, template}: {
type?: CompileTypeMetadata,
isComponent?: boolean,
selector?: string,
exportAs?: string,
changeDetection?: ChangeDetectionStrategy,
inputs?: string[],
outputs?: string[],
host?: {[key: string]: string},
providers?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
viewProviders?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileTypeMetadata[],
viewDirectives?: CompileTypeMetadata[],
viewPipes?: CompileTypeMetadata[],
template?: CompileTemplateMetadata
} = {}): CompileDirectiveMetadata {
var hostListeners: {[key: string]: string} = {};
var hostProperties: {[key: string]: string} = {};
var hostAttributes: {[key: string]: string} = {};
if (isPresent(host)) {
StringMapWrapper.forEach(host, (value: string, key: string) => {
const matches = key.match(HOST_REG_EXP);
if (matches === null) {
hostAttributes[key] = value;
} else if (isPresent(matches[1])) {
hostProperties[matches[1]] = value;
} else if (isPresent(matches[2])) {
hostListeners[matches[2]] = value;
}
});
}
var inputsMap: {[key: string]: string} = {};
if (isPresent(inputs)) {
inputs.forEach((bindConfig: string) => {
// canonical syntax: `dirProp: elProp`
// if there is no `:`, use dirProp = elProp
var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]);
inputsMap[parts[0]] = parts[1];
});
}
var outputsMap: {[key: string]: string} = {};
if (isPresent(outputs)) {
outputs.forEach((bindConfig: string) => {
// canonical syntax: `dirProp: elProp`
// if there is no `:`, use dirProp = elProp
var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]);
outputsMap[parts[0]] = parts[1];
});
}
return new CompileDirectiveMetadata({
type,
isComponent: normalizeBool(isComponent), selector, exportAs, changeDetection,
inputs: inputsMap,
outputs: outputsMap,
hostListeners,
hostProperties,
hostAttributes,
providers,
viewProviders,
queries,
viewQueries,
entryComponents,
template,
});
}
type: CompileTypeMetadata;
isComponent: boolean;
selector: string;
exportAs: string;
changeDetection: ChangeDetectionStrategy;
inputs: {[key: string]: string};
outputs: {[key: string]: string};
hostListeners: {[key: string]: string};
hostProperties: {[key: string]: string};
hostAttributes: {[key: string]: string};
providers: CompileProviderMetadata[];
viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[];
viewQueries: CompileQueryMetadata[];
// Note: Need to keep types here to prevent cycles!
entryComponents: CompileTypeMetadata[];
template: CompileTemplateMetadata;
constructor(
{type, isComponent, selector, exportAs, changeDetection, inputs, outputs, hostListeners,
hostProperties, hostAttributes, providers, viewProviders, queries, viewQueries,
entryComponents, template}: {
type?: CompileTypeMetadata,
isComponent?: boolean,
selector?: string,
exportAs?: string,
changeDetection?: ChangeDetectionStrategy,
inputs?: {[key: string]: string},
outputs?: {[key: string]: string},
hostListeners?: {[key: string]: string},
hostProperties?: {[key: string]: string},
hostAttributes?: {[key: string]: string},
providers?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
viewProviders?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[],
entryComponents?: CompileTypeMetadata[],
viewDirectives?: CompileTypeMetadata[],
viewPipes?: CompileTypeMetadata[],
template?: CompileTemplateMetadata,
} = {}) {
this.type = type;
this.isComponent = isComponent;
this.selector = selector;
this.exportAs = exportAs;
this.changeDetection = changeDetection;
this.inputs = inputs;
this.outputs = outputs;
this.hostListeners = hostListeners;
this.hostProperties = hostProperties;
this.hostAttributes = hostAttributes;
this.providers = _normalizeArray(providers);
this.viewProviders = _normalizeArray(viewProviders);
this.queries = _normalizeArray(queries);
this.viewQueries = _normalizeArray(viewQueries);
this.entryComponents = _normalizeArray(entryComponents);
this.template = template;
}
get identifier(): CompileIdentifierMetadata { return this.type; }
}
/**
* Construct {@link CompileDirectiveMetadata} from {@link ComponentTypeMetadata} and a selector.
*/
export function createHostComponentMeta(compMeta: CompileDirectiveMetadata):
CompileDirectiveMetadata {
var template = CssSelector.parse(compMeta.selector)[0].getMatchingElementTemplate();
return CompileDirectiveMetadata.create({
type: new CompileTypeMetadata({
reference: Object,
name: `${compMeta.type.name}_Host`,
moduleUrl: compMeta.type.moduleUrl,
isHost: true
}),
template: new CompileTemplateMetadata({
encapsulation: ViewEncapsulation.None,
template: template,
templateUrl: '',
styles: [],
styleUrls: [],
ngContentSelectors: [],
animations: []
}),
changeDetection: ChangeDetectionStrategy.Default,
inputs: [],
outputs: [],
host: {},
isComponent: true,
selector: '*',
providers: [],
viewProviders: [],
queries: [],
viewQueries: []
});
}
export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
type: CompileTypeMetadata;
name: string;
pure: boolean;
constructor({type, name, pure}: {
type?: CompileTypeMetadata,
name?: string,
pure?: boolean,
} = {}) {
this.type = type;
this.name = name;
this.pure = normalizeBool(pure);
}
get identifier(): CompileIdentifierMetadata { return this.type; }
}
/**
* Metadata regarding compilation of a directive.
*/
export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
type: CompileTypeMetadata;
declaredDirectives: CompileDirectiveMetadata[];
exportedDirectives: CompileDirectiveMetadata[];
declaredPipes: CompilePipeMetadata[];
exportedPipes: CompilePipeMetadata[];
// Note: See CompileDirectiveMetadata.entryComponents why this has to be a type.
entryComponents: CompileTypeMetadata[];
bootstrapComponents: CompileTypeMetadata[];
providers: CompileProviderMetadata[];
importedModules: CompileNgModuleMetadata[];
exportedModules: CompileNgModuleMetadata[];
schemas: SchemaMetadata[];
id: string;
transitiveModule: TransitiveCompileNgModuleMetadata;
constructor(
{type, providers, declaredDirectives, exportedDirectives, declaredPipes, exportedPipes,
entryComponents, bootstrapComponents, importedModules, exportedModules, schemas,
transitiveModule, id}: {
type?: CompileTypeMetadata,
providers?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
declaredDirectives?: CompileDirectiveMetadata[],
exportedDirectives?: CompileDirectiveMetadata[],
declaredPipes?: CompilePipeMetadata[],
exportedPipes?: CompilePipeMetadata[],
entryComponents?: CompileTypeMetadata[],
bootstrapComponents?: CompileTypeMetadata[],
importedModules?: CompileNgModuleMetadata[],
exportedModules?: CompileNgModuleMetadata[],
transitiveModule?: TransitiveCompileNgModuleMetadata,
schemas?: SchemaMetadata[],
id?: string
} = {}) {
this.type = type;
this.declaredDirectives = _normalizeArray(declaredDirectives);
this.exportedDirectives = _normalizeArray(exportedDirectives);
this.declaredPipes = _normalizeArray(declaredPipes);
this.exportedPipes = _normalizeArray(exportedPipes);
this.providers = _normalizeArray(providers);
this.entryComponents = _normalizeArray(entryComponents);
this.bootstrapComponents = _normalizeArray(bootstrapComponents);
this.importedModules = _normalizeArray(importedModules);
this.exportedModules = _normalizeArray(exportedModules);
this.schemas = _normalizeArray(schemas);
this.id = id;
this.transitiveModule = transitiveModule;
}
get identifier(): CompileIdentifierMetadata { return this.type; }
}
export class TransitiveCompileNgModuleMetadata {
directivesSet = new Set<Type<any>>();
pipesSet = new Set<Type<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));
}
}
export function removeIdentifierDuplicates<T extends CompileMetadataWithIdentifier>(items: T[]):
T[] {
const map = new Map<any, T>();
items.forEach((item) => {
if (!map.get(item.identifier.reference)) {
map.set(item.identifier.reference, item);
}
});
return MapWrapper.values(map);
}
function _normalizeArray(obj: any[]): any[] {
return isPresent(obj) ? obj : [];
}
export function isStaticSymbol(value: any): value is StaticSymbol {
return isStringMap(value) && isPresent(value['name']) && isPresent(value['filePath']);
}
export interface StaticSymbol {
name: string;
filePath: string;
}
export class ProviderMeta {
token: any;
useClass: Type<any>;
useValue: any;
useExisting: any;
useFactory: Function;
dependencies: Object[];
multi: boolean;
constructor(token: any, {useClass, useValue, useExisting, useFactory, deps, multi}: {
useClass?: Type<any>,
useValue?: any,
useExisting?: any,
useFactory?: Function,
deps?: Object[],
multi?: boolean
}) {
this.token = token;
this.useClass = useClass;
this.useValue = useValue;
this.useExisting = useExisting;
this.useFactory = useFactory;
this.dependencies = deps;
this.multi = !!multi;
}
}