From 8a0813e96b08235dbbbb5d6151a1e30f76e16c89 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 25 Nov 2017 10:34:44 -0500 Subject: [PATCH] SSR await-then-catch --- src/generators/server-side-rendering/index.ts | 31 +++++++++----- .../server-side-rendering/preprocess.ts | 10 +++++ .../visitors/AwaitBlock.ts | 40 +++++++++++++++++++ .../server-side-rendering/visitors/index.ts | 2 + src/parse/state/mustache.ts | 3 +- .../samples/ssr-no-oncreate-etc/expected.js | 12 ------ 6 files changed, 76 insertions(+), 22 deletions(-) create mode 100644 src/generators/server-side-rendering/visitors/AwaitBlock.ts diff --git a/src/generators/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index 2accc2bc7e..e08c10ff14 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -159,16 +159,29 @@ export default function ssr( }; }; - var escaped = { - '"': '"', - "'": '&##39;', - '&': '&', - '<': '<', - '>': '>' - }; + ${ + // TODO this is a bit hacky + /__escape/.test(generator.renderCode) && deindent` + var escaped = { + '"': '"', + "'": '&##39;', + '&': '&', + '<': '<', + '>': '>' + }; + + function __escape(html) { + return String(html).replace(/["'&<>]/g, match => escaped[match]); + } + ` + } - function __escape(html) { - return String(html).replace(/["'&<>]/g, match => escaped[match]); + ${ + /__isPromise/.test(generator.renderCode) && deindent` + function __isPromise(value) { + return value && typeof value.then === 'function'; + } + ` } `.replace(/(@+|#+|%+)(\w*(?:-\w*)?)/g, (match: string, sigil: string, name: string) => { if (sigil === '@') return generator.alias(name); diff --git a/src/generators/server-side-rendering/preprocess.ts b/src/generators/server-side-rendering/preprocess.ts index 0734a135f2..7e9cb7365d 100644 --- a/src/generators/server-side-rendering/preprocess.ts +++ b/src/generators/server-side-rendering/preprocess.ts @@ -10,6 +10,16 @@ const preprocessors = { RawMustacheTag: noop, Text: noop, + AwaitBlock: ( + generator: SsrGenerator, + node: Node, + elementStack: Node[] + ) => { + preprocessChildren(generator, node.pending, elementStack); + preprocessChildren(generator, node.then, elementStack); + preprocessChildren(generator, node.catch, elementStack); + }, + IfBlock: ( generator: SsrGenerator, node: Node, diff --git a/src/generators/server-side-rendering/visitors/AwaitBlock.ts b/src/generators/server-side-rendering/visitors/AwaitBlock.ts new file mode 100644 index 0000000000..6251eb15c0 --- /dev/null +++ b/src/generators/server-side-rendering/visitors/AwaitBlock.ts @@ -0,0 +1,40 @@ +import visit from '../visit'; +import { SsrGenerator } from '../index'; +import Block from '../Block'; +import { Node } from '../../../interfaces'; + +export default function visitAwaitBlock( + generator: SsrGenerator, + block: Block, + node: Node +) { + block.contextualise(node.expression); + const { dependencies, snippet } = node.metadata; + + // TODO should this be the generator's job? It's duplicated between + // here and the equivalent DOM compiler visitor + const contexts = new Map(block.contexts); + contexts.set(node.value, '__value'); + + const contextDependencies = new Map(block.contextDependencies); + contextDependencies.set(node.value, dependencies); + + const childBlock = block.child({ + contextDependencies, + contexts + }); + + generator.append('${(function(__value) { if(__isPromise(__value)) return `'); + + node.pending.children.forEach((child: Node) => { + visit(generator, childBlock, child); + }); + + generator.append('`; return `'); + + node.then.children.forEach((child: Node) => { + visit(generator, childBlock, child); + }); + + generator.append(`\`;}(${snippet})) }`); +} diff --git a/src/generators/server-side-rendering/visitors/index.ts b/src/generators/server-side-rendering/visitors/index.ts index 0ed4a58e22..468239a949 100644 --- a/src/generators/server-side-rendering/visitors/index.ts +++ b/src/generators/server-side-rendering/visitors/index.ts @@ -1,3 +1,4 @@ +import AwaitBlock from './AwaitBlock'; import Comment from './Comment'; import EachBlock from './EachBlock'; import Element from './Element'; @@ -7,6 +8,7 @@ import RawMustacheTag from './RawMustacheTag'; import Text from './Text'; export default { + AwaitBlock, Comment, EachBlock, Element, diff --git a/src/parse/state/mustache.ts b/src/parse/state/mustache.ts index 10b6751af3..b59cf92b8f 100644 --- a/src/parse/state/mustache.ts +++ b/src/parse/state/mustache.ts @@ -209,7 +209,7 @@ export default function mustache(parser: Parser) { value: null, error: null, pending: { - start, + start: null, end: null, type: 'PendingBlock', children: [] @@ -289,6 +289,7 @@ export default function mustache(parser: Parser) { parser.stack.push(block); if (type === 'AwaitBlock') { + block.pending.start = parser.index; parser.stack.push(block.pending); } } else if (parser.eat('yield')) { diff --git a/test/js/samples/ssr-no-oncreate-etc/expected.js b/test/js/samples/ssr-no-oncreate-etc/expected.js index 51c10c7656..e8e5330377 100644 --- a/test/js/samples/ssr-no-oncreate-etc/expected.js +++ b/test/js/samples/ssr-no-oncreate-etc/expected.js @@ -22,16 +22,4 @@ SvelteComponent.renderCss = function() { }; }; -var escaped = { - '"': '"', - "'": ''', - '&': '&', - '<': '<', - '>': '>' -}; - -function __escape(html) { - return String(html).replace(/["'&<>]/g, match => escaped[match]); -} - module.exports = SvelteComponent; \ No newline at end of file