Skip to content

Commit 2f2d5f3

Browse files
vikermanjasonaden
authored andcommitted
feat(platform-server): provide a DOM implementation on the server
Fixes #14638 Uses Domino - https://github.com/fgnass/domino and removes dependency on Parse5. The DOCUMENT and nativeElement were never typed earlier and were different on the browser(DOM nodes) and the server(Parse5 nodes). With this change, platform-server also exposes a DOCUMENT and nativeElement that is closer to the client. If you were relying on nativeElement on the server, you would have to change your code to use the DOM API now instead of Parse5 AST API. Removes the need to add services for each and every Document manipulation like Title/Meta etc. This does *not* provide a global variable 'document' or 'window' on the server. You still have to inject DOCUMENT to get the document backing the current platform server instance.
1 parent 30d53a8 commit 2f2d5f3

File tree

21 files changed

+280
-914
lines changed

21 files changed

+280
-914
lines changed

npm-shrinkwrap.clean.json

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2352,6 +2352,9 @@
23522352
"domhandler": {
23532353
"version": "2.3.0"
23542354
},
2355+
"domino": {
2356+
"version": "1.0.29"
2357+
},
23552358
"domutils": {
23562359
"version": "1.5.1"
23572360
},
@@ -6162,14 +6165,6 @@
61626165
"parse-json": {
61636166
"version": "2.2.0"
61646167
},
6165-
"parse5": {
6166-
"version": "3.0.1",
6167-
"dependencies": {
6168-
"@types/node": {
6169-
"version": "6.0.63"
6170-
}
6171-
}
6172-
},
61736168
"parsejson": {
61746169
"version": "0.0.1"
61756170
},

npm-shrinkwrap.json

Lines changed: 5 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"cors": "^2.7.1",
5757
"dgeni": "^0.4.2",
5858
"dgeni-packages": "^0.16.5",
59+
"domino": "^1.0.29",
5960
"entities": "^1.1.1",
6061
"firebase-tools": "^3.9.2",
6162
"firefox-profile": "^0.3.4",
@@ -81,7 +82,6 @@
8182
"minimist": "^1.2.0",
8283
"nan": "^2.4.0",
8384
"node-uuid": "1.4.x",
84-
"parse5": "^3.0.1",
8585
"protractor": "^4.0.14",
8686
"react": "^0.14.0",
8787
"rewire": "^2.3.3",

packages/compiler-cli/integrationtest/test/basic_spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ describe('template codegen output', () => {
8484

8585
it('should support i18n for content tags', () => {
8686
const containerElement = createComponent(BasicComp).nativeElement;
87-
const pElement = containerElement.children.find((c: any) => c.name == 'p');
88-
const pText = pElement.children.map((c: any) => c.data).join('').trim();
87+
const pElement = containerElement.querySelector('p');
88+
const pText = pElement.textContent;
8989
expect(pText).toBe('tervetuloa');
9090
});
9191

packages/compiler-cli/integrationtest/test/ng_module_spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('NgModule', () => {
4949
// https://github.com/angular/angular/issues/15221
5050
const fixture = createComponent(ComponentUsingFlatModule);
5151
const bundleComp = fixture.nativeElement.children;
52-
expect(bundleComp[0].children[0].children[0].data).toEqual('flat module component');
52+
expect(bundleComp[0].children[0].textContent).toEqual('flat module component');
5353
});
5454
});
5555

@@ -58,8 +58,8 @@ describe('NgModule', () => {
5858
it('should support third party entryComponents components', () => {
5959
const fixture = createComponent(ComponentUsingThirdParty);
6060
const thirdPComps = fixture.nativeElement.children;
61-
expect(thirdPComps[0].children[0].children[0].data).toEqual('3rdP-component');
62-
expect(thirdPComps[1].children[0].children[0].data).toEqual(`other-3rdP-component
61+
expect(thirdPComps[0].children[0].textContent).toEqual('3rdP-component');
62+
expect(thirdPComps[1].children[0].textContent).toEqual(`other-3rdP-component
6363
multi-lines`);
6464
});
6565

packages/platform-browser/src/browser/browser_adapter.ts

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,12 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
129129
}
130130
dispatchEvent(el: Node, evt: any) { el.dispatchEvent(evt); }
131131
createMouseEvent(eventType: string): MouseEvent {
132-
const evt: MouseEvent = document.createEvent('MouseEvent');
132+
const evt: MouseEvent = this.getDefaultDocument().createEvent('MouseEvent');
133133
evt.initEvent(eventType, true, true);
134134
return evt;
135135
}
136136
createEvent(eventType: any): Event {
137-
const evt: Event = document.createEvent('Event');
137+
const evt: Event = this.getDefaultDocument().createEvent('Event');
138138
evt.initEvent(eventType, true, true);
139139
return evt;
140140
}
@@ -147,7 +147,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
147147
}
148148
getInnerHTML(el: HTMLElement): string { return el.innerHTML; }
149149
getTemplateContent(el: Node): Node|null {
150-
return 'content' in el && el instanceof HTMLTemplateElement ? el.content : null;
150+
return 'content' in el && this.isTemplateElement(el) ? (<any>el).content : null;
151151
}
152152
getOuterHTML(el: HTMLElement): string { return el.outerHTML; }
153153
nodeName(node: Node): string { return node.nodeName; }
@@ -198,25 +198,34 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
198198
setValue(el: any, value: string) { el.value = value; }
199199
getChecked(el: any): boolean { return el.checked; }
200200
setChecked(el: any, value: boolean) { el.checked = value; }
201-
createComment(text: string): Comment { return document.createComment(text); }
201+
createComment(text: string): Comment { return this.getDefaultDocument().createComment(text); }
202202
createTemplate(html: any): HTMLElement {
203-
const t = document.createElement('template');
203+
const t = this.getDefaultDocument().createElement('template');
204204
t.innerHTML = html;
205205
return t;
206206
}
207-
createElement(tagName: string, doc = document): HTMLElement { return doc.createElement(tagName); }
208-
createElementNS(ns: string, tagName: string, doc = document): Element {
207+
createElement(tagName: string, doc?: Document): HTMLElement {
208+
doc = doc || this.getDefaultDocument();
209+
return doc.createElement(tagName);
210+
}
211+
createElementNS(ns: string, tagName: string, doc?: Document): Element {
212+
doc = doc || this.getDefaultDocument();
209213
return doc.createElementNS(ns, tagName);
210214
}
211-
createTextNode(text: string, doc = document): Text { return doc.createTextNode(text); }
212-
createScriptTag(attrName: string, attrValue: string, doc = document): HTMLScriptElement {
215+
createTextNode(text: string, doc?: Document): Text {
216+
doc = doc || this.getDefaultDocument();
217+
return doc.createTextNode(text);
218+
}
219+
createScriptTag(attrName: string, attrValue: string, doc?: Document): HTMLScriptElement {
220+
doc = doc || this.getDefaultDocument();
213221
const el = <HTMLScriptElement>doc.createElement('SCRIPT');
214222
el.setAttribute(attrName, attrValue);
215223
return el;
216224
}
217-
createStyleElement(css: string, doc = document): HTMLStyleElement {
225+
createStyleElement(css: string, doc?: Document): HTMLStyleElement {
226+
doc = doc || this.getDefaultDocument();
218227
const style = <HTMLStyleElement>doc.createElement('style');
219-
this.appendChild(style, this.createTextNode(css));
228+
this.appendChild(style, this.createTextNode(css, doc));
220229
return style;
221230
}
222231
createShadowRoot(el: HTMLElement): DocumentFragment { return (<any>el).createShadowRoot(); }
@@ -253,7 +262,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
253262
const res = new Map<string, string>();
254263
const elAttrs = element.attributes;
255264
for (let i = 0; i < elAttrs.length; i++) {
256-
const attrib = elAttrs[i];
265+
const attrib = elAttrs.item(i);
257266
res.set(attrib.name, attrib.value);
258267
}
259268
return res;
@@ -282,17 +291,18 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
282291
createHtmlDocument(): HTMLDocument {
283292
return document.implementation.createHTMLDocument('fakeTitle');
284293
}
294+
getDefaultDocument(): Document { return document; }
285295
getBoundingClientRect(el: Element): any {
286296
try {
287297
return el.getBoundingClientRect();
288298
} catch (e) {
289299
return {top: 0, bottom: 0, left: 0, right: 0, width: 0, height: 0};
290300
}
291301
}
292-
getTitle(doc: Document): string { return document.title; }
293-
setTitle(doc: Document, newTitle: string) { document.title = newTitle || ''; }
302+
getTitle(doc: Document): string { return doc.title; }
303+
setTitle(doc: Document, newTitle: string) { doc.title = newTitle || ''; }
294304
elementMatches(n: any, selector: string): boolean {
295-
if (n instanceof HTMLElement) {
305+
if (this.isElementNode(n)) {
296306
return n.matches && n.matches(selector) ||
297307
n.msMatchesSelector && n.msMatchesSelector(selector) ||
298308
n.webkitMatchesSelector && n.webkitMatchesSelector(selector);
@@ -301,7 +311,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
301311
return false;
302312
}
303313
isTemplateElement(el: Node): boolean {
304-
return el instanceof HTMLElement && el.nodeName == 'TEMPLATE';
314+
return this.isElementNode(el) && el.nodeName === 'TEMPLATE';
305315
}
306316
isTextNode(node: Node): boolean { return node.nodeType === Node.TEXT_NODE; }
307317
isCommentNode(node: Node): boolean { return node.nodeType === Node.COMMENT_NODE; }
@@ -312,7 +322,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
312322
isShadowRoot(node: any): boolean { return node instanceof DocumentFragment; }
313323
importIntoDoc(node: Node): any { return document.importNode(this.templateAwareRoot(node), true); }
314324
adoptNode(node: Node): any { return document.adoptNode(node); }
315-
getHref(el: Element): string { return (<any>el).href; }
325+
getHref(el: Element): string { return el.getAttribute('href') !; }
316326

317327
getEventKey(event: any): string {
318328
let key = event.key;
@@ -342,10 +352,10 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
342352
return window;
343353
}
344354
if (target === 'document') {
345-
return document;
355+
return doc;
346356
}
347357
if (target === 'body') {
348-
return document.body;
358+
return doc.body;
349359
}
350360
return null;
351361
}

packages/platform-browser/src/browser/meta.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class Meta {
5656

5757
getTag(attrSelector: string): HTMLMetaElement|null {
5858
if (!attrSelector) return null;
59-
return this._dom.querySelector(this._doc, `meta[${attrSelector}]`);
59+
return this._dom.querySelector(this._doc, `meta[${attrSelector}]`) || null;
6060
}
6161

6262
getTags(attrSelector: string): HTMLMetaElement[] {

packages/platform-browser/src/dom/dom_adapter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export abstract class DomAdapter {
125125
abstract removeAttributeNS(element: any, ns: string, attribute: string): any;
126126
abstract templateAwareRoot(el: any): any;
127127
abstract createHtmlDocument(): HTMLDocument;
128+
abstract getDefaultDocument(): Document;
128129
abstract getBoundingClientRect(el: any): any;
129130
abstract getTitle(doc: Document): string;
130131
abstract setTitle(doc: Document, newTitle: string): any;

packages/platform-browser/test/browser/meta_spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
1414

1515
export function main() {
1616
describe('Meta service', () => {
17-
const doc = getDOM().createHtmlDocument();
18-
const metaService = new Meta(doc);
17+
let doc: Document;
18+
let metaService: Meta;
1919
let defaultMeta: HTMLMetaElement;
2020

2121
beforeEach(() => {
22+
doc = getDOM().createHtmlDocument();
23+
metaService = new Meta(doc);
2224
defaultMeta = getDOM().createElement('meta', doc) as HTMLMetaElement;
2325
getDOM().setAttribute(defaultMeta, 'property', 'fb:app_id');
2426
getDOM().setAttribute(defaultMeta, 'content', '123456789');

packages/platform-browser/test/browser/title_spec.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,15 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
1414

1515
export function main() {
1616
describe('title service', () => {
17-
const doc = getDOM().createHtmlDocument();
18-
const initialTitle = getDOM().getTitle(doc);
19-
const titleService = new Title(doc);
17+
let doc: Document;
18+
let initialTitle: string;
19+
let titleService: Title;
20+
21+
beforeEach(() => {
22+
doc = getDOM().createHtmlDocument();
23+
initialTitle = getDOM().getTitle(doc);
24+
titleService = new Title(doc);
25+
});
2026

2127
afterEach(() => { getDOM().setTitle(doc, initialTitle); });
2228

packages/platform-server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
},
1919
"dependencies": {
2020
"tslib": "^1.7.1",
21-
"parse5": "^3.0.1",
21+
"domino": "^1.0.29",
2222
"xhr2": "^0.1.4"
2323
},
2424
"repository": {

0 commit comments

Comments
 (0)