Skip to content

Commit 9f576b0

Browse files
committed
feat(compile): add HtmlParser, TemplateParser, ComponentMetadataLoader
First bits of new compile pipeline #3605 Closes #3839
1 parent 343dcfa commit 9f576b0

File tree

12 files changed

+1555
-0
lines changed

12 files changed

+1555
-0
lines changed

modules/angular2/src/compiler/api.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {HtmlAst} from './html_ast';
2+
3+
export class TypeMeta {
4+
type: any;
5+
typeName: string;
6+
typeUrl: string;
7+
constructor({type, typeName, typeUrl}:
8+
{type?: string, typeName?: string, typeUrl?: string} = {}) {
9+
this.type = type;
10+
this.typeName = typeName;
11+
this.typeUrl = typeUrl;
12+
}
13+
}
14+
15+
export class TemplateMeta {
16+
encapsulation: ViewEncapsulation;
17+
nodes: HtmlAst[];
18+
styles: string[];
19+
styleAbsUrls: string[];
20+
ngContentSelectors: string[];
21+
constructor({encapsulation, nodes, styles, styleAbsUrls, ngContentSelectors}: {
22+
encapsulation: ViewEncapsulation,
23+
nodes: HtmlAst[],
24+
styles: string[],
25+
styleAbsUrls: string[],
26+
ngContentSelectors: string[]
27+
}) {
28+
this.encapsulation = encapsulation;
29+
this.nodes = nodes;
30+
this.styles = styles;
31+
this.styleAbsUrls = styleAbsUrls;
32+
this.ngContentSelectors = ngContentSelectors;
33+
}
34+
}
35+
36+
/**
37+
* How the template and styles of a view should be encapsulated.
38+
*/
39+
export enum ViewEncapsulation {
40+
/**
41+
* Emulate scoping of styles by preprocessing the style rules
42+
* and adding additional attributes to elements. This is the default.
43+
*/
44+
Emulated,
45+
/**
46+
* Uses the native mechanism of the renderer. For the DOM this means creating a ShadowRoot.
47+
*/
48+
Native,
49+
/**
50+
* Don't scope the template nor the styles.
51+
*/
52+
None
53+
}
54+
55+
export class DirectiveMetadata {
56+
type: TypeMeta;
57+
selector: string;
58+
constructor({type, selector}: {type?: TypeMeta, selector?: string} = {}) {
59+
this.type = type;
60+
this.selector = selector;
61+
}
62+
}
63+
64+
export class ComponentMetadata extends DirectiveMetadata {
65+
template: TemplateMeta;
66+
constructor({type, selector, template}:
67+
{type?: TypeMeta, selector?: string, template?: TemplateMeta}) {
68+
super({type: type, selector: selector});
69+
this.template = template;
70+
}
71+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {isPresent} from 'angular2/src/core/facade/lang';
2+
3+
export interface HtmlAst {
4+
sourceInfo: string;
5+
visit(visitor: HtmlAstVisitor): any;
6+
}
7+
8+
export class HtmlTextAst implements HtmlAst {
9+
constructor(public value: string, public sourceInfo: string) {}
10+
visit(visitor: HtmlAstVisitor): any { return visitor.visitText(this); }
11+
}
12+
13+
export class HtmlAttrAst implements HtmlAst {
14+
constructor(public name: string, public value: string, public sourceInfo: string) {}
15+
visit(visitor: HtmlAstVisitor): any { return visitor.visitAttr(this); }
16+
}
17+
18+
export class HtmlElementAst implements HtmlAst {
19+
constructor(public name: string, public attrs: HtmlAttrAst[], public children: HtmlAst[],
20+
public sourceInfo: string) {}
21+
visit(visitor: HtmlAstVisitor): any { return visitor.visitElement(this); }
22+
}
23+
24+
export interface HtmlAstVisitor {
25+
visitElement(ast: HtmlElementAst): any;
26+
visitAttr(ast: HtmlAttrAst): any;
27+
visitText(ast: HtmlTextAst): any;
28+
}
29+
30+
export function htmlVisitAll(visitor: HtmlAstVisitor, asts: HtmlAst[]): any[] {
31+
var result = [];
32+
asts.forEach(ast => {
33+
var astResult = ast.visit(visitor);
34+
if (isPresent(astResult)) {
35+
result.push(astResult);
36+
}
37+
});
38+
return result;
39+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import {MapWrapper, ListWrapper} from 'angular2/src/core/facade/collection';
2+
import {
3+
isPresent,
4+
StringWrapper,
5+
stringify,
6+
assertionsEnabled,
7+
StringJoiner
8+
} from 'angular2/src/core/facade/lang';
9+
import {DOM} from 'angular2/src/core/dom/dom_adapter';
10+
11+
import {HtmlAst, HtmlAttrAst, HtmlTextAst, HtmlElementAst} from './html_ast';
12+
13+
const NG_NON_BINDABLE = 'ng-non-bindable';
14+
15+
export class HtmlParser {
16+
parse(template: string, sourceInfo: string): HtmlAst[] {
17+
var root = DOM.createTemplate(template);
18+
return parseChildNodes(root, sourceInfo);
19+
}
20+
}
21+
22+
function parseText(text: Text, indexInParent: number, parentSourceInfo: string): HtmlTextAst {
23+
// TODO(tbosch): add source row/column source info from parse5 / package:html
24+
var value = DOM.getText(text);
25+
return new HtmlTextAst(value,
26+
`${parentSourceInfo} > #text(${value}):nth-child(${indexInParent})`);
27+
}
28+
29+
function parseAttr(element: Element, parentSourceInfo: string, attrName: string, attrValue: string):
30+
HtmlAttrAst {
31+
// TODO(tbosch): add source row/column source info from parse5 / package:html
32+
return new HtmlAttrAst(attrName, attrValue, `${parentSourceInfo}[${attrName}=${attrValue}]`);
33+
}
34+
35+
function parseElement(element: Element, indexInParent: number, parentSourceInfo: string):
36+
HtmlElementAst {
37+
// normalize nodename always as lower case so that following build steps
38+
// can rely on this
39+
var nodeName = DOM.nodeName(element).toLowerCase();
40+
// TODO(tbosch): add source row/column source info from parse5 / package:html
41+
var sourceInfo = `${parentSourceInfo} > ${nodeName}:nth-child(${indexInParent})`;
42+
var attrs = parseAttrs(element, sourceInfo);
43+
44+
var childNodes;
45+
if (ignoreChildren(attrs)) {
46+
childNodes = [];
47+
} else {
48+
childNodes = parseChildNodes(element, sourceInfo);
49+
}
50+
return new HtmlElementAst(nodeName, attrs, childNodes, sourceInfo);
51+
}
52+
53+
function parseAttrs(element: Element, elementSourceInfo: string): HtmlAttrAst[] {
54+
// Note: sort the attributes early in the pipeline to get
55+
// consistent results throughout the pipeline, as attribute order is not defined
56+
// in DOM parsers!
57+
var attrMap = DOM.attributeMap(element);
58+
var attrList: string[][] = [];
59+
MapWrapper.forEach(attrMap, (value, name) => { attrList.push([name, value]); });
60+
ListWrapper.sort(attrList, (entry1, entry2) => StringWrapper.compare(entry1[0], entry2[0]));
61+
return attrList.map(entry => parseAttr(element, elementSourceInfo, entry[0], entry[1]));
62+
}
63+
64+
function parseChildNodes(element: Element, parentSourceInfo: string): HtmlAst[] {
65+
var root = DOM.templateAwareRoot(element);
66+
var childNodes = DOM.childNodesAsList(root);
67+
var result = [];
68+
var index = 0;
69+
childNodes.forEach(childNode => {
70+
var childResult = null;
71+
if (DOM.isTextNode(childNode)) {
72+
var text = <Text>childNode;
73+
childResult = parseText(text, index, parentSourceInfo);
74+
} else if (DOM.isElementNode(childNode)) {
75+
var el = <Element>childNode;
76+
childResult = parseElement(el, index, parentSourceInfo);
77+
}
78+
if (isPresent(childResult)) {
79+
// Won't have a childResult for e.g. comment nodes
80+
result.push(childResult);
81+
}
82+
index++;
83+
});
84+
return result;
85+
}
86+
87+
function ignoreChildren(attrs: HtmlAttrAst[]): boolean {
88+
for (var i = 0; i < attrs.length; i++) {
89+
var a = attrs[i];
90+
if (a.name == NG_NON_BINDABLE) {
91+
return true;
92+
}
93+
}
94+
return false;
95+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Some of the code comes from WebComponents.JS
2+
// https://github.com/webcomponents/webcomponentsjs/blob/master/src/HTMLImports/path.js
3+
4+
import {Injectable} from 'angular2/di';
5+
import {RegExp, RegExpWrapper, StringWrapper} from 'angular2/src/core/facade/lang';
6+
import {UrlResolver} from 'angular2/src/core/services/url_resolver';
7+
8+
/**
9+
* Rewrites URLs by resolving '@import' and 'url()' URLs from the given base URL,
10+
* removes and returns the @import urls
11+
*/
12+
@Injectable()
13+
export class StyleUrlResolver {
14+
constructor(public _resolver: UrlResolver) {}
15+
16+
resolveUrls(cssText: string, baseUrl: string): string {
17+
cssText = this._replaceUrls(cssText, _cssUrlRe, baseUrl);
18+
return cssText;
19+
}
20+
21+
extractImports(cssText: string): StyleWithImports {
22+
var foundUrls = [];
23+
cssText = this._extractUrls(cssText, _cssImportRe, foundUrls);
24+
return new StyleWithImports(cssText, foundUrls);
25+
}
26+
27+
_replaceUrls(cssText: string, re: RegExp, baseUrl: string) {
28+
return StringWrapper.replaceAllMapped(cssText, re, (m) => {
29+
var pre = m[1];
30+
var originalUrl = m[2];
31+
if (RegExpWrapper.test(_dataUrlRe, originalUrl)) {
32+
// Do not attempt to resolve data: URLs
33+
return m[0];
34+
}
35+
var url = StringWrapper.replaceAll(originalUrl, _quoteRe, '');
36+
var post = m[3];
37+
38+
var resolvedUrl = this._resolver.resolve(baseUrl, url);
39+
40+
return pre + "'" + resolvedUrl + "'" + post;
41+
});
42+
}
43+
44+
_extractUrls(cssText: string, re: RegExp, foundUrls: string[]) {
45+
return StringWrapper.replaceAllMapped(cssText, re, (m) => {
46+
var originalUrl = m[2];
47+
if (RegExpWrapper.test(_dataUrlRe, originalUrl)) {
48+
// Do not attempt to resolve data: URLs
49+
return m[0];
50+
}
51+
var url = StringWrapper.replaceAll(originalUrl, _quoteRe, '');
52+
foundUrls.push(url);
53+
return '';
54+
});
55+
}
56+
}
57+
58+
export class StyleWithImports {
59+
constructor(public style: string, public styleUrls: string[]) {}
60+
}
61+
62+
var _cssUrlRe = /(url\()([^)]*)(\))/g;
63+
var _cssImportRe = /(@import[\s]+(?:url\()?)['"]?([^'"\)]*)['"]?(.*;)/g;
64+
var _quoteRe = /['"]/g;
65+
var _dataUrlRe = /^['"]?data:/g;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {AST} from 'angular2/src/core/change_detection/change_detection';
2+
import {isPresent} from 'angular2/src/core/facade/lang';
3+
import {DirectiveMetadata} from './api';
4+
5+
export interface TemplateAst {
6+
sourceInfo: string;
7+
visit(visitor: TemplateAstVisitor): any;
8+
}
9+
10+
export class TextAst implements TemplateAst {
11+
constructor(public value: string, public sourceInfo: string) {}
12+
visit(visitor: TemplateAstVisitor): any { return visitor.visitText(this); }
13+
}
14+
15+
export class BoundTextAst implements TemplateAst {
16+
constructor(public value: AST, public sourceInfo: string) {}
17+
visit(visitor: TemplateAstVisitor): any { return visitor.visitBoundText(this); }
18+
}
19+
20+
export class AttrAst implements TemplateAst {
21+
constructor(public name: string, public value: string, public sourceInfo: string) {}
22+
visit(visitor: TemplateAstVisitor): any { return visitor.visitAttr(this); }
23+
}
24+
25+
export class BoundPropertyAst implements TemplateAst {
26+
constructor(public name: string, public value: AST, public sourceInfo: string) {}
27+
visit(visitor: TemplateAstVisitor): any { return visitor.visitProperty(this); }
28+
}
29+
30+
export class BoundEventAst implements TemplateAst {
31+
constructor(public name: string, public handler: AST, public sourceInfo: string) {}
32+
visit(visitor: TemplateAstVisitor): any { return visitor.visitEvent(this); }
33+
}
34+
35+
export class VariableAst implements TemplateAst {
36+
constructor(public name: string, public value: string, public sourceInfo: string) {}
37+
visit(visitor: TemplateAstVisitor): any { return visitor.visitVariable(this); }
38+
}
39+
40+
export class ElementAst implements TemplateAst {
41+
constructor(public attrs: AttrAst[], public properties: BoundPropertyAst[],
42+
public events: BoundEventAst[], public vars: VariableAst[],
43+
public directives: DirectiveMetadata[], public children: TemplateAst[],
44+
public sourceInfo: string) {}
45+
visit(visitor: TemplateAstVisitor): any { return visitor.visitElement(this); }
46+
}
47+
48+
export class EmbeddedTemplateAst implements TemplateAst {
49+
constructor(public attrs: AttrAst[], public properties: BoundPropertyAst[],
50+
public vars: VariableAst[], public directives: DirectiveMetadata[],
51+
public children: TemplateAst[], public sourceInfo: string) {}
52+
visit(visitor: TemplateAstVisitor): any { return visitor.visitEmbeddedTemplate(this); }
53+
}
54+
55+
export class NgContentAst implements TemplateAst {
56+
constructor(public select: string, public sourceInfo: string) {}
57+
visit(visitor: TemplateAstVisitor): any { return visitor.visitNgContent(this); }
58+
}
59+
60+
export interface TemplateAstVisitor {
61+
visitNgContent(ast: NgContentAst): any;
62+
visitEmbeddedTemplate(ast: EmbeddedTemplateAst): any;
63+
visitElement(ast: ElementAst): any;
64+
visitVariable(ast: VariableAst): any;
65+
visitEvent(ast: BoundEventAst): any;
66+
visitProperty(ast: BoundPropertyAst): any;
67+
visitAttr(ast: AttrAst): any;
68+
visitBoundText(ast: BoundTextAst): any;
69+
visitText(ast: TextAst): any;
70+
}
71+
72+
73+
export function templateVisitAll(visitor: TemplateAstVisitor, asts: TemplateAst[]): any[] {
74+
var result = [];
75+
asts.forEach(ast => {
76+
var astResult = ast.visit(visitor);
77+
if (isPresent(astResult)) {
78+
result.push(astResult);
79+
}
80+
});
81+
return result;
82+
}

0 commit comments

Comments
 (0)