Skip to content

Commit

Permalink
Merge pull request #952 from sveltejs/gh-654
Browse files Browse the repository at this point in the history
await-then-catch
  • Loading branch information
Rich-Harris authored Dec 3, 2017
2 parents fd77c40 + 844e89f commit 7e40ee2
Show file tree
Hide file tree
Showing 22 changed files with 813 additions and 125 deletions.
10 changes: 10 additions & 0 deletions src/generators/Generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,16 @@ export default class Generator {
}
}

if (node.type === 'AwaitBlock') {
node.metadata = contextualise(node.expression, contextDependencies, indexes);

contextDependencies = new Map(contextDependencies);
contextDependencies.set(node.value, node.metadata.dependencies);
contextDependencies.set(node.error, node.metadata.dependencies);

contextDependenciesStack.push(contextDependencies);
}

if (node.type === 'IfBlock') {
node.metadata = contextualise(node.expression, contextDependencies, indexes);
}
Expand Down
53 changes: 53 additions & 0 deletions src/generators/dom/preprocess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,59 @@ const preprocessors = {
node.var = block.getUniqueName(`text`);
},

AwaitBlock: (
generator: DomGenerator,
block: Block,
state: State,
node: Node,
inEachBlock: boolean,
elementStack: Node[],
componentStack: Node[],
stripWhitespace: boolean,
nextSibling: Node
) => {
cannotUseInnerHTML(node);

node.var = block.getUniqueName('await_block');
block.addDependencies(node.metadata.dependencies);

let dynamic = false;

[
['pending', null],
['then', node.value],
['catch', node.error]
].forEach(([status, arg]) => {
const child = node[status];

const context = block.getUniqueName(arg || '_');
const contexts = new Map(block.contexts);
contexts.set(arg, context);

child._block = block.child({
comment: createDebuggingComment(child, generator),
name: generator.getUniqueName(`create_${status}_block`),
params: block.params.concat(context),
context,
contexts
});

child._state = getChildState(state);

preprocessChildren(generator, child._block, child._state, child, inEachBlock, elementStack, componentStack, stripWhitespace, nextSibling);
generator.blocks.push(child._block);

if (child._block.dependencies.size > 0) {
dynamic = true;
block.addDependencies(child._block.dependencies);
}
});

node.pending._block.hasUpdateMethod = dynamic;
node.then._block.hasUpdateMethod = dynamic;
node.catch._block.hasUpdateMethod = dynamic;
},

IfBlock: (
generator: DomGenerator,
block: Block,
Expand Down
155 changes: 155 additions & 0 deletions src/generators/dom/visitors/AwaitBlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import deindent from '../../../utils/deindent';
import visit from '../visit';
import { DomGenerator } from '../index';
import Block from '../Block';
import isDomNode from './shared/isDomNode';
import { Node } from '../../../interfaces';
import { State } from '../interfaces';

export default function visitAwaitBlock(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
elementStack: Node[],
componentStack: Node[]
) {
const name = node.var;

const needsAnchor = node.next ? !isDomNode(node.next, generator) : !state.parentNode || !isDomNode(node.parent, generator);
const anchor = needsAnchor
? block.getUniqueName(`${name}_anchor`)
: (node.next && node.next.var) || 'null';

const params = block.params.join(', ');

block.contextualise(node.expression);
const { snippet } = node.metadata;

if (needsAnchor) {
block.addElement(
anchor,
`@createComment()`,
`@createComment()`,
state.parentNode
);
}

const promise = block.getUniqueName(`promise`);
const resolved = block.getUniqueName(`resolved`);
const await_block = block.getUniqueName(`await_block`);
const await_block_type = block.getUniqueName(`await_block_type`);
const token = block.getUniqueName(`token`);
const await_token = block.getUniqueName(`await_token`);
const handle_promise = block.getUniqueName(`handle_promise`);
const replace_await_block = block.getUniqueName(`replace_await_block`);
const old_block = block.getUniqueName(`old_block`);
const value = block.getUniqueName(`value`);
const error = block.getUniqueName(`error`);
const create_pending_block = node.pending._block.name;
const create_then_block = node.then._block.name;
const create_catch_block = node.catch._block.name;

block.addVariable(await_block);
block.addVariable(await_block_type);
block.addVariable(await_token);
block.addVariable(promise);
block.addVariable(resolved);

block.builders.init.addBlock(deindent`
function ${replace_await_block}(${token}, type, ${value}, ${params}) {
if (${token} !== ${await_token}) return;
var ${old_block} = ${await_block};
${await_block} = (${await_block_type} = type)(${params}, ${resolved} = ${value}, #component);
if (${old_block}) {
${old_block}.u();
${old_block}.d();
${await_block}.c();
${await_block}.m(${anchor}.parentNode, ${anchor});
}
}
function ${handle_promise}(${promise}, ${params}) {
var ${token} = ${await_token} = {};
if (@isPromise(${promise})) {
${promise}.then(function(${value}) {
${replace_await_block}(${token}, ${create_then_block}, ${value}, ${params});
}, function (${error}) {
${replace_await_block}(${token}, ${create_catch_block}, ${error}, ${params});
});
// if we previously had a then/catch block, destroy it
if (${await_block_type} !== ${create_pending_block}) {
${replace_await_block}(${token}, ${create_pending_block}, null, ${params});
return true;
}
} else {
${resolved} = ${promise};
if (${await_block_type} !== ${create_then_block}) {
${replace_await_block}(${token}, ${create_then_block}, ${resolved}, ${params});
return true;
}
}
}
${handle_promise}(${promise} = ${snippet}, ${params});
`);

block.builders.create.addBlock(deindent`
${await_block}.c();
`);

block.builders.claim.addBlock(deindent`
${await_block}.l(${state.parentNodes});
`);

const targetNode = state.parentNode || '#target';
const anchorNode = state.parentNode ? 'null' : 'anchor';

block.builders.mount.addBlock(deindent`
${await_block}.m(${targetNode}, ${anchorNode});
`);

const conditions = [];
if (node.metadata.dependencies) {
conditions.push(
`(${node.metadata.dependencies.map(dep => `'${dep}' in changed`).join(' || ')})`
);
}

conditions.push(
`${promise} !== (${promise} = ${snippet})`,
`${handle_promise}(${promise}, ${params})`
);

if (node.pending._block.hasUpdateMethod) {
block.builders.update.addBlock(deindent`
if (${conditions.join(' && ')}) {
// nothing
} else {
${await_block}.p(changed, ${params}, ${resolved});
}
`);
} else {
block.builders.update.addBlock(deindent`
if (${conditions.join(' && ')}) {
${await_block}.c();
${await_block}.m(${anchor}.parentNode, ${anchor});
}
`);
}

block.builders.destroy.addBlock(deindent`
${await_token} = null;
${await_block}.d();
`);

[node.pending, node.then, node.catch].forEach(status => {
status.children.forEach(child => {
visit(generator, status._block, status._state, child, elementStack, componentStack);
});
});
}
2 changes: 2 additions & 0 deletions src/generators/dom/visitors/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import AwaitBlock from './AwaitBlock';
import EachBlock from './EachBlock';
import Element from './Element/Element';
import IfBlock from './IfBlock';
Expand All @@ -7,6 +8,7 @@ import Text from './Text';
import { Visitor } from '../interfaces';

const visitors: Record<string, Visitor> = {
AwaitBlock,
EachBlock,
Element,
IfBlock,
Expand Down
31 changes: 22 additions & 9 deletions src/generators/server-side-rendering/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,16 +165,29 @@ export default function ssr(
};
};
var escaped = {
'"': '&quot;',
"'": '&##39;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;'
};
${
// TODO this is a bit hacky
/__escape/.test(generator.renderCode) && deindent`
var escaped = {
'"': '&quot;',
"'": '&##39;',
'&': '&amp;',
'<': '&lt;',
'>': '&gt;'
};
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);
Expand Down
10 changes: 10 additions & 0 deletions src/generators/server-side-rendering/preprocess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
40 changes: 40 additions & 0 deletions src/generators/server-side-rendering/visitors/AwaitBlock.ts
Original file line number Diff line number Diff line change
@@ -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})) }`);
}
2 changes: 2 additions & 0 deletions src/generators/server-side-rendering/visitors/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import AwaitBlock from './AwaitBlock';
import Comment from './Comment';
import EachBlock from './EachBlock';
import Element from './Element';
Expand All @@ -7,6 +8,7 @@ import RawMustacheTag from './RawMustacheTag';
import Text from './Text';

export default {
AwaitBlock,
Comment,
EachBlock,
Element,
Expand Down
12 changes: 12 additions & 0 deletions src/parse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fragment from './state/fragment';
import { whitespace } from '../utils/patterns';
import { trimStart, trimEnd } from '../utils/trim';
import getCodeFrame from '../utils/getCodeFrame';
import reservedNames from '../utils/reservedNames';
import hash from './utils/hash';
import { Node, Parsed } from '../interfaces';
import CompileError from '../utils/CompileError';
Expand Down Expand Up @@ -139,6 +140,17 @@ export class Parser {
return match[0];
}

readIdentifier() {
const start = this.index;
const identifier = this.read(/[a-zA-Z_$][a-zA-Z0-9_$]*/);

if (reservedNames.has(identifier)) {
this.error(`'${identifier}' is a reserved word in JavaScript and cannot be used here`, start);
}

return identifier;
}

readUntil(pattern: RegExp) {
if (this.index >= this.template.length)
this.error('Unexpected end of input');
Expand Down
Loading

0 comments on commit 7e40ee2

Please sign in to comment.