refactor(compiler): allow to serialize and deserialize DirectiveMetadata

This commit is contained in:
Tobias Bosch
2015-09-11 13:35:46 -07:00
parent 67c79ba3f6
commit 71cbb49672
10 changed files with 443 additions and 157 deletions

View File

@ -1,19 +1,36 @@
import {isPresent, normalizeBool} from 'angular2/src/core/facade/lang';
import {HtmlAst} from './html_ast';
import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection/change_detection';
import {isPresent, normalizeBool, serializeEnum, Type} from 'angular2/src/core/facade/lang';
import {
ChangeDetectionStrategy,
changeDetectionStrategyFromJson
} from 'angular2/src/core/change_detection/change_detection';
import {ViewEncapsulation, viewEncapsulationFromJson} from 'angular2/src/core/render/api';
export class TypeMetadata {
id: number;
type: any;
type: Type;
typeName: string;
typeUrl: string;
constructor({id, type, typeName, typeUrl}:
{id?: number, type?: string, typeName?: string, typeUrl?: string} = {}) {
{id?: number, type?: Type, typeName?: string, typeUrl?: string} = {}) {
this.id = id;
this.type = type;
this.typeName = typeName;
this.typeUrl = typeUrl;
}
static fromJson(data: StringMap<string, any>): TypeMetadata {
return new TypeMetadata(
{id: data['id'], type: data['type'], typeName: data['typeName'], typeUrl: data['typeUrl']});
}
toJson(): StringMap<string, any> {
return {
// Note: Runtime type can't be serialized...
'id': this.id,
'typeName': this.typeName,
'typeUrl': this.typeUrl
};
}
}
export class ChangeDetectionMetadata {
@ -44,7 +61,7 @@ export class ChangeDetectionMetadata {
callOnChanges?: boolean,
callDoCheck?: boolean,
callOnInit?: boolean
}) {
} = {}) {
this.changeDetection = changeDetection;
this.properties = properties;
this.events = events;
@ -58,60 +75,102 @@ export class ChangeDetectionMetadata {
this.callDoCheck = callDoCheck;
this.callOnInit = callOnInit;
}
static fromJson(data: StringMap<string, any>): ChangeDetectionMetadata {
return new ChangeDetectionMetadata({
changeDetection: isPresent(data['changeDetection']) ?
changeDetectionStrategyFromJson(data['changeDetection']) :
data['changeDetection'],
properties: data['properties'],
events: data['events'],
hostListeners: data['hostListeners'],
hostProperties: data['hostProperties'],
callAfterContentInit: data['callAfterContentInit'],
callAfterContentChecked: data['callAfterContentChecked'],
callAfterViewInit: data['callAfterViewInit'],
callAfterViewChecked: data['callAfterViewChecked'],
callOnChanges: data['callOnChanges'],
callDoCheck: data['callDoCheck'],
callOnInit: data['callOnInit']
});
}
toJson(): StringMap<string, any> {
return {
'changeDetection': isPresent(this.changeDetection) ? serializeEnum(this.changeDetection) :
this.changeDetection,
'properties': this.properties,
'events': this.events,
'hostListeners': this.hostListeners,
'hostProperties': this.hostProperties,
'callAfterContentInit': this.callAfterContentInit,
'callAfterContentChecked': this.callAfterContentChecked,
'callAfterViewInit': this.callAfterViewInit,
'callAfterViewChecked': this.callAfterViewChecked,
'callOnChanges': this.callOnChanges,
'callDoCheck': this.callDoCheck,
'callOnInit': this.callOnInit
};
}
}
export class TemplateMetadata {
encapsulation: ViewEncapsulation;
nodes: HtmlAst[];
template: string;
styles: string[];
styleAbsUrls: string[];
ngContentSelectors: string[];
constructor({encapsulation, nodes, styles, styleAbsUrls, ngContentSelectors}: {
constructor({encapsulation, template, styles, styleAbsUrls, ngContentSelectors}: {
encapsulation?: ViewEncapsulation,
nodes?: HtmlAst[],
template?: string,
styles?: string[],
styleAbsUrls?: string[],
ngContentSelectors?: string[]
}) {
} = {}) {
this.encapsulation = encapsulation;
this.nodes = nodes;
this.template = template;
this.styles = styles;
this.styleAbsUrls = styleAbsUrls;
this.ngContentSelectors = ngContentSelectors;
}
static fromJson(data: StringMap<string, any>):TemplateMetadata {
return new TemplateMetadata({
encapsulation: isPresent(data['encapsulation']) ?
viewEncapsulationFromJson(data['encapsulation']) :
data['encapsulation'],
template: data['template'],
styles: data['styles'],
styleAbsUrls: data['styleAbsUrls'],
ngContentSelectors: data['ngContentSelectors'],
});
}
toJson(): StringMap<string, any> {
return {
'encapsulation':
isPresent(this.encapsulation) ? serializeEnum(this.encapsulation) : this.encapsulation,
'template': this.template,
'styles': this.styles,
'styleAbsUrls': this.styleAbsUrls,
'ngContentSelectors': this.ngContentSelectors,
};
}
}
/**
* How the template and styles of a view should be encapsulated.
*/
export enum ViewEncapsulation {
/**
* Emulate scoping of styles by preprocessing the style rules
* and adding additional attributes to elements. This is the default.
*/
Emulated,
/**
* Uses the native mechanism of the renderer. For the DOM this means creating a ShadowRoot.
*/
Native,
/**
* Don't scope the template nor the styles.
*/
None
}
export class DirectiveMetadata {
type: TypeMetadata;
isComponent: boolean;
selector: string;
hostAttributes: Map<string, string>;
hostAttributes: StringMap<string, string>;
changeDetection: ChangeDetectionMetadata;
template: TemplateMetadata;
constructor({type, isComponent, selector, hostAttributes, changeDetection, template}: {
type?: TypeMetadata,
isComponent?: boolean,
selector?: string,
hostAttributes?: Map<string, string>,
hostAttributes?: StringMap<string, string>,
changeDetection?: ChangeDetectionMetadata,
template?: TemplateMetadata
} = {}) {
@ -122,6 +181,32 @@ export class DirectiveMetadata {
this.changeDetection = changeDetection;
this.template = template;
}
static fromJson(data: StringMap<string, any>): DirectiveMetadata {
return new DirectiveMetadata({
type: isPresent(data['type']) ? TypeMetadata.fromJson(data['type']) : data['type'],
isComponent: data['isComponent'],
selector: data['selector'],
hostAttributes: data['hostAttributes'],
changeDetection: isPresent(data['changeDetection']) ?
ChangeDetectionMetadata.fromJson(data['changeDetection']) :
data['changeDetection'],
template: isPresent(data['template']) ? TemplateMetadata.fromJson(data['template']) :
data['template']
});
}
toJson(): StringMap<string, any> {
return {
'type': isPresent(this.type) ? this.type.toJson() : this.type,
'isComponent': this.isComponent,
'selector': this.selector,
'hostAttributes': this.hostAttributes,
'changeDetection':
isPresent(this.changeDetection) ? this.changeDetection.toJson() : this.changeDetection,
'template': isPresent(this.template) ? this.template.toJson() : this.template
};
}
}
export class SourceModule {

View File

@ -2,35 +2,35 @@ import {isPresent} from 'angular2/src/core/facade/lang';
export interface HtmlAst {
sourceInfo: string;
visit(visitor: HtmlAstVisitor): any;
visit(visitor: HtmlAstVisitor, context: any): any;
}
export class HtmlTextAst implements HtmlAst {
constructor(public value: string, public sourceInfo: string) {}
visit(visitor: HtmlAstVisitor): any { return visitor.visitText(this); }
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitText(this, context); }
}
export class HtmlAttrAst implements HtmlAst {
constructor(public name: string, public value: string, public sourceInfo: string) {}
visit(visitor: HtmlAstVisitor): any { return visitor.visitAttr(this); }
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitAttr(this, context); }
}
export class HtmlElementAst implements HtmlAst {
constructor(public name: string, public attrs: HtmlAttrAst[], public children: HtmlAst[],
public sourceInfo: string) {}
visit(visitor: HtmlAstVisitor): any { return visitor.visitElement(this); }
visit(visitor: HtmlAstVisitor, context: any): any { return visitor.visitElement(this, context); }
}
export interface HtmlAstVisitor {
visitElement(ast: HtmlElementAst): any;
visitAttr(ast: HtmlAttrAst): any;
visitText(ast: HtmlTextAst): any;
visitElement(ast: HtmlElementAst, context: any): any;
visitAttr(ast: HtmlAttrAst, context: any): any;
visitText(ast: HtmlTextAst, context: any): any;
}
export function htmlVisitAll(visitor: HtmlAstVisitor, asts: HtmlAst[]): any[] {
export function htmlVisitAll(visitor: HtmlAstVisitor, asts: HtmlAst[], context: any = null): any[] {
var result = [];
asts.forEach(ast => {
var astResult = ast.visit(visitor);
var astResult = ast.visit(visitor, context);
if (isPresent(astResult)) {
result.push(astResult);
}

View File

@ -7,7 +7,16 @@ import {
} from 'angular2/src/core/facade/lang';
import {DOM} from 'angular2/src/core/dom/dom_adapter';
import {HtmlAst, HtmlAttrAst, HtmlTextAst, HtmlElementAst} from './html_ast';
import {
HtmlAst,
HtmlAttrAst,
HtmlTextAst,
HtmlElementAst,
HtmlAstVisitor,
htmlVisitAll
} from './html_ast';
import {escapeDoubleQuoteString} from './util';
const NG_NON_BINDABLE = 'ng-non-bindable';
@ -16,6 +25,12 @@ export class HtmlParser {
var root = DOM.createTemplate(template);
return parseChildNodes(root, sourceInfo);
}
unparse(nodes: HtmlAst[]): string {
var visitor = new UnparseVisitor();
var parts = [];
htmlVisitAll(visitor, nodes, parts);
return parts.join('');
}
}
function parseText(text: Text, indexInParent: number, parentSourceInfo: string): HtmlTextAst {
@ -92,3 +107,27 @@ function ignoreChildren(attrs: HtmlAttrAst[]): boolean {
}
return false;
}
class UnparseVisitor implements HtmlAstVisitor {
visitElement(ast: HtmlElementAst, parts: string[]): any {
parts.push(`<${ast.name}`);
var attrs = [];
htmlVisitAll(this, ast.attrs, attrs);
if (ast.attrs.length > 0) {
parts.push(' ');
parts.push(attrs.join(' '));
}
parts.push(`>`);
htmlVisitAll(this, ast.children, parts);
parts.push(`</${ast.name}>`);
return null;
}
visitAttr(ast: HtmlAttrAst, parts: string[]): any {
parts.push(`${ast.name}=${escapeDoubleQuoteString(ast.value)}`);
return null;
}
visitText(ast: HtmlTextAst, parts: string[]): any {
parts.push(ast.value);
return null;
}
}

View File

@ -1,5 +1,6 @@
import {TypeMetadata, TemplateMetadata, ViewEncapsulation} from './api';
import {isPresent} from 'angular2/src/core/facade/lang';
import {TypeMetadata, TemplateMetadata} from './api';
import {ViewEncapsulation} from 'angular2/src/core/render/api';
import {isPresent, isBlank} from 'angular2/src/core/facade/lang';
import {Promise, PromiseWrapper} from 'angular2/src/core/facade/async';
import {XHR} from 'angular2/src/core/render/xhr';
@ -61,7 +62,7 @@ export class TemplateLoader {
allStyleUrls.map(styleUrl => this._urlResolver.resolve(templateSourceUrl, styleUrl));
return new TemplateMetadata({
encapsulation: encapsulation,
nodes: remainingNodes,
template: this._domParser.unparse(remainingNodes),
styles: allResolvedStyles,
styleAbsUrls: allStyleAbsUrls,
ngContentSelectors: visitor.ngContentSelectors
@ -74,7 +75,7 @@ class TemplatePreparseVisitor implements HtmlAstVisitor {
styles: string[] = [];
styleUrls: string[] = [];
visitElement(ast: HtmlElementAst): HtmlElementAst {
visitElement(ast: HtmlElementAst, context: any): HtmlElementAst {
var selectAttr = null;
var hrefAttr = null;
var relAttr = null;
@ -90,7 +91,7 @@ class TemplatePreparseVisitor implements HtmlAstVisitor {
var nodeName = ast.name;
var keepElement = true;
if (nodeName == NG_CONTENT_ELEMENT) {
this.ngContentSelectors.push(selectAttr);
this.ngContentSelectors.push(normalizeNgContentSelect(selectAttr));
} else if (nodeName == STYLE_ELEMENT) {
keepElement = false;
var textContent = '';
@ -111,6 +112,13 @@ class TemplatePreparseVisitor implements HtmlAstVisitor {
return null;
}
}
visitAttr(ast: HtmlAttrAst): HtmlAttrAst { return ast; }
visitText(ast: HtmlTextAst): HtmlTextAst { return ast; }
visitAttr(ast: HtmlAttrAst, context: any): HtmlAttrAst { return ast; }
visitText(ast: HtmlTextAst, context: any): HtmlTextAst { return ast; }
}
function normalizeNgContentSelect(selectAttr: string): string {
if (isBlank(selectAttr) || selectAttr.length === 0) {
return '*';
}
return selectAttr;
}

View File

@ -34,7 +34,7 @@ export {
DebugContext,
ChangeDetectorGenConfig
} from './interfaces';
export {ChangeDetectionStrategy} from './constants';
export {ChangeDetectionStrategy, changeDetectionStrategyFromJson} from './constants';
export {DynamicProtoChangeDetector} from './proto_change_detector';
export {BindingRecord, BindingTarget} from './binding_record';
export {DirectiveIndex, DirectiveRecord} from './directive_record';

View File

@ -1,5 +1,11 @@
// TODO:vsavkin Use enums after switching to TypeScript
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/core/facade/lang';
import {
StringWrapper,
normalizeBool,
isBlank,
serializeEnum,
deserializeEnum
} from 'angular2/src/core/facade/lang';
import {MapWrapper} from 'angular2/src/core/facade/collection';
export enum ChangeDetectionStrategy {
/**
@ -42,6 +48,20 @@ export enum ChangeDetectionStrategy {
OnPushObserve
}
var strategyMap: Map<number, ChangeDetectionStrategy> = MapWrapper.createFromPairs([
[0, ChangeDetectionStrategy.CheckOnce],
[1, ChangeDetectionStrategy.Checked],
[2, ChangeDetectionStrategy.CheckAlways],
[3, ChangeDetectionStrategy.Detached],
[4, ChangeDetectionStrategy.OnPush],
[5, ChangeDetectionStrategy.Default],
[6, ChangeDetectionStrategy.OnPushObserve]
]);
export function changeDetectionStrategyFromJson(value: number): ChangeDetectionStrategy {
return deserializeEnum(value, strategyMap);
}
export function isDefaultChangeDetectionStrategy(changeDetectionStrategy: ChangeDetectionStrategy):
boolean {
return isBlank(changeDetectionStrategy) ||

View File

@ -304,6 +304,12 @@ export enum ViewEncapsulation {
None
}
var encapsulationMap: Map<number, ViewEncapsulation> = MapWrapper.createFromPairs(
[[0, ViewEncapsulation.Emulated], [1, ViewEncapsulation.Native], [2, ViewEncapsulation.None]]);
export function viewEncapsulationFromJson(value: number): ViewEncapsulation {
return deserializeEnum(value, encapsulationMap);
}
export class ViewDefinition {
componentId: string;
templateAbsUrl: string;