Skip to content

Commit 7e40ee2

Browse files
authored
Merge pull request #952 from sveltejs/gh-654
await-then-catch
2 parents fd77c40 + 844e89f commit 7e40ee2

File tree

22 files changed

+813
-125
lines changed

22 files changed

+813
-125
lines changed

src/generators/Generator.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,16 @@ export default class Generator {
728728
}
729729
}
730730

731+
if (node.type === 'AwaitBlock') {
732+
node.metadata = contextualise(node.expression, contextDependencies, indexes);
733+
734+
contextDependencies = new Map(contextDependencies);
735+
contextDependencies.set(node.value, node.metadata.dependencies);
736+
contextDependencies.set(node.error, node.metadata.dependencies);
737+
738+
contextDependenciesStack.push(contextDependencies);
739+
}
740+
731741
if (node.type === 'IfBlock') {
732742
node.metadata = contextualise(node.expression, contextDependencies, indexes);
733743
}

src/generators/dom/preprocess.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,59 @@ const preprocessors = {
108108
node.var = block.getUniqueName(`text`);
109109
},
110110

111+
AwaitBlock: (
112+
generator: DomGenerator,
113+
block: Block,
114+
state: State,
115+
node: Node,
116+
inEachBlock: boolean,
117+
elementStack: Node[],
118+
componentStack: Node[],
119+
stripWhitespace: boolean,
120+
nextSibling: Node
121+
) => {
122+
cannotUseInnerHTML(node);
123+
124+
node.var = block.getUniqueName('await_block');
125+
block.addDependencies(node.metadata.dependencies);
126+
127+
let dynamic = false;
128+
129+
[
130+
['pending', null],
131+
['then', node.value],
132+
['catch', node.error]
133+
].forEach(([status, arg]) => {
134+
const child = node[status];
135+
136+
const context = block.getUniqueName(arg || '_');
137+
const contexts = new Map(block.contexts);
138+
contexts.set(arg, context);
139+
140+
child._block = block.child({
141+
comment: createDebuggingComment(child, generator),
142+
name: generator.getUniqueName(`create_${status}_block`),
143+
params: block.params.concat(context),
144+
context,
145+
contexts
146+
});
147+
148+
child._state = getChildState(state);
149+
150+
preprocessChildren(generator, child._block, child._state, child, inEachBlock, elementStack, componentStack, stripWhitespace, nextSibling);
151+
generator.blocks.push(child._block);
152+
153+
if (child._block.dependencies.size > 0) {
154+
dynamic = true;
155+
block.addDependencies(child._block.dependencies);
156+
}
157+
});
158+
159+
node.pending._block.hasUpdateMethod = dynamic;
160+
node.then._block.hasUpdateMethod = dynamic;
161+
node.catch._block.hasUpdateMethod = dynamic;
162+
},
163+
111164
IfBlock: (
112165
generator: DomGenerator,
113166
block: Block,
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import deindent from '../../../utils/deindent';
2+
import visit from '../visit';
3+
import { DomGenerator } from '../index';
4+
import Block from '../Block';
5+
import isDomNode from './shared/isDomNode';
6+
import { Node } from '../../../interfaces';
7+
import { State } from '../interfaces';
8+
9+
export default function visitAwaitBlock(
10+
generator: DomGenerator,
11+
block: Block,
12+
state: State,
13+
node: Node,
14+
elementStack: Node[],
15+
componentStack: Node[]
16+
) {
17+
const name = node.var;
18+
19+
const needsAnchor = node.next ? !isDomNode(node.next, generator) : !state.parentNode || !isDomNode(node.parent, generator);
20+
const anchor = needsAnchor
21+
? block.getUniqueName(`${name}_anchor`)
22+
: (node.next && node.next.var) || 'null';
23+
24+
const params = block.params.join(', ');
25+
26+
block.contextualise(node.expression);
27+
const { snippet } = node.metadata;
28+
29+
if (needsAnchor) {
30+
block.addElement(
31+
anchor,
32+
`@createComment()`,
33+
`@createComment()`,
34+
state.parentNode
35+
);
36+
}
37+
38+
const promise = block.getUniqueName(`promise`);
39+
const resolved = block.getUniqueName(`resolved`);
40+
const await_block = block.getUniqueName(`await_block`);
41+
const await_block_type = block.getUniqueName(`await_block_type`);
42+
const token = block.getUniqueName(`token`);
43+
const await_token = block.getUniqueName(`await_token`);
44+
const handle_promise = block.getUniqueName(`handle_promise`);
45+
const replace_await_block = block.getUniqueName(`replace_await_block`);
46+
const old_block = block.getUniqueName(`old_block`);
47+
const value = block.getUniqueName(`value`);
48+
const error = block.getUniqueName(`error`);
49+
const create_pending_block = node.pending._block.name;
50+
const create_then_block = node.then._block.name;
51+
const create_catch_block = node.catch._block.name;
52+
53+
block.addVariable(await_block);
54+
block.addVariable(await_block_type);
55+
block.addVariable(await_token);
56+
block.addVariable(promise);
57+
block.addVariable(resolved);
58+
59+
block.builders.init.addBlock(deindent`
60+
function ${replace_await_block}(${token}, type, ${value}, ${params}) {
61+
if (${token} !== ${await_token}) return;
62+
63+
var ${old_block} = ${await_block};
64+
${await_block} = (${await_block_type} = type)(${params}, ${resolved} = ${value}, #component);
65+
66+
if (${old_block}) {
67+
${old_block}.u();
68+
${old_block}.d();
69+
${await_block}.c();
70+
${await_block}.m(${anchor}.parentNode, ${anchor});
71+
}
72+
}
73+
74+
function ${handle_promise}(${promise}, ${params}) {
75+
var ${token} = ${await_token} = {};
76+
77+
if (@isPromise(${promise})) {
78+
${promise}.then(function(${value}) {
79+
${replace_await_block}(${token}, ${create_then_block}, ${value}, ${params});
80+
}, function (${error}) {
81+
${replace_await_block}(${token}, ${create_catch_block}, ${error}, ${params});
82+
});
83+
84+
// if we previously had a then/catch block, destroy it
85+
if (${await_block_type} !== ${create_pending_block}) {
86+
${replace_await_block}(${token}, ${create_pending_block}, null, ${params});
87+
return true;
88+
}
89+
} else {
90+
${resolved} = ${promise};
91+
if (${await_block_type} !== ${create_then_block}) {
92+
${replace_await_block}(${token}, ${create_then_block}, ${resolved}, ${params});
93+
return true;
94+
}
95+
}
96+
}
97+
98+
${handle_promise}(${promise} = ${snippet}, ${params});
99+
`);
100+
101+
block.builders.create.addBlock(deindent`
102+
${await_block}.c();
103+
`);
104+
105+
block.builders.claim.addBlock(deindent`
106+
${await_block}.l(${state.parentNodes});
107+
`);
108+
109+
const targetNode = state.parentNode || '#target';
110+
const anchorNode = state.parentNode ? 'null' : 'anchor';
111+
112+
block.builders.mount.addBlock(deindent`
113+
${await_block}.m(${targetNode}, ${anchorNode});
114+
`);
115+
116+
const conditions = [];
117+
if (node.metadata.dependencies) {
118+
conditions.push(
119+
`(${node.metadata.dependencies.map(dep => `'${dep}' in changed`).join(' || ')})`
120+
);
121+
}
122+
123+
conditions.push(
124+
`${promise} !== (${promise} = ${snippet})`,
125+
`${handle_promise}(${promise}, ${params})`
126+
);
127+
128+
if (node.pending._block.hasUpdateMethod) {
129+
block.builders.update.addBlock(deindent`
130+
if (${conditions.join(' && ')}) {
131+
// nothing
132+
} else {
133+
${await_block}.p(changed, ${params}, ${resolved});
134+
}
135+
`);
136+
} else {
137+
block.builders.update.addBlock(deindent`
138+
if (${conditions.join(' && ')}) {
139+
${await_block}.c();
140+
${await_block}.m(${anchor}.parentNode, ${anchor});
141+
}
142+
`);
143+
}
144+
145+
block.builders.destroy.addBlock(deindent`
146+
${await_token} = null;
147+
${await_block}.d();
148+
`);
149+
150+
[node.pending, node.then, node.catch].forEach(status => {
151+
status.children.forEach(child => {
152+
visit(generator, status._block, status._state, child, elementStack, componentStack);
153+
});
154+
});
155+
}

src/generators/dom/visitors/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import AwaitBlock from './AwaitBlock';
12
import EachBlock from './EachBlock';
23
import Element from './Element/Element';
34
import IfBlock from './IfBlock';
@@ -7,6 +8,7 @@ import Text from './Text';
78
import { Visitor } from '../interfaces';
89

910
const visitors: Record<string, Visitor> = {
11+
AwaitBlock,
1012
EachBlock,
1113
Element,
1214
IfBlock,

src/generators/server-side-rendering/index.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -165,16 +165,29 @@ export default function ssr(
165165
};
166166
};
167167
168-
var escaped = {
169-
'"': '&quot;',
170-
"'": '&##39;',
171-
'&': '&amp;',
172-
'<': '&lt;',
173-
'>': '&gt;'
174-
};
168+
${
169+
// TODO this is a bit hacky
170+
/__escape/.test(generator.renderCode) && deindent`
171+
var escaped = {
172+
'"': '&quot;',
173+
"'": '&##39;',
174+
'&': '&amp;',
175+
'<': '&lt;',
176+
'>': '&gt;'
177+
};
178+
179+
function __escape(html) {
180+
return String(html).replace(/["'&<>]/g, match => escaped[match]);
181+
}
182+
`
183+
}
175184
176-
function __escape(html) {
177-
return String(html).replace(/["'&<>]/g, match => escaped[match]);
185+
${
186+
/__isPromise/.test(generator.renderCode) && deindent`
187+
function __isPromise(value) {
188+
return value && typeof value.then === 'function';
189+
}
190+
`
178191
}
179192
`.replace(/(@+|#+|%+)(\w*(?:-\w*)?)/g, (match: string, sigil: string, name: string) => {
180193
if (sigil === '@') return generator.alias(name);

src/generators/server-side-rendering/preprocess.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ const preprocessors = {
1010
RawMustacheTag: noop,
1111
Text: noop,
1212

13+
AwaitBlock: (
14+
generator: SsrGenerator,
15+
node: Node,
16+
elementStack: Node[]
17+
) => {
18+
preprocessChildren(generator, node.pending, elementStack);
19+
preprocessChildren(generator, node.then, elementStack);
20+
preprocessChildren(generator, node.catch, elementStack);
21+
},
22+
1323
IfBlock: (
1424
generator: SsrGenerator,
1525
node: Node,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import visit from '../visit';
2+
import { SsrGenerator } from '../index';
3+
import Block from '../Block';
4+
import { Node } from '../../../interfaces';
5+
6+
export default function visitAwaitBlock(
7+
generator: SsrGenerator,
8+
block: Block,
9+
node: Node
10+
) {
11+
block.contextualise(node.expression);
12+
const { dependencies, snippet } = node.metadata;
13+
14+
// TODO should this be the generator's job? It's duplicated between
15+
// here and the equivalent DOM compiler visitor
16+
const contexts = new Map(block.contexts);
17+
contexts.set(node.value, '__value');
18+
19+
const contextDependencies = new Map(block.contextDependencies);
20+
contextDependencies.set(node.value, dependencies);
21+
22+
const childBlock = block.child({
23+
contextDependencies,
24+
contexts
25+
});
26+
27+
generator.append('${(function(__value) { if(__isPromise(__value)) return `');
28+
29+
node.pending.children.forEach((child: Node) => {
30+
visit(generator, childBlock, child);
31+
});
32+
33+
generator.append('`; return `');
34+
35+
node.then.children.forEach((child: Node) => {
36+
visit(generator, childBlock, child);
37+
});
38+
39+
generator.append(`\`;}(${snippet})) }`);
40+
}

src/generators/server-side-rendering/visitors/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import AwaitBlock from './AwaitBlock';
12
import Comment from './Comment';
23
import EachBlock from './EachBlock';
34
import Element from './Element';
@@ -7,6 +8,7 @@ import RawMustacheTag from './RawMustacheTag';
78
import Text from './Text';
89

910
export default {
11+
AwaitBlock,
1012
Comment,
1113
EachBlock,
1214
Element,

src/parse/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import fragment from './state/fragment';
33
import { whitespace } from '../utils/patterns';
44
import { trimStart, trimEnd } from '../utils/trim';
55
import getCodeFrame from '../utils/getCodeFrame';
6+
import reservedNames from '../utils/reservedNames';
67
import hash from './utils/hash';
78
import { Node, Parsed } from '../interfaces';
89
import CompileError from '../utils/CompileError';
@@ -139,6 +140,17 @@ export class Parser {
139140
return match[0];
140141
}
141142

143+
readIdentifier() {
144+
const start = this.index;
145+
const identifier = this.read(/[a-zA-Z_$][a-zA-Z0-9_$]*/);
146+
147+
if (reservedNames.has(identifier)) {
148+
this.error(`'${identifier}' is a reserved word in JavaScript and cannot be used here`, start);
149+
}
150+
151+
return identifier;
152+
}
153+
142154
readUntil(pattern: RegExp) {
143155
if (this.index >= this.template.length)
144156
this.error('Unexpected end of input');

0 commit comments

Comments
 (0)