mirror of https://github.com/sveltejs/svelte
				
				
				
			Merge 5186155999 into da00abe116
	
		
	
				
					
				
			
						commit
						378e924744
					
				@ -1,16 +1,22 @@
 | 
				
			|||||||
/** @import { BlockStatement } from 'estree' */
 | 
					/** @import { BlockStatement } from 'estree' */
 | 
				
			||||||
/** @import { AST } from '#compiler' */
 | 
					/** @import { AST } from '#compiler' */
 | 
				
			||||||
/** @import { ComponentContext } from '../types.js' */
 | 
					/** @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 {AST.KeyBlock} node
 | 
				
			||||||
 * @param {ComponentContext} context
 | 
					 * @param {ComponentContext} context
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function KeyBlock(node, 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(
 | 
						context.state.template.push(
 | 
				
			||||||
		empty_comment,
 | 
							empty_comment,
 | 
				
			||||||
		/** @type {BlockStatement} */ (context.visit(node.fragment)),
 | 
							/** @type {BlockStatement} */ (context.visit(node.fragment)),
 | 
				
			||||||
		empty_comment
 | 
							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