Tobias Bosch 46b212706b refactor(core): change module semantics
This contains major changes to the compiler, bootstrap of the platforms
and test environment initialization.

Main part of #10043
Closes #10164

BREAKING CHANGE:
- Semantics and name of `@AppModule` (now `@NgModule`) changed quite a bit.
  This is actually not breaking as `@AppModules` were not part of rc.4.
  We will have detailed docs on `@NgModule` separately.
- `coreLoadAndBootstrap` and `coreBootstrap` can't be used any more (without migration support).
  Use `bootstrapModule` / `bootstrapModuleFactory` instead.
- All Components listed in routes have to be part of the `declarations` of an NgModule.
  Either directly on the bootstrap module / lazy loaded module, or in an NgModule imported by them.
2016-07-26 07:04:10 -07:00

703 lines
23 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, ViewEncapsulation} from '@angular/core';
import {CHANGE_DETECTION_STRATEGY_VALUES, LIFECYCLE_HOOKS_VALUES, LifecycleHooks, VIEW_ENCAPSULATION_VALUES, reflector} from '../core_private';
import {ListWrapper, StringMapWrapper} from '../src/facade/collection';
import {BaseException, unimplemented} from '../src/facade/exceptions';
import {NumberWrapper, RegExpWrapper, Type, isArray, isBlank, isBoolean, isNumber, isPresent, isString, isStringMap, normalizeBlank, normalizeBool, serializeEnum} from '../src/facade/lang';
import {CssSelector} from './selector';
import {getUrlScheme} from './url_resolver';
import {sanitizeIdentifier, splitAtColon} from './util';
// 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]+)$/g;
const UNDEFINED = new Object();
export abstract class CompileMetadataWithIdentifier {
get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
get runtimeCacheKey(): any { return unimplemented(); }
get assetCacheKey(): any { return unimplemented(); }
equalsTo(id2: CompileMetadataWithIdentifier): boolean { return 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 {
runtime: any;
name: string;
prefix: string;
moduleUrl: string;
value: any;
private _assetCacheKey: any = UNDEFINED;
constructor(
{runtime, name, moduleUrl, prefix, value}:
{runtime?: any, name?: string, moduleUrl?: string, prefix?: string, value?: any} = {}) {
this.runtime = runtime;
this.name = name;
this.prefix = prefix;
this.moduleUrl = moduleUrl;
this.value = value;
}
get identifier(): CompileIdentifierMetadata { return this; }
get runtimeCacheKey(): any { return this.identifier.runtime; }
get assetCacheKey(): any {
if (this._assetCacheKey === UNDEFINED) {
if (isPresent(this.moduleUrl) && isPresent(getUrlScheme(this.moduleUrl))) {
var uri = reflector.importUri({'filePath': this.moduleUrl, 'name': this.name});
this._assetCacheKey = `${this.name}|${uri}`;
} else {
this._assetCacheKey = null;
}
}
return this._assetCacheKey;
}
equalsTo(id2: CompileIdentifierMetadata): boolean {
var rk = this.runtimeCacheKey;
var ak = this.assetCacheKey;
return (isPresent(rk) && rk == id2.runtimeCacheKey) ||
(isPresent(ak) && ak == id2.assetCacheKey);
}
}
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({runtime, name, moduleUrl, prefix, diDeps, value}: {
runtime?: Function,
name?: string,
prefix?: string,
moduleUrl?: string,
value?: boolean,
diDeps?: CompileDiDependencyMetadata[]
}) {
super({runtime: runtime, 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 runtimeCacheKey(): any {
if (isPresent(this.identifier)) {
return this.identifier.runtimeCacheKey;
} else {
return this.value;
}
}
get assetCacheKey(): any {
if (isPresent(this.identifier)) {
return this.identifier.assetCacheKey;
} else {
return this.value;
}
}
equalsTo(token2: CompileTokenMetadata): boolean {
var rk = this.runtimeCacheKey;
var ak = this.assetCacheKey;
return (isPresent(rk) && rk == token2.runtimeCacheKey) ||
(isPresent(ak) && ak == token2.assetCacheKey);
}
get name(): string {
return isPresent(this.value) ? sanitizeIdentifier(this.value) : this.identifier.name;
}
}
/**
* Note: We only need this in places where we need to support identifiers that
* don't have a `runtime` value given by the `StaticReflector`. E.g. see the `identifiers`
* file where we have some identifiers hard coded by name/module path.
*
* TODO(tbosch): Eventually, all of these places should go through the static reflector
* as well, providing them with a valid `StaticSymbol` that is again a singleton.
*/
export class CompileIdentifierMap<KEY extends CompileMetadataWithIdentifier, VALUE> {
private _valueMap = new Map<any, VALUE>();
private _values: VALUE[] = [];
private _tokens: KEY[] = [];
add(token: KEY, value: VALUE) {
var existing = this.get(token);
if (isPresent(existing)) {
throw new BaseException(
`Cannot overwrite in a CompileIdentifierMap! Token: ${token.identifier.name}`);
}
this._tokens.push(token);
this._values.push(value);
var rk = token.runtimeCacheKey;
if (isPresent(rk)) {
this._valueMap.set(rk, value);
}
var ak = token.assetCacheKey;
if (isPresent(ak)) {
this._valueMap.set(ak, value);
}
}
get(token: KEY): VALUE {
var rk = token.runtimeCacheKey;
var ak = token.assetCacheKey;
var result: VALUE;
if (isPresent(rk)) {
result = this._valueMap.get(rk);
}
if (isBlank(result) && isPresent(ak)) {
result = this._valueMap.get(ak);
}
return result;
}
keys(): KEY[] { return this._tokens; }
values(): VALUE[] { return this._values; }
get size(): number { return this._values.length; }
}
/**
* Metadata regarding compilation of a type.
*/
export class CompileTypeMetadata extends CompileIdentifierMetadata {
isHost: boolean;
diDeps: CompileDiDependencyMetadata[];
constructor({runtime, name, moduleUrl, prefix, isHost, value, diDeps}: {
runtime?: Type,
name?: string,
moduleUrl?: string,
prefix?: string,
isHost?: boolean,
value?: any,
diDeps?: CompileDiDependencyMetadata[]
} = {}) {
super({runtime: runtime, name: name, moduleUrl: moduleUrl, prefix: prefix, value: value});
this.isHost = normalizeBool(isHost);
this.diDeps = _normalizeArray(diDeps);
}
}
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 BaseException(`'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,
lifecycleHooks, providers, viewProviders, queries, viewQueries, precompile, template}: {
type?: CompileTypeMetadata,
isComponent?: boolean,
selector?: string,
exportAs?: string,
changeDetection?: ChangeDetectionStrategy,
inputs?: string[],
outputs?: string[],
host?: {[key: string]: string},
lifecycleHooks?: LifecycleHooks[],
providers?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
viewProviders?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[],
precompile?: 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) => {
var matches = RegExpWrapper.firstMatch(HOST_REG_EXP, key);
if (isBlank(matches)) {
hostAttributes[key] = value;
} else if (isPresent(matches[1])) {
hostProperties[matches[1]] = value;
} else if (isPresent(matches[2])) {
hostListeners[matches[2]] = value;
} else if (isPresent(matches[3])) {
hostProperties[matches[3]] = 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,
lifecycleHooks: isPresent(lifecycleHooks) ? lifecycleHooks : [],
providers,
viewProviders,
queries,
viewQueries,
precompile,
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};
lifecycleHooks: LifecycleHooks[];
providers: CompileProviderMetadata[];
viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[];
viewQueries: CompileQueryMetadata[];
// Note: Need to keep types here to prevent cycles!
precompile: CompileTypeMetadata[];
template: CompileTemplateMetadata;
constructor(
{type, isComponent, selector, exportAs, changeDetection, inputs, outputs, hostListeners,
hostProperties, hostAttributes, lifecycleHooks, providers, viewProviders, queries,
viewQueries, precompile, 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},
lifecycleHooks?: LifecycleHooks[],
providers?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
viewProviders?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
queries?: CompileQueryMetadata[],
viewQueries?: CompileQueryMetadata[],
precompile?: 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.lifecycleHooks = _normalizeArray(lifecycleHooks);
this.providers = _normalizeArray(providers);
this.viewProviders = _normalizeArray(viewProviders);
this.queries = _normalizeArray(queries);
this.viewQueries = _normalizeArray(viewQueries);
this.precompile = _normalizeArray(precompile);
this.template = template;
}
get identifier(): CompileIdentifierMetadata { return this.type; }
get runtimeCacheKey(): any { return this.type.runtimeCacheKey; }
get assetCacheKey(): any { return this.type.assetCacheKey; }
equalsTo(other: CompileMetadataWithIdentifier): boolean {
return this.type.equalsTo(other.identifier);
}
}
/**
* 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({
runtime: Object,
name: `${compMeta.type.name}_Host`,
moduleUrl: compMeta.type.moduleUrl,
isHost: true
}),
template: new CompileTemplateMetadata({
template: template,
templateUrl: '',
styles: [],
styleUrls: [],
ngContentSelectors: [],
animations: []
}),
changeDetection: ChangeDetectionStrategy.Default,
inputs: [],
outputs: [],
host: {},
lifecycleHooks: [],
isComponent: true,
selector: '*',
providers: [],
viewProviders: [],
queries: [],
viewQueries: []
});
}
export class CompilePipeMetadata implements CompileMetadataWithIdentifier {
type: CompileTypeMetadata;
name: string;
pure: boolean;
lifecycleHooks: LifecycleHooks[];
constructor({type, name, pure, lifecycleHooks}: {
type?: CompileTypeMetadata,
name?: string,
pure?: boolean,
lifecycleHooks?: LifecycleHooks[]
} = {}) {
this.type = type;
this.name = name;
this.pure = normalizeBool(pure);
this.lifecycleHooks = _normalizeArray(lifecycleHooks);
}
get identifier(): CompileIdentifierMetadata { return this.type; }
get runtimeCacheKey(): any { return this.type.runtimeCacheKey; }
get assetCacheKey(): any { return this.type.assetCacheKey; }
equalsTo(other: CompileMetadataWithIdentifier): boolean {
return this.type.equalsTo(other.identifier);
}
}
/**
* Metadata regarding compilation of a directive.
*/
export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
type: CompileTypeMetadata;
declaredDirectives: CompileDirectiveMetadata[];
exportedDirectives: CompileDirectiveMetadata[];
declaredPipes: CompilePipeMetadata[];
exportedPipes: CompilePipeMetadata[];
// Note: See CompileDirectiveMetadata.precompile why this has to be a type.
precompile: CompileTypeMetadata[];
providers: CompileProviderMetadata[];
importedModules: CompileNgModuleMetadata[];
exportedModules: CompileNgModuleMetadata[];
transitiveModule: TransitiveCompileNgModuleMetadata;
constructor(
{type, providers, declaredDirectives, exportedDirectives, declaredPipes, exportedPipes,
precompile, importedModules, exportedModules, transitiveModule}: {
type?: CompileTypeMetadata,
providers?:
Array<CompileProviderMetadata|CompileTypeMetadata|CompileIdentifierMetadata|any[]>,
declaredDirectives?: CompileDirectiveMetadata[],
exportedDirectives?: CompileDirectiveMetadata[],
declaredPipes?: CompilePipeMetadata[],
exportedPipes?: CompilePipeMetadata[],
precompile?: CompileTypeMetadata[],
importedModules?: CompileNgModuleMetadata[],
exportedModules?: CompileNgModuleMetadata[],
transitiveModule?: TransitiveCompileNgModuleMetadata
} = {}) {
this.type = type;
this.declaredDirectives = _normalizeArray(declaredDirectives);
this.exportedDirectives = _normalizeArray(exportedDirectives);
this.declaredPipes = _normalizeArray(declaredPipes);
this.exportedPipes = _normalizeArray(exportedPipes);
this.providers = _normalizeArray(providers);
this.precompile = _normalizeArray(precompile);
this.importedModules = _normalizeArray(importedModules);
this.exportedModules = _normalizeArray(exportedModules);
this.transitiveModule = transitiveModule;
}
get identifier(): CompileIdentifierMetadata { return this.type; }
get runtimeCacheKey(): any { return this.type.runtimeCacheKey; }
get assetCacheKey(): any { return this.type.assetCacheKey; }
equalsTo(other: CompileMetadataWithIdentifier): boolean {
return this.type.equalsTo(other.identifier);
}
}
export class TransitiveCompileNgModuleMetadata {
directivesSet = new Set<Type>();
pipesSet = new Set<Type>();
constructor(
public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[],
public precompile: CompileTypeMetadata[], public directives: CompileDirectiveMetadata[],
public pipes: CompilePipeMetadata[]) {
directives.forEach(dir => this.directivesSet.add(dir.type.runtime));
pipes.forEach(pipe => this.pipesSet.add(pipe.type.runtime));
}
}
export function removeIdentifierDuplicates<T extends CompileMetadataWithIdentifier>(items: T[]):
T[] {
const map = new CompileIdentifierMap<T, T>();
items.forEach((item) => {
if (!map.get(item)) {
map.add(item, item);
}
});
return map.keys();
}
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;
}