mirror of https://github.com/sveltejs/svelte
Merge 5186155999 into da00abe116
commit
378e924744
@ -1,16 +1,22 @@
|
||||
/** @import { BlockStatement } from 'estree' */
|
||||
/** @import { AST } from '#compiler' */
|
||||
/** @import { ComponentContext } from '../types.js' */
|
||||
import { empty_comment } from './shared/utils.js';
|
||||
import { block_close, block_open, empty_comment } from './shared/utils.js';
|
||||
|
||||
/**
|
||||
* @param {AST.KeyBlock} node
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function KeyBlock(node, context) {
|
||||
const is_async = node.metadata.expression.is_async();
|
||||
|
||||
if (is_async) context.state.template.push(block_open);
|
||||
|
||||
context.state.template.push(
|
||||
empty_comment,
|
||||
/** @type {BlockStatement} */ (context.visit(node.fragment)),
|
||||
empty_comment
|
||||
);
|
||||
|
||||
if (is_async) context.state.template.push(block_close);
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
/** @import { Node, Program } from 'estree' */
|
||||
/** @import { Context, ComponentContext } from '../types' */
|
||||
import * as b from '#compiler/builders';
|
||||
import { runes } from '../../../../state.js';
|
||||
import { transform_body } from '../../shared/transform-async.js';
|
||||
|
||||
/**
|
||||
* @param {Program} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function Program(node, context) {
|
||||
if (context.state.is_instance && runes) {
|
||||
// @ts-ignore wtf
|
||||
const c = /** @type {ComponentContext} */ (context);
|
||||
|
||||
return {
|
||||
...node,
|
||||
body: transform_body(
|
||||
node,
|
||||
c.state.analysis.awaited_statements,
|
||||
b.id('$$renderer.run'),
|
||||
(node) => /** @type {Node} */ (context.visit(node)),
|
||||
(statement) => c.state.hoisted.push(statement)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
context.next();
|
||||
}
|
||||
@ -0,0 +1,207 @@
|
||||
/** @import * as ESTree from 'estree' */
|
||||
/** @import { AwaitedStatement } from '../../types' */
|
||||
import * as b from '#compiler/builders';
|
||||
|
||||
// TODO find a way to DRY out this and the corresponding server visitor
|
||||
/**
|
||||
* @param {ESTree.Program} program
|
||||
* @param {Map<ESTree.Node, AwaitedStatement>} awaited_statements
|
||||
* @param {ESTree.Expression} runner
|
||||
* @param {(node: ESTree.Node) => ESTree.Node} transform
|
||||
* @param {(node: ESTree.Statement | ESTree.ModuleDeclaration) => void} hoist
|
||||
*/
|
||||
export function transform_body(program, awaited_statements, runner, transform, hoist) {
|
||||
/** @type {ESTree.Statement[]} */
|
||||
const out = [];
|
||||
|
||||
/** @type {AwaitedStatement[]} */
|
||||
const statements = [];
|
||||
|
||||
/** @type {AwaitedStatement[]} */
|
||||
const deriveds = [];
|
||||
|
||||
let awaited = false;
|
||||
|
||||
/**
|
||||
* @param {ESTree.Statement | ESTree.VariableDeclarator | ESTree.ClassDeclaration | ESTree.FunctionDeclaration} node
|
||||
*/
|
||||
const push = (node) => {
|
||||
const statement = awaited_statements.get(node);
|
||||
|
||||
awaited ||= !!statement?.has_await;
|
||||
|
||||
if (!awaited || !statement || node.type === 'FunctionDeclaration') {
|
||||
if (node.type === 'VariableDeclarator') {
|
||||
out.push(/** @type {ESTree.VariableDeclaration} */ (transform(b.var(node.id, node.init))));
|
||||
} else {
|
||||
out.push(/** @type {ESTree.Statement} */ (transform(node)));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO put deriveds into a separate array, and group them immediately
|
||||
// after their latest dependency. for now, to avoid having to figure
|
||||
// out the intricacies of dependency tracking, just let 'em waterfall
|
||||
// if (node.type === 'VariableDeclarator') {
|
||||
// const rune = get_rune(node.init, context.state.scope);
|
||||
|
||||
// if (rune === '$derived' || rune === '$derived.by') {
|
||||
// deriveds.push(statement);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
statements.push(statement);
|
||||
};
|
||||
|
||||
for (let node of program.body) {
|
||||
if (node.type === 'ImportDeclaration') {
|
||||
// TODO we can get rid of the visitor
|
||||
hoist(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.type === 'ExportDefaultDeclaration' || node.type === 'ExportAllDeclaration') {
|
||||
// this can't happen, but it's useful for TypeScript to understand that
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.type === 'ExportNamedDeclaration') {
|
||||
if (node.declaration) {
|
||||
// TODO ditto — no visitor needed
|
||||
node = node.declaration;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.type === 'VariableDeclaration') {
|
||||
if (
|
||||
!awaited &&
|
||||
node.declarations.every((declarator) => !awaited_statements.get(declarator)?.has_await)
|
||||
) {
|
||||
out.push(/** @type {ESTree.VariableDeclaration} */ (transform(node)));
|
||||
} else {
|
||||
for (const declarator of node.declarations) {
|
||||
push(declarator);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
push(node);
|
||||
}
|
||||
}
|
||||
|
||||
for (const derived of deriveds) {
|
||||
// find the earliest point we can insert this derived
|
||||
let index = -1;
|
||||
|
||||
for (const binding of derived.reads) {
|
||||
index = Math.max(
|
||||
index,
|
||||
statements.findIndex((s) => s.declarations.includes(binding))
|
||||
);
|
||||
}
|
||||
|
||||
if (index === -1 && !derived.has_await) {
|
||||
const node = /** @type {ESTree.VariableDeclarator} */ (derived.node);
|
||||
out.push(/** @type {ESTree.VariableDeclaration} */ (transform(b.var(node.id, node.init))));
|
||||
} else {
|
||||
// TODO combine deriveds with Promise.all where necessary
|
||||
statements.splice(index + 1, 0, derived);
|
||||
}
|
||||
}
|
||||
|
||||
var promises = b.id('$$promises'); // TODO if we use this technique for fragments, need to deconflict
|
||||
|
||||
if (statements.length > 0) {
|
||||
var declarations = statements.map((s) => s.declarations).flat();
|
||||
|
||||
if (declarations.length > 0) {
|
||||
out.push(
|
||||
b.declaration(
|
||||
'var',
|
||||
declarations.map((d) => b.declarator(d.node))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const thunks = statements.map((s) => {
|
||||
if (s.node.type === 'VariableDeclarator') {
|
||||
const visited = /** @type {ESTree.VariableDeclaration} */ (
|
||||
transform(b.var(s.node.id, s.node.init))
|
||||
);
|
||||
|
||||
if (visited.declarations.length === 1) {
|
||||
return b.thunk(
|
||||
b.assignment('=', s.node.id, visited.declarations[0].init ?? b.void0),
|
||||
s.has_await
|
||||
);
|
||||
}
|
||||
|
||||
// if we have multiple declarations, it indicates destructuring
|
||||
return b.thunk(
|
||||
b.block([
|
||||
b.var(visited.declarations[0].id, visited.declarations[0].init),
|
||||
...visited.declarations
|
||||
.slice(1)
|
||||
.map((d) => b.stmt(b.assignment('=', d.id, d.init ?? b.void0)))
|
||||
]),
|
||||
s.has_await
|
||||
);
|
||||
}
|
||||
|
||||
if (s.node.type === 'ClassDeclaration') {
|
||||
return b.thunk(
|
||||
b.assignment(
|
||||
'=',
|
||||
s.node.id,
|
||||
/** @type {ESTree.ClassExpression} */ ({ ...s.node, type: 'ClassExpression' })
|
||||
),
|
||||
s.has_await
|
||||
);
|
||||
}
|
||||
|
||||
if (s.node.type === 'FunctionDeclaration') {
|
||||
return b.thunk(
|
||||
b.assignment(
|
||||
'=',
|
||||
s.node.id,
|
||||
/** @type {ESTree.FunctionExpression} */ ({ ...s.node, type: 'FunctionExpression' })
|
||||
),
|
||||
s.has_await
|
||||
);
|
||||
}
|
||||
|
||||
if (s.node.type === 'ExpressionStatement') {
|
||||
const expression = /** @type {ESTree.Expression} */ (transform(s.node.expression));
|
||||
|
||||
return expression.type === 'AwaitExpression'
|
||||
? b.thunk(expression, true)
|
||||
: b.thunk(b.unary('void', expression), s.has_await);
|
||||
}
|
||||
|
||||
return b.thunk(b.block([/** @type {ESTree.Statement} */ (transform(s.node))]), s.has_await);
|
||||
});
|
||||
|
||||
out.push(b.var(promises, b.call(runner, b.array(thunks))));
|
||||
|
||||
for (let i = 0; i < statements.length; i += 1) {
|
||||
const s = statements[i];
|
||||
|
||||
var blocker = b.member(promises, b.literal(i), true);
|
||||
|
||||
for (const binding of s.declarations) {
|
||||
binding.blocker = blocker;
|
||||
}
|
||||
|
||||
for (const binding of s.writes) {
|
||||
// if a statement writes to a binding, any reads of that
|
||||
// binding must wait for the statement
|
||||
binding.blocker = blocker;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import { tick } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
skip_mode: ['server'],
|
||||
|
||||
ssrHtml: '<p>yep</p>',
|
||||
|
||||
async test({ assert, target, variant }) {
|
||||
if (variant === 'dom') {
|
||||
await tick();
|
||||
}
|
||||
|
||||
assert.htmlEqual(target.innerHTML, '<p>yep</p>');
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,10 @@
|
||||
<script>
|
||||
await 0;
|
||||
const condition = await true;
|
||||
</script>
|
||||
|
||||
{#if condition}
|
||||
<p>yep</p>
|
||||
{:else}
|
||||
<p>nope</p>
|
||||
{/if}
|
||||
Loading…
Reference in new issue