diff --git a/app-shell/src/app/app-shell-providers.ts b/app-shell/src/app/app-shell-providers.ts new file mode 100644 index 0000000..745d175 --- /dev/null +++ b/app-shell/src/app/app-shell-providers.ts @@ -0,0 +1,27 @@ +import { Provider} from '@angular/core'; + +import { IS_PRERENDER } from './is-prerender.service'; +import { TemplateVisibilityStrategy } from './template-visibility-strategy'; +import { TemplateCommentStrategy } from './template-comment-strategy'; + +export const APP_SHELL_RUNTIME_PROVIDERS: Provider[] = [ + { + provide: IS_PRERENDER, + useValue: false + }, + { + provide: TemplateVisibilityStrategy, + useClass: TemplateCommentStrategy + } +]; + +export const APP_SHELL_BUILD_PROVIDERS: Provider[] = [ + { + provide: IS_PRERENDER, + useValue: true + }, + { + provide: TemplateVisibilityStrategy, + useClass: TemplateCommentStrategy + } +]; diff --git a/app-shell/src/app/index.ts b/app-shell/src/app/index.ts index d9c2768..394070d 100644 --- a/app-shell/src/app/index.ts +++ b/app-shell/src/app/index.ts @@ -1,3 +1,4 @@ export * from './module'; export * from './prerender'; export * from './shell'; + diff --git a/app-shell/src/app/shell.spec.ts b/app-shell/src/app/shell.spec.ts index 8a33d56..3ea3cfe 100644 --- a/app-shell/src/app/shell.spec.ts +++ b/app-shell/src/app/shell.spec.ts @@ -22,6 +22,7 @@ export default function () { expect(fixture.debugElement.childNodes.length).toBe(1); expect(fixture.debugElement.childNodes[0].nativeNode.data).toBe('template bindings={}'); }); + it('should render the element at runtime', () => { const fixture = TestBed .configureTestingModule({ @@ -55,6 +56,7 @@ export default function () { expect(fixture.debugElement.childNodes[0].nativeNode.data).toBe('template bindings={}'); expect(fixture.debugElement.childNodes[1].nativeNode.name).toBe('div'); }); + it('should NOT render the element at runtime', () => { const fixture = TestBed .configureTestingModule({ diff --git a/app-shell/src/app/template-comment-strategy.ts b/app-shell/src/app/template-comment-strategy.ts new file mode 100644 index 0000000..fd84c5c --- /dev/null +++ b/app-shell/src/app/template-comment-strategy.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { __platform_browser_private__ as _ } from '@angular/platform-browser'; + +import { TemplateVisibilityStrategy } from './template-visibility-strategy'; + +@Injectable() +export class TemplateCommentStrategy extends TemplateVisibilityStrategy { + private _hidden: any[] = []; + private _shown: any[] = []; + + show(marker: string) { + (this._rootNodes[marker] || []) + .filter((node: any) => this._shown.indexOf(node) < 0) + .forEach((node: any) => { + _.getDOM().setAttribute(node, marker, ''); + this._shown.push(node); + }); + } + + hide(marker: string) { + const DOM = _.getDOM(); + (this._rootNodes[marker] || []) + .filter((node: any) => this._hidden.indexOf(node) < 0) + .forEach((node: any) => { + const comment = DOM.createComment(`${marker}(${DOM.getOuterHTML(node)})`); + const parentNode = DOM.parentElement(node); + DOM.replaceChild(parentNode, comment, node); + this._hidden.push(node); + }); + } +} diff --git a/app-shell/src/app/template-visibility-strategy.ts b/app-shell/src/app/template-visibility-strategy.ts new file mode 100644 index 0000000..36a6f43 --- /dev/null +++ b/app-shell/src/app/template-visibility-strategy.ts @@ -0,0 +1,15 @@ +export interface DirectiveNodes { + [marker: string]: any[]; +}; + +export abstract class TemplateVisibilityStrategy { + protected _rootNodes: DirectiveNodes = {}; + + abstract show(marker: string): void; + abstract hide(marker: string): void; + + setRootNodes(marker: string, ...rootNodes: any[]) { + const nodes = this._rootNodes[marker] || []; + this._rootNodes[marker] = nodes.concat(rootNodes); + } +} diff --git a/app-shell/src/experimental/shell-parser/ast/ast-node.ts b/app-shell/src/experimental/shell-parser/ast/ast-node.ts index 663a35c..6035ead 100644 --- a/app-shell/src/experimental/shell-parser/ast/ast-node.ts +++ b/app-shell/src/experimental/shell-parser/ast/ast-node.ts @@ -9,5 +9,5 @@ export interface ASTNode { parentNode?: ASTNode; nodeName: string; value?: string; + data?: string; } - diff --git a/app-shell/src/experimental/shell-parser/ast/index.ts b/app-shell/src/experimental/shell-parser/ast/index.ts index ddebadd..1f72a8d 100644 --- a/app-shell/src/experimental/shell-parser/ast/index.ts +++ b/app-shell/src/experimental/shell-parser/ast/index.ts @@ -1,2 +1 @@ export * from './ast-node'; - diff --git a/app-shell/src/experimental/shell-parser/config.ts b/app-shell/src/experimental/shell-parser/config.ts index 96a186b..be13ab3 100644 --- a/app-shell/src/experimental/shell-parser/config.ts +++ b/app-shell/src/experimental/shell-parser/config.ts @@ -3,6 +3,7 @@ export type RouteDefinition = string; const SHELL_PARSER_CACHE_NAME = 'mobile-toolkit:app-shell'; const APP_SHELL_URL = './app_shell.html'; const NO_RENDER_CSS_SELECTOR = '[shellNoRender]'; +const RENDER_MARKER = 'shellRender'; const ROUTE_DEFINITIONS: RouteDefinition[] = []; const INLINE_IMAGES: string[] = ['png', 'svg', 'jpg']; @@ -13,6 +14,7 @@ export interface ShellParserConfig { APP_SHELL_URL?: string; SHELL_PARSER_CACHE_NAME?: string; NO_RENDER_CSS_SELECTOR?: string; + RENDER_MARKER?: string; ROUTE_DEFINITIONS?: RouteDefinition[]; INLINE_IMAGES?: string[]; } @@ -21,6 +23,7 @@ export const SHELL_PARSER_DEFAULT_CONFIG: ShellParserConfig = { SHELL_PARSER_CACHE_NAME, APP_SHELL_URL, NO_RENDER_CSS_SELECTOR, + RENDER_MARKER, ROUTE_DEFINITIONS, INLINE_IMAGES }; diff --git a/app-shell/src/experimental/shell-parser/node-matcher/css-node-matcher.spec.ts b/app-shell/src/experimental/shell-parser/node-matcher/css-node-matcher.spec.ts index 2180578..5edf27f 100644 --- a/app-shell/src/experimental/shell-parser/node-matcher/css-node-matcher.spec.ts +++ b/app-shell/src/experimental/shell-parser/node-matcher/css-node-matcher.spec.ts @@ -1,6 +1,3 @@ -import { - inject -} from '@angular/core/testing'; import { ASTNode } from '../ast'; import { CssSelector } from './css-selector'; import { CssNodeMatcher } from './css-node-matcher'; diff --git a/app-shell/src/experimental/shell-parser/node-matcher/css-selector/css-selector.spec.ts b/app-shell/src/experimental/shell-parser/node-matcher/css-selector/css-selector.spec.ts index 0f5dae5..91a5074 100644 --- a/app-shell/src/experimental/shell-parser/node-matcher/css-selector/css-selector.spec.ts +++ b/app-shell/src/experimental/shell-parser/node-matcher/css-selector/css-selector.spec.ts @@ -1,6 +1,3 @@ -import { - inject -} from '@angular/core/testing'; import { CssSelector } from './css-selector'; describe('CssSelector', () => { @@ -52,7 +49,5 @@ describe('CssSelector', () => { expect(result.elementId).toBe('baz'); expect(result.classNames).toEqual(['foo', 'qux']); }); - }); }); - diff --git a/app-shell/src/experimental/shell-parser/node-matcher/css-selector/index.ts b/app-shell/src/experimental/shell-parser/node-matcher/css-selector/index.ts index c33a033..724cfb0 100644 --- a/app-shell/src/experimental/shell-parser/node-matcher/css-selector/index.ts +++ b/app-shell/src/experimental/shell-parser/node-matcher/css-selector/index.ts @@ -1,2 +1 @@ export * from './css-selector'; - diff --git a/app-shell/src/experimental/shell-parser/node-matcher/node-matcher.ts b/app-shell/src/experimental/shell-parser/node-matcher/node-matcher.ts index ba6bd29..1dbcb2a 100644 --- a/app-shell/src/experimental/shell-parser/node-matcher/node-matcher.ts +++ b/app-shell/src/experimental/shell-parser/node-matcher/node-matcher.ts @@ -3,4 +3,3 @@ import {ASTNode} from '../ast'; export abstract class NodeMatcher { abstract match(node: ASTNode): boolean; } - diff --git a/app-shell/src/experimental/shell-parser/node-visitor/index.ts b/app-shell/src/experimental/shell-parser/node-visitor/index.ts index edbc12d..33da9dd 100644 --- a/app-shell/src/experimental/shell-parser/node-visitor/index.ts +++ b/app-shell/src/experimental/shell-parser/node-visitor/index.ts @@ -1,4 +1,5 @@ export * from './node-visitor'; export * from './resource-inline'; export * from './template-strip-visitor'; +export * from './template-recover-visitor'; diff --git a/app-shell/src/experimental/shell-parser/node-visitor/node-visitor.ts b/app-shell/src/experimental/shell-parser/node-visitor/node-visitor.ts index 74a0d3a..d483452 100644 --- a/app-shell/src/experimental/shell-parser/node-visitor/node-visitor.ts +++ b/app-shell/src/experimental/shell-parser/node-visitor/node-visitor.ts @@ -16,8 +16,6 @@ export abstract class NodeVisitor { } else { return null; } - }) + }); } - } - diff --git a/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/inline-style-resource-inline-visitor.spec.ts b/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/inline-style-resource-inline-visitor.spec.ts index 9203dda..fea66a7 100644 --- a/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/inline-style-resource-inline-visitor.spec.ts +++ b/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/inline-style-resource-inline-visitor.spec.ts @@ -1,7 +1,3 @@ -import { - inject -} from '@angular/core/testing'; - import {ASTNode} from '../../ast'; import {MockWorkerScope, MockResponse} from '../../testing'; import {InlineStyleResourceInlineVisitor} from './'; diff --git a/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/inline-style-resource-inline-visitor.ts b/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/inline-style-resource-inline-visitor.ts index 99ebba7..5de6320 100644 --- a/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/inline-style-resource-inline-visitor.ts +++ b/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/inline-style-resource-inline-visitor.ts @@ -1,8 +1,5 @@ import {ASTNode, ASTAttribute} from '../../ast'; import {ResourceInlineVisitor} from './resource-inline-visitor'; -import {WorkerScope} from '../../context'; - -const URL_REGEXP = /:\s+url\(['"]?(.*?)['"]?\)/gmi; export class InlineStyleResourceInlineVisitor extends ResourceInlineVisitor { diff --git a/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/resource-inline-visitor.ts b/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/resource-inline-visitor.ts index d5f3f41..433869f 100644 --- a/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/resource-inline-visitor.ts +++ b/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/resource-inline-visitor.ts @@ -1,4 +1,3 @@ -import {ASTNode} from '../../ast'; import {NodeVisitor} from '../node-visitor'; import {WorkerScope} from '../../context'; @@ -13,8 +12,7 @@ export abstract class ResourceInlineVisitor extends NodeVisitor { inlineAssets(style: string) { let urls = this.getImagesUrls(style); urls = urls.filter((url: string, idx: number) => urls.indexOf(url) === idx); - return this.processInline(urls, style) - .then((content: string) => content); + return this.processInline(urls, style); } protected getImagesUrls(styles: string): string[] { @@ -55,6 +53,4 @@ export abstract class ResourceInlineVisitor extends NodeVisitor { img ? content.replace(new RegExp(urls[idx], 'g'), img) : content, styles); }); } - } - diff --git a/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/stylesheet-resource-inline-visitor.spec.ts b/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/stylesheet-resource-inline-visitor.spec.ts index 753e4f1..7b99eb3 100644 --- a/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/stylesheet-resource-inline-visitor.spec.ts +++ b/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/stylesheet-resource-inline-visitor.spec.ts @@ -1,7 +1,3 @@ -import { - inject -} from '@angular/core/testing'; - import {ASTNode} from '../../ast'; import {MockWorkerScope, MockResponse} from '../../testing'; import {StylesheetResourceInlineVisitor} from './'; @@ -193,4 +189,3 @@ describe('ResourceInlineVisitor', () => { }); }); - diff --git a/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/stylesheet-resource-inline-visitor.ts b/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/stylesheet-resource-inline-visitor.ts index cdeca53..ef32cfc 100644 --- a/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/stylesheet-resource-inline-visitor.ts +++ b/app-shell/src/experimental/shell-parser/node-visitor/resource-inline/stylesheet-resource-inline-visitor.ts @@ -17,6 +17,4 @@ export class StylesheetResourceInlineVisitor extends ResourceInlineVisitor { } return Promise.resolve(node); } - } - diff --git a/app-shell/src/experimental/shell-parser/node-visitor/template-recover-visitor.spec.ts b/app-shell/src/experimental/shell-parser/node-visitor/template-recover-visitor.spec.ts new file mode 100644 index 0000000..85ddd37 --- /dev/null +++ b/app-shell/src/experimental/shell-parser/node-visitor/template-recover-visitor.spec.ts @@ -0,0 +1,111 @@ +import {ASTNode} from '../ast'; +import {TemplateRecoverVisitor} from './'; +import {Parse5TemplateParser} from '../template-parser'; + +describe('TemplateRecoverVisitor', () => { + + let astRoot: ASTNode; + let nestedNode: ASTNode; + let differentComments: ASTNode; + + beforeEach(() => { + astRoot = { + nodeName: '#comment', + attrs: null, + data: 'shellRender(
Hello world!
)' + }; + const comment2: ASTNode = { + nodeName: '#comment', + attrs: null, + data: 'shellRender(
)Test
)' + }; + const span: ASTNode = { + nodeName: 'span', + attrs: null, + childNodes: [comment2] + }; + comment2.parentNode = span; + const section: ASTNode = { + nodeName: 'section', + attrs: null + }; + const comment1: ASTNode = { + nodeName: '#comment', + attrs: null, + data: 'shellRender(bar)' + }; + nestedNode = { + childNodes: [span, section, comment1], + attrs: null, + nodeName: 'div' + }; + span.parentNode = nestedNode; + comment1.parentNode = nestedNode; + section.parentNode = nestedNode; + + const nonMatchingComment1: ASTNode = { + nodeName: '#comment', + attrs: null, + data: 'bindings {}' + }; + const nonMatchingComment2: ASTNode = { + nodeName: '#comment', + attrs: null, + data: 'shellRender(test)' + }; + const matchingComment: ASTNode = { + nodeName: '#comment', + attrs: null, + data: 'shellNoRender(
)' + }; + differentComments = { + nodeName: 'div', + attrs: null, + childNodes: [nonMatchingComment1, matchingComment, nonMatchingComment2] + }; + nonMatchingComment1.parentNode = differentComments; + nonMatchingComment2.parentNode = differentComments; + matchingComment.parentNode = differentComments; + }); + + it('should process top-level comments', (done: any) => { + const visitor = new TemplateRecoverVisitor('shellRender', new Parse5TemplateParser()); + visitor.visit(astRoot) + .then((node: ASTNode) => { + expect(node.nodeName).toBe('div'); + expect(node.childNodes.length).toBe(1); + expect(node.childNodes[0].value).toBe('Hello world!'); + done(); + }); + }); + + it('should process nested nodes', (done: any) => { + const visitor = new TemplateRecoverVisitor('shellRender', new Parse5TemplateParser()); + visitor.visit(nestedNode) + .then((node: ASTNode) => { + const span = nestedNode.childNodes[0]; + expect(span.childNodes.length).toBe(1); + expect(span.childNodes[0].nodeName).toBe('div'); + expect(span.childNodes[0].childNodes.length).toBe(2); + expect(span.childNodes[0].childNodes[0].value).toBe(')'); + expect(span.childNodes[0].childNodes[1].nodeName).toBe('span'); + expect(node.childNodes[2].nodeName).toBe('#text'); + expect(node.childNodes[2].value).toBe('bar'); + done(); + }); + }); + + it('should process only nodes with appropriate marker', (done: any) => { + const visitor = new TemplateRecoverVisitor('shellNoRender', new Parse5TemplateParser()); + visitor.visit(differentComments) + .then((node: ASTNode) => { + expect(node.childNodes[0].nodeName).toBe('#comment'); + expect(node.childNodes[0].data).toBe('bindings {}'); + expect(node.childNodes[1].nodeName).toBe('div'); + expect(node.childNodes[2].nodeName).toBe('#comment'); + expect(node.childNodes[2].data).toBe('shellRender(test)'); + done(); + }); + }); + +}); diff --git a/app-shell/src/experimental/shell-parser/node-visitor/template-recover-visitor.ts b/app-shell/src/experimental/shell-parser/node-visitor/template-recover-visitor.ts new file mode 100644 index 0000000..da205ae --- /dev/null +++ b/app-shell/src/experimental/shell-parser/node-visitor/template-recover-visitor.ts @@ -0,0 +1,29 @@ +import {ASTNode} from '../ast'; +import {TemplateParser} from '../template-parser'; +import {NodeVisitor} from './node-visitor'; + +export class TemplateRecoverVisitor extends NodeVisitor { + + constructor(private marker: string, private parser: TemplateParser) { + super(); + } + + process(node: ASTNode) { + const regexp = new RegExp(`^${this.marker}\\(\\s*([\\s\\S]*)\\)$`); + if (node.nodeName === '#comment' && regexp.test(node.data)) { + const template = node.data.match(regexp)[1]; + // Returns a #document-fragment node with multiple childs. + // The regular expression above should strip all the whitespace before + // the HTML fragment so once the parser parses the HTML fragment, + // the first should should be our target node. + const replacement = this.parser.parseFragment(template).childNodes.shift(); + if (node.parentNode) { + const commentIdx = node.parentNode.childNodes.indexOf(node); + node.parentNode.childNodes[commentIdx] = replacement; + } + return Promise.resolve(replacement); + } + return Promise.resolve(node); + } + +} diff --git a/app-shell/src/experimental/shell-parser/node-visitor/template-strip-visitor.spec.ts b/app-shell/src/experimental/shell-parser/node-visitor/template-strip-visitor.spec.ts index 9706880..a827b12 100644 --- a/app-shell/src/experimental/shell-parser/node-visitor/template-strip-visitor.spec.ts +++ b/app-shell/src/experimental/shell-parser/node-visitor/template-strip-visitor.spec.ts @@ -1,10 +1,5 @@ -import { - inject -} from '@angular/core/testing'; - import {ASTNode} from '../ast'; import {cssNodeMatcherFactory} from '../node-matcher'; -import {MockWorkerScope, MockResponse} from '../testing'; import {TemplateStripVisitor} from './'; describe('TemplateStripVisitor', () => { @@ -69,6 +64,5 @@ describe('TemplateStripVisitor', () => { done(); }); }); - }); diff --git a/app-shell/src/experimental/shell-parser/node-visitor/template-strip-visitor.ts b/app-shell/src/experimental/shell-parser/node-visitor/template-strip-visitor.ts index 2560764..6cca9bf 100644 --- a/app-shell/src/experimental/shell-parser/node-visitor/template-strip-visitor.ts +++ b/app-shell/src/experimental/shell-parser/node-visitor/template-strip-visitor.ts @@ -1,6 +1,5 @@ import {ASTNode} from '../ast'; import {NodeVisitor} from './node-visitor'; -import {WorkerScope} from '../context'; import {CssNodeMatcher} from '../node-matcher'; export class TemplateStripVisitor extends NodeVisitor { @@ -19,6 +18,4 @@ export class TemplateStripVisitor extends NodeVisitor { } return Promise.resolve(node); } - } - diff --git a/app-shell/src/experimental/shell-parser/shell-parser-factory.ts b/app-shell/src/experimental/shell-parser/shell-parser-factory.ts index 2fe9b65..b6e46ed 100644 --- a/app-shell/src/experimental/shell-parser/shell-parser-factory.ts +++ b/app-shell/src/experimental/shell-parser/shell-parser-factory.ts @@ -1,7 +1,13 @@ import {Parse5TemplateParser} from './template-parser'; import {ShellParserImpl} from './shell-parser'; import {cssNodeMatcherFactory} from './node-matcher'; -import {StylesheetResourceInlineVisitor, InlineStyleResourceInlineVisitor, TemplateStripVisitor, NodeVisitor} from './node-visitor'; +import { + StylesheetResourceInlineVisitor, + InlineStyleResourceInlineVisitor, + TemplateStripVisitor, + NodeVisitor, + TemplateRecoverVisitor +} from './node-visitor'; import {BrowserWorkerScope} from './context'; import {ShellParserConfig, SHELL_PARSER_DEFAULT_CONFIG} from './config'; @@ -13,8 +19,9 @@ export const shellParserFactory = (config: ShellParserConfig = {}) => { const parserConfig = normalizeConfig(config); const scope = new BrowserWorkerScope(); const visitors: NodeVisitor[] = []; + visitors.push(new TemplateRecoverVisitor(parserConfig.RENDER_MARKER, new Parse5TemplateParser())); + visitors.push(new TemplateStripVisitor(cssNodeMatcherFactory(parserConfig.NO_RENDER_CSS_SELECTOR))); if (config.INLINE_IMAGES) { - visitors.push(new TemplateStripVisitor(cssNodeMatcherFactory(parserConfig.NO_RENDER_CSS_SELECTOR))); visitors.push(new StylesheetResourceInlineVisitor(scope, config.INLINE_IMAGES)); visitors.push(new InlineStyleResourceInlineVisitor(scope, config.INLINE_IMAGES)); } @@ -24,4 +31,3 @@ export const shellParserFactory = (config: ShellParserConfig = {}) => { visitors, scope); }; - diff --git a/app-shell/src/experimental/shell-parser/shell-parser.spec.ts b/app-shell/src/experimental/shell-parser/shell-parser.spec.ts index f26206e..8b4c344 100644 --- a/app-shell/src/experimental/shell-parser/shell-parser.spec.ts +++ b/app-shell/src/experimental/shell-parser/shell-parser.spec.ts @@ -1,9 +1,5 @@ -import { - inject -} from '@angular/core/testing'; -import {BrowserWorkerScope, WorkerScope} from './context'; import {ShellParserConfig, SHELL_PARSER_DEFAULT_CONFIG} from './config'; -import {TemplateStripVisitor} from './node-visitor'; +import {NodeVisitor, TemplateRecoverVisitor, TemplateStripVisitor} from './node-visitor'; import {cssNodeMatcherFactory} from './node-matcher'; import {Parse5TemplateParser} from './template-parser'; import {normalizeConfig} from './shell-parser-factory'; @@ -33,6 +29,47 @@ const prerenderedTemplate = ` `; +const prerenderedTemplateWithCommentedShell = ` + + + + + + + +

Hello world

+
+
+
+ + + +`; + +const processedTemplateWithCommentedShell = ` + + + + + +
+

Hey I'm appshell!

+
+ + + +`; + const strippedWithDefaultSelector = ` @@ -83,12 +120,17 @@ const normalize = (template: string) => .replace(/\s+$/gm, '') .replace(/\n/gm, ''); -const createMockedWorker = (mockScope: MockWorkerScope, config: ShellParserConfig = {}) => { +const createMockedWorker = (mockScope: MockWorkerScope, + config: ShellParserConfig = {}, + visitors: NodeVisitor[] = null) => { config = normalizeConfig(config); + visitors = visitors || + [new TemplateStripVisitor( + cssNodeMatcherFactory(config.NO_RENDER_CSS_SELECTOR))]; return new ShellParserImpl( config, new Parse5TemplateParser(), - [new TemplateStripVisitor(cssNodeMatcherFactory(config.NO_RENDER_CSS_SELECTOR))], + visitors, mockScope); }; @@ -180,6 +222,24 @@ describe('ShellParserImpl', () => { .then(done); }); + describe('parseDoc with commented markup', () => { + it('should strip shellNoRender markup and recover shellRender one', (done: any) => { + const mockScope = new MockWorkerScope(); + const parser = createMockedWorker(mockScope, {}, [ + new TemplateRecoverVisitor('shellRender', new Parse5TemplateParser()), + new TemplateStripVisitor( + cssNodeMatcherFactory('[shellNoRender]')) + ]); + const response = new MockResponse(prerenderedTemplateWithCommentedShell); + parser.parseDoc(response) + .then((response: any) => response.text()) + .then((template: string) => { + expect(normalize(template)).toBe(normalize(processedTemplateWithCommentedShell)); + done(); + }); + }); + }); + describe('match', () => { it('should match routes added to the config', (done: any) => { @@ -264,7 +324,7 @@ describe('ShellParserImpl', () => { .then((data: any) => { expect(data).toBe(null); done(); - }) + }); }); }); diff --git a/app-shell/src/experimental/shell-parser/shell-parser.ts b/app-shell/src/experimental/shell-parser/shell-parser.ts index 00b917e..2da86cc 100644 --- a/app-shell/src/experimental/shell-parser/shell-parser.ts +++ b/app-shell/src/experimental/shell-parser/shell-parser.ts @@ -1,7 +1,6 @@ import {RouteDefinition, ShellParserConfig} from './config'; import {ASTNode} from './ast'; import {NodeVisitor} from './node-visitor'; -import {NodeMatcher} from './node-matcher'; import {TemplateParser} from './template-parser'; import {WorkerScope} from './context'; diff --git a/app-shell/src/experimental/shell-parser/template-parser/parse5/drop-named-entities-patch.ts b/app-shell/src/experimental/shell-parser/template-parser/parse5/drop-named-entities-patch.ts index ee9bb12..4b88152 100644 --- a/app-shell/src/experimental/shell-parser/template-parser/parse5/drop-named-entities-patch.ts +++ b/app-shell/src/experimental/shell-parser/template-parser/parse5/drop-named-entities-patch.ts @@ -29,7 +29,7 @@ function isAsciiAlphaNumeric(cp: number) { function isDigit(cp: number, isHex: boolean) { return isAsciiDigit(cp) || isHex && (cp >= CP.LATIN_CAPITAL_A && cp <= CP.LATIN_CAPITAL_F || - cp >= CP.LATIN_SMALL_A && cp <= CP.LATIN_SMALL_F); + cp >= CP.LATIN_SMALL_A && cp <= CP.LATIN_SMALL_F); } Serializer.escapeString = function (str: string) { diff --git a/app-shell/src/experimental/shell-parser/template-parser/parse5/parse5-template-parser.ts b/app-shell/src/experimental/shell-parser/template-parser/parse5/parse5-template-parser.ts index 185f2c1..8e13798 100644 --- a/app-shell/src/experimental/shell-parser/template-parser/parse5/parse5-template-parser.ts +++ b/app-shell/src/experimental/shell-parser/template-parser/parse5/parse5-template-parser.ts @@ -13,6 +13,11 @@ export class Parse5TemplateParser extends TemplateParser { return parser.parse(template); } + parseFragment(fragment: string) { + var parser = new Parser(); + return parser.parseFragment(fragment); + } + serialize(node: ASTNode): string { var serializer = new Serializer(node); return serializer.serialize(); diff --git a/app-shell/src/experimental/shell-parser/template-parser/template-parser.ts b/app-shell/src/experimental/shell-parser/template-parser/template-parser.ts index d072fed..a374d98 100644 --- a/app-shell/src/experimental/shell-parser/template-parser/template-parser.ts +++ b/app-shell/src/experimental/shell-parser/template-parser/template-parser.ts @@ -2,6 +2,7 @@ import {ASTNode} from '../ast'; export abstract class TemplateParser { abstract parse(template: string): ASTNode; + abstract parseFragment(fragment: string): ASTNode; abstract serialize(node: ASTNode): string; }