mirror of https://github.com/sveltejs/svelte
commit
d6814d76e4
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
@ -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})) }`);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{{#await thePromise}}
|
||||
<p>loading...</p>
|
||||
{{then theValue}}
|
||||
<p>the value is {{theValue}}</p>
|
||||
{{catch theError}}
|
||||
<p>oh no! {{theError.message}}</p>
|
||||
{{/await}}
|
@ -0,0 +1,161 @@
|
||||
{
|
||||
"hash": 1040536517,
|
||||
"html": {
|
||||
"start": 0,
|
||||
"end": 158,
|
||||
"type": "Fragment",
|
||||
"children": [
|
||||
{
|
||||
"start": 0,
|
||||
"end": 158,
|
||||
"type": "AwaitBlock",
|
||||
"expression": {
|
||||
"type": "Identifier",
|
||||
"start": 9,
|
||||
"end": 19,
|
||||
"name": "thePromise"
|
||||
},
|
||||
"value": "theValue",
|
||||
"error": "theError",
|
||||
"pending": {
|
||||
"start": 21,
|
||||
"end": 41,
|
||||
"type": "PendingBlock",
|
||||
"children": [
|
||||
{
|
||||
"start": 21,
|
||||
"end": 23,
|
||||
"type": "Text",
|
||||
"data": "\n\t"
|
||||
},
|
||||
{
|
||||
"start": 23,
|
||||
"end": 40,
|
||||
"type": "Element",
|
||||
"name": "p",
|
||||
"attributes": [],
|
||||
"children": [
|
||||
{
|
||||
"start": 26,
|
||||
"end": 36,
|
||||
"type": "Text",
|
||||
"data": "loading..."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"start": 40,
|
||||
"end": 41,
|
||||
"type": "Text",
|
||||
"data": "\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"start": 41,
|
||||
"end": 93,
|
||||
"type": "ThenBlock",
|
||||
"children": [
|
||||
{
|
||||
"start": 58,
|
||||
"end": 60,
|
||||
"type": "Text",
|
||||
"data": "\n\t"
|
||||
},
|
||||
{
|
||||
"start": 60,
|
||||
"end": 92,
|
||||
"type": "Element",
|
||||
"name": "p",
|
||||
"attributes": [],
|
||||
"children": [
|
||||
{
|
||||
"start": 63,
|
||||
"end": 76,
|
||||
"type": "Text",
|
||||
"data": "the value is "
|
||||
},
|
||||
{
|
||||
"start": 76,
|
||||
"end": 88,
|
||||
"type": "MustacheTag",
|
||||
"expression": {
|
||||
"type": "Identifier",
|
||||
"start": 78,
|
||||
"end": 86,
|
||||
"name": "theValue"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"start": 92,
|
||||
"end": 93,
|
||||
"type": "Text",
|
||||
"data": "\n"
|
||||
}
|
||||
]
|
||||
},
|
||||
"catch": {
|
||||
"start": 93,
|
||||
"end": 148,
|
||||
"type": "CatchBlock",
|
||||
"children": [
|
||||
{
|
||||
"start": 111,
|
||||
"end": 113,
|
||||
"type": "Text",
|
||||
"data": "\n\t"
|
||||
},
|
||||
{
|
||||
"start": 113,
|
||||
"end": 147,
|
||||
"type": "Element",
|
||||
"name": "p",
|
||||
"attributes": [],
|
||||
"children": [
|
||||
{
|
||||
"start": 116,
|
||||
"end": 123,
|
||||
"type": "Text",
|
||||
"data": "oh no! "
|
||||
},
|
||||
{
|
||||
"start": 123,
|
||||
"end": 143,
|
||||
"type": "MustacheTag",
|
||||
"expression": {
|
||||
"type": "MemberExpression",
|
||||
"start": 125,
|
||||
"end": 141,
|
||||
"object": {
|
||||
"type": "Identifier",
|
||||
"start": 125,
|
||||
"end": 133,
|
||||
"name": "theError"
|
||||
},
|
||||
"property": {
|
||||
"type": "Identifier",
|
||||
"start": 134,
|
||||
"end": 141,
|
||||
"name": "message"
|
||||
},
|
||||
"computed": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"start": 147,
|
||||
"end": 148,
|
||||
"type": "Text",
|
||||
"data": "\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"css": null,
|
||||
"js": null
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
let fulfil;
|
||||
|
||||
let thePromise = new Promise(f => {
|
||||
fulfil = f;
|
||||
});
|
||||
|
||||
export default {
|
||||
data: {
|
||||
thePromise
|
||||
},
|
||||
|
||||
html: `
|
||||
<p>loading...</p>
|
||||
<p>loading...</p>
|
||||
`,
|
||||
|
||||
test(assert, component, target) {
|
||||
fulfil(42);
|
||||
|
||||
return thePromise
|
||||
.then(() => {
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<p>the value is 42</p>
|
||||
<p>the value is 42</p>
|
||||
`);
|
||||
|
||||
let reject;
|
||||
|
||||
thePromise = new Promise((f, r) => {
|
||||
reject = r;
|
||||
});
|
||||
|
||||
component.set({
|
||||
thePromise
|
||||
});
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<p>loading...</p>
|
||||
<p>loading...</p>
|
||||
`);
|
||||
|
||||
reject(new Error('something broke'));
|
||||
|
||||
return thePromise.catch(() => {});
|
||||
})
|
||||
.then(() => {
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<p>oh no! something broke</p>
|
||||
<p>oh no! something broke</p>
|
||||
`);
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,15 @@
|
||||
{{#await thePromise}}
|
||||
<p>loading...</p>
|
||||
{{then theValue}}
|
||||
<p>the value is {{theValue}}</p>
|
||||
{{catch theError}}
|
||||
<p>oh no! {{theError.message}}</p>
|
||||
{{/await}}
|
||||
|
||||
{{#await thePromise}}
|
||||
<p>loading...</p>
|
||||
{{then theValue}}
|
||||
<p>the value is {{theValue}}</p>
|
||||
{{catch theError}}
|
||||
<p>oh no! {{theError.message}}</p>
|
||||
{{/await}}
|
@ -0,0 +1,19 @@
|
||||
export default {
|
||||
data: {
|
||||
thePromise: 'not actually a promise'
|
||||
},
|
||||
|
||||
html: `
|
||||
<p>the value is not actually a promise</p>
|
||||
`,
|
||||
|
||||
test(assert, component, target) {
|
||||
component.set({
|
||||
thePromise: 'still not a promise'
|
||||
});
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<p>the value is still not a promise</p>
|
||||
`);
|
||||
}
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
{{#await thePromise}}
|
||||
<p>loading...</p>
|
||||
{{then theValue}}
|
||||
<p>the value is {{theValue}}</p>
|
||||
{{catch theError}}
|
||||
<p>oh no! {{theError.message}}</p>
|
||||
{{/await}}
|
@ -0,0 +1,49 @@
|
||||
let fulfil;
|
||||
|
||||
let thePromise = new Promise(f => {
|
||||
fulfil = f;
|
||||
});
|
||||
|
||||
export default {
|
||||
data: {
|
||||
thePromise
|
||||
},
|
||||
|
||||
html: `
|
||||
<p>loading...</p>
|
||||
`,
|
||||
|
||||
test(assert, component, target) {
|
||||
fulfil(42);
|
||||
|
||||
return thePromise
|
||||
.then(() => {
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<p>the value is 42</p>
|
||||
`);
|
||||
|
||||
let reject;
|
||||
|
||||
thePromise = new Promise((f, r) => {
|
||||
reject = r;
|
||||
});
|
||||
|
||||
component.set({
|
||||
thePromise
|
||||
});
|
||||
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<p>loading...</p>
|
||||
`);
|
||||
|
||||
reject(new Error('something broke'));
|
||||
|
||||
return thePromise.catch(() => {});
|
||||
})
|
||||
.then(() => {
|
||||
assert.htmlEqual(target.innerHTML, `
|
||||
<p>oh no! something broke</p>
|
||||
`);
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
{{#await thePromise}}
|
||||
<p>loading...</p>
|
||||
{{then theValue}}
|
||||
<p>the value is {{theValue}}</p>
|
||||
{{catch theError}}
|
||||
<p>oh no! {{theError.message}}</p>
|
||||
{{/await}}
|
Loading…
Reference in new issue