From de48a77cb220bb8f5b4a86d3546ab229479b6923 Mon Sep 17 00:00:00 2001 From: "S. Elliott Johnson" Date: Fri, 5 Sep 2025 17:04:35 -0600 Subject: [PATCH] feat: hoisting --- .../compiler/phases/1-parse/state/element.js | 3 +- .../compiler/phases/1-parse/utils/create.js | 4 ++- .../src/compiler/phases/2-analyze/index.js | 15 +++++--- .../src/compiler/phases/2-analyze/types.d.ts | 8 +++++ .../phases/2-analyze/visitors/AwaitBlock.js | 23 ++++++++++-- .../2-analyze/visitors/AwaitExpression.js | 18 ++++++++-- .../phases/2-analyze/visitors/EachBlock.js | 12 +++++-- .../phases/2-analyze/visitors/IfBlock.js | 12 +++++-- .../phases/2-analyze/visitors/KeyBlock.js | 6 +++- .../phases/2-analyze/visitors/SnippetBlock.js | 7 +++- .../2-analyze/visitors/SvelteBoundary.js | 6 +++- .../3-transform/server/transform-server.js | 2 ++ .../server/visitors/AwaitExpression.js | 14 ++++++++ .../3-transform/server/visitors/Fragment.js | 20 +++++++++++ .../server/visitors/SnippetBlock.js | 13 +------ .../svelte/src/compiler/phases/types.d.ts | 4 ++- .../svelte/src/compiler/types/template.d.ts | 6 +++- .../_expected.html | 1 + .../async-each-fallback-hoisting/main.svelte | 5 +++ .../async-each-hoisting/_expected.html | 1 + .../samples/async-each-hoisting/main.svelte | 9 +++++ .../_expected.html | 1 + .../async-if-alternate-hoisting/main.svelte | 5 +++ .../samples/async-if-hoisting/_expected.html | 1 + .../samples/async-if-hoisting/main.svelte | 5 +++ .../async-each-fallback-hoisting/_config.js | 3 ++ .../_expected/client/index.svelte.js | 35 +++++++++++++++++++ .../_expected/server/index.svelte.js | 33 +++++++++++++++++ .../async-each-fallback-hoisting/index.svelte | 5 +++ .../samples/async-each-hoisting/_config.js | 3 ++ .../_expected/client/index.svelte.js | 24 +++++++++++++ .../_expected/server/index.svelte.js | 26 ++++++++++++++ .../samples/async-each-hoisting/index.svelte | 9 +++++ .../async-if-alternate-hoisting/_config.js | 3 ++ .../_expected/client/index.svelte.js | 30 ++++++++++++++++ .../_expected/server/index.svelte.js | 29 +++++++++++++++ .../async-if-alternate-hoisting/index.svelte | 5 +++ .../samples/async-if-hoisting/_config.js | 3 ++ .../_expected/client/index.svelte.js | 30 ++++++++++++++++ .../_expected/server/index.svelte.js | 29 +++++++++++++++ .../samples/async-if-hoisting/index.svelte | 5 +++ .../_expected/server/index.svelte.js | 4 +-- 42 files changed, 442 insertions(+), 35 deletions(-) create mode 100644 packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitExpression.js create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-each-fallback-hoisting/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-each-fallback-hoisting/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-each-hoisting/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-each-hoisting/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/main.svelte create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/_expected.html create mode 100644 packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/main.svelte create mode 100644 packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_config.js create mode 100644 packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/index.svelte create mode 100644 packages/svelte/tests/snapshot/samples/async-each-hoisting/_config.js create mode 100644 packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-each-hoisting/index.svelte create mode 100644 packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_config.js create mode 100644 packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/index.svelte create mode 100644 packages/svelte/tests/snapshot/samples/async-if-hoisting/_config.js create mode 100644 packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/client/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/server/index.svelte.js create mode 100644 packages/svelte/tests/snapshot/samples/async-if-hoisting/index.svelte diff --git a/packages/svelte/src/compiler/phases/1-parse/state/element.js b/packages/svelte/src/compiler/phases/1-parse/state/element.js index ed1b047d55..b0db9ce178 100644 --- a/packages/svelte/src/compiler/phases/1-parse/state/element.js +++ b/packages/svelte/src/compiler/phases/1-parse/state/element.js @@ -177,7 +177,8 @@ export default function element(parser) { mathml: false, scoped: false, has_spread: false, - path: [] + path: [], + synthetic_value_node: null } } : /** @type {AST.ElementLike} */ ({ diff --git a/packages/svelte/src/compiler/phases/1-parse/utils/create.js b/packages/svelte/src/compiler/phases/1-parse/utils/create.js index 2fba918f20..1a6f15aee7 100644 --- a/packages/svelte/src/compiler/phases/1-parse/utils/create.js +++ b/packages/svelte/src/compiler/phases/1-parse/utils/create.js @@ -11,7 +11,9 @@ export function create_fragment(transparent = false) { metadata: { transparent, dynamic: false, - has_await: false + has_await: false, + // name is added later, after we've done scope analysis + hoisted_promises: { name: '', promises: [] } } }; } diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js index a601928325..d3f759a10f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/index.js +++ b/packages/svelte/src/compiler/phases/2-analyze/index.js @@ -131,6 +131,9 @@ const visitors = { ignore_map.set(node, structuredClone(ignore_stack)); const scope = state.scopes.get(node); + if (node.type === 'Fragment') { + node.metadata.hoisted_promises.name = state.scope.generate('promises'); + } next(scope !== undefined && scope !== state.scope ? { ...state, scope } : state); if (ignores.length > 0) { @@ -307,7 +310,8 @@ export function analyze_module(source, options) { title: null, boundary: null, parent_element: null, - reactive_statement: null + reactive_statement: null, + async_hoist_boundary: null }, visitors ); @@ -535,7 +539,8 @@ export function analyze_component(root, source, options) { snippet_renderers: new Map(), snippets: new Set(), async_deriveds: new Set(), - has_blocking_await: false + has_blocking_await: false, + hoisted_promises: new Map() }; state.adjust({ @@ -704,7 +709,8 @@ export function analyze_component(root, source, options) { expression: null, state_fields: new Map(), function_depth: scope.function_depth, - reactive_statement: null + reactive_statement: null, + async_hoist_boundary: ast === template.ast ? ast : null }; walk(/** @type {AST.SvelteNode} */ (ast), state, visitors); @@ -774,7 +780,8 @@ export function analyze_component(root, source, options) { component_slots: new Set(), expression: null, state_fields: new Map(), - function_depth: scope.function_depth + function_depth: scope.function_depth, + async_hoist_boundary: ast === template.ast ? ast : null }; walk(/** @type {AST.SvelteNode} */ (ast), state, visitors); diff --git a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts index f020461d73..45523975dd 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/types.d.ts +++ b/packages/svelte/src/compiler/phases/2-analyze/types.d.ts @@ -12,6 +12,14 @@ export interface AnalysisState { snippet: AST.SnippetBlock | null; title: AST.TitleElement | null; boundary: AST.SvelteBoundary | null; + /** + * The "anchor" fragment for any hoisted promises. This is the root fragment when + * walking starts and until another boundary fragment is encountered, like a + * consequent or alternate of an `#if` or `#each` block. When this fragment is emitted + * during server transformation, the promise expressions will be hoisted out of the fragment + * and placed right above it in an array. + */ + async_hoist_boundary: AST.Fragment | null; /** * Tag name of the parent element. `null` if the parent is `svelte:element`, `#snippet`, a component or the root. * Parent doesn't necessarily mean direct path predecessor because there could be `#each`, `#if` etc in-between. diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js index 5aa04ba3b9..b26848f565 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitBlock.js @@ -41,8 +41,25 @@ export function AwaitBlock(node, context) { mark_subtree_dynamic(context.path); + // this one doesn't get the new state because it still hoists to the existing scope context.visit(node.expression, { ...context.state, expression: node.metadata.expression }); - if (node.pending) context.visit(node.pending); - if (node.then) context.visit(node.then); - if (node.catch) context.visit(node.catch); + + if (node.pending) { + context.visit(node.pending, { + ...context.state, + async_hoist_boundary: node.pending + }); + } + if (node.then) { + context.visit(node.then, { + ...context.state, + async_hoist_boundary: node.then + }); + } + if (node.catch) { + context.visit(node.catch, { + ...context.state, + async_hoist_boundary: node.catch + }); + } } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js index 588410af96..c9d06e3589 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/AwaitExpression.js @@ -1,6 +1,7 @@ /** @import { AwaitExpression } from 'estree' */ /** @import { Context } from '../types' */ import * as e from '../../../errors.js'; +import * as b from '#compiler/builders'; /** * @param {AwaitExpression} node @@ -19,8 +20,21 @@ export function AwaitExpression(node, context) { suspend = true; } - if (context.state.snippet) { - context.state.snippet.metadata.has_await = true; + // Only set has_await on the boundary when we're in a template expression context + // (not in event handlers or other non-template contexts) + if (context.state.async_hoist_boundary && context.state.expression) { + context.state.async_hoist_boundary.metadata.is_async = true; + const len = context.state.async_hoist_boundary.metadata.hoisted_promises.promises.push( + node.argument + ); + context.state.analysis.hoisted_promises.set( + node.argument, + b.member( + b.id(context.state.async_hoist_boundary.metadata.hoisted_promises.name), + b.literal(len - 1), + true + ) + ); } if (context.state.title) { diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/EachBlock.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/EachBlock.js index e6a83921b1..0a72b72a65 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/EachBlock.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/EachBlock.js @@ -35,9 +35,17 @@ export function EachBlock(node, context) { scope: /** @type {Scope} */ (context.state.scope.parent) }); - context.visit(node.body); + context.visit(node.body, { + ...context.state, + async_hoist_boundary: node.body + }); if (node.key) context.visit(node.key); - if (node.fallback) context.visit(node.fallback); + if (node.fallback) { + context.visit(node.fallback, { + ...context.state, + async_hoist_boundary: node.fallback + }); + } if (!context.state.analysis.runes) { let mutated = diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js index dcdae3587f..edaf3d398e 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/IfBlock.js @@ -22,6 +22,14 @@ export function IfBlock(node, context) { expression: node.metadata.expression }); - context.visit(node.consequent); - if (node.alternate) context.visit(node.alternate); + context.visit(node.consequent, { + ...context.state, + async_hoist_boundary: node.consequent + }); + if (node.alternate) { + context.visit(node.alternate, { + ...context.state, + async_hoist_boundary: node.alternate + }); + } } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js index 09e604ea66..dbc2eb6611 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/KeyBlock.js @@ -17,5 +17,9 @@ export function KeyBlock(node, context) { mark_subtree_dynamic(context.path); context.visit(node.expression, { ...context.state, expression: node.metadata.expression }); - context.visit(node.fragment); + + context.visit(node.fragment, { + ...context.state, + async_hoist_boundary: node.fragment + }); } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/SnippetBlock.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/SnippetBlock.js index a30c384904..86951fd2a6 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/SnippetBlock.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/SnippetBlock.js @@ -23,7 +23,12 @@ export function SnippetBlock(node, context) { } } - context.next({ ...context.state, parent_element: null, snippet: node }); + context.next({ + ...context.state, + parent_element: null, + snippet: node, + async_hoist_boundary: node.body + }); const can_hoist = context.path.length === 1 && diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBoundary.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBoundary.js index d195e01f86..786b08bb88 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBoundary.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBoundary.js @@ -34,5 +34,9 @@ export function SvelteBoundary(node, context) { ) ) ?? null; - context.next({ ...context.state, boundary: node }); + context.next({ + ...context.state, + boundary: node, + async_hoist_boundary: node.fragment + }); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js index 4f164a8e1b..0214f11a8f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/transform-server.js @@ -10,6 +10,7 @@ import { dev, filename } from '../../../state.js'; import { render_stylesheet } from '../css/index.js'; import { AssignmentExpression } from './visitors/AssignmentExpression.js'; import { AwaitBlock } from './visitors/AwaitBlock.js'; +import { AwaitExpression } from './visitors/AwaitExpression.js'; import { CallExpression } from './visitors/CallExpression.js'; import { ClassBody } from './visitors/ClassBody.js'; import { Component } from './visitors/Component.js'; @@ -57,6 +58,7 @@ const global_visitors = { /** @type {ComponentVisitors} */ const template_visitors = { + AwaitExpression, AwaitBlock, Component, ConstTag, diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitExpression.js new file mode 100644 index 0000000000..70070509d7 --- /dev/null +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/AwaitExpression.js @@ -0,0 +1,14 @@ +/** @import { AwaitExpression } from 'estree' */ +/** @import { ComponentContext } from '../types.js' */ + +/** + * This is only registered for components, currently. + * @param {AwaitExpression} node + * @param {ComponentContext} context + */ +export function AwaitExpression(node, context) { + const hoisted = context.state.analysis.hoisted_promises.get(node.argument); + if (hoisted) { + node.argument = hoisted; + } +} diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/Fragment.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Fragment.js index a1d25980c4..1069459e3e 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/Fragment.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Fragment.js @@ -42,5 +42,25 @@ export function Fragment(node, context) { process_children(trimmed, { ...context, state }); + if (node.metadata.hoisted_promises.promises.length > 0) { + return b.block([ + b.const( + node.metadata.hoisted_promises.name, + b.array(node.metadata.hoisted_promises.promises) + ), + ...state.init, + b.stmt( + b.call( + '$$payload.child', + b.arrow( + [b.id('$$payload')], + b.block(build_template(state.template)), + node.metadata.is_async + ) + ) + ) + ]); + } + return b.block([...state.init, ...build_template(state.template)]); } diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js index 9aeb4674d7..238485e665 100644 --- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js +++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/SnippetBlock.js @@ -12,18 +12,7 @@ export function SnippetBlock(node, context) { let fn = b.function_declaration( node.expression, [b.id('$$payload'), ...node.parameters], - b.block([ - b.stmt( - b.call( - '$$payload.child', - b.arrow( - [b.id('$$payload')], - /** @type {BlockStatement} */ (context.visit(node.body)), - node.metadata.has_await - ) - ) - ) - ]) + /** @type {BlockStatement} */ (context.visit(node.body)) ); // @ts-expect-error - TODO remove this hack once $$render_inner for legacy bindings is gone diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index a73472e44f..5653a5752c 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -2,9 +2,10 @@ import type { AST, Binding, StateField } from '#compiler'; import type { CallExpression, ClassBody, + Expression, Identifier, LabeledStatement, - Node, + MemberExpression, Program } from 'estree'; import type { Scope, ScopeRoot } from './scope.js'; @@ -108,6 +109,7 @@ export interface ComponentAnalysis extends Analysis { snippets: Set; /** Whether the component uses `await` in a context that would require an `await` on the server. */ has_blocking_await: boolean; + hoisted_promises: Map; } declare module 'estree' { diff --git a/packages/svelte/src/compiler/types/template.d.ts b/packages/svelte/src/compiler/types/template.d.ts index 7845d3eb4e..4efeeb3bfd 100644 --- a/packages/svelte/src/compiler/types/template.d.ts +++ b/packages/svelte/src/compiler/types/template.d.ts @@ -57,6 +57,11 @@ export namespace AST { */ dynamic: boolean; has_await: boolean; + /** + * True when this fragment has a top-level `await` expression. + */ + is_async: boolean; + hoisted_promises: { name: string; promises: Expression[] }; }; } @@ -523,7 +528,6 @@ export namespace AST { /** @internal */ metadata: { can_hoist: boolean; - has_await: boolean; /** The set of components/render tags that could render this snippet, * used for CSS pruning */ sites: Set; diff --git a/packages/svelte/tests/server-side-rendering/samples/async-each-fallback-hoisting/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-each-fallback-hoisting/_expected.html new file mode 100644 index 0000000000..bf0d87ab1b --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-each-fallback-hoisting/_expected.html @@ -0,0 +1 @@ +4 \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/async-each-fallback-hoisting/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-each-fallback-hoisting/main.svelte new file mode 100644 index 0000000000..e580345a2e --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-each-fallback-hoisting/main.svelte @@ -0,0 +1,5 @@ +{#each await Promise.resolve([]) as item} + {await Promise.reject('This should never be reached')} +{:else} + {await Promise.resolve(4)} +{/each} diff --git a/packages/svelte/tests/server-side-rendering/samples/async-each-hoisting/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-each-hoisting/_expected.html new file mode 100644 index 0000000000..d800886d9c --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-each-hoisting/_expected.html @@ -0,0 +1 @@ +123 \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/async-each-hoisting/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-each-hoisting/main.svelte new file mode 100644 index 0000000000..4b6bf7eadc --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-each-hoisting/main.svelte @@ -0,0 +1,9 @@ + + +{#each await Promise.resolve([first, second, third]) as item} + {await item} +{/each} diff --git a/packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/_expected.html new file mode 100644 index 0000000000..c3b0e19566 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/_expected.html @@ -0,0 +1 @@ +yes yes yes \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/main.svelte new file mode 100644 index 0000000000..a97f32f034 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-if-alternate-hoisting/main.svelte @@ -0,0 +1,5 @@ +{#if await Promise.resolve(false)} + {await Promise.reject('no no no')} +{:else} + {await Promise.resolve('yes yes yes')} +{/if} diff --git a/packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/_expected.html b/packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/_expected.html new file mode 100644 index 0000000000..c3b0e19566 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/_expected.html @@ -0,0 +1 @@ +yes yes yes \ No newline at end of file diff --git a/packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/main.svelte b/packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/main.svelte new file mode 100644 index 0000000000..4ca3462771 --- /dev/null +++ b/packages/svelte/tests/server-side-rendering/samples/async-if-hoisting/main.svelte @@ -0,0 +1,5 @@ +{#if await Promise.resolve(true)} + {await Promise.resolve('yes yes yes')} +{:else} + {await Promise.reject('no no no')} +{/if} diff --git a/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_config.js b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_config.js new file mode 100644 index 0000000000..2e30bbeb16 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({ compileOptions: { experimental: { async: true } } }); diff --git a/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/client/index.svelte.js new file mode 100644 index 0000000000..c4316aad79 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/client/index.svelte.js @@ -0,0 +1,35 @@ +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/async'; +import * as $ from 'svelte/internal/client'; + +export default function Async_each_fallback_hoisting($$anchor) { + var fragment = $.comment(); + var node = $.first_child(fragment); + + $.async(node, [() => Promise.resolve([])], (node, $$collection) => { + $.each( + node, + 16, + () => $.get($$collection), + $.index, + ($$anchor, item) => { + $.next(); + + var text = $.text(); + + $.template_effect(($0) => $.set_text(text, $0), undefined, [() => Promise.reject('This should never be reached')]); + $.append($$anchor, text); + }, + ($$anchor) => { + $.next(); + + var text_1 = $.text(); + + $.template_effect(($0) => $.set_text(text_1, $0), undefined, [() => Promise.resolve(4)]); + $.append($$anchor, text_1); + } + ); + }); + + $.append($$anchor, fragment); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/server/index.svelte.js new file mode 100644 index 0000000000..91b8b7e2d1 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/_expected/server/index.svelte.js @@ -0,0 +1,33 @@ +import * as $ from 'svelte/internal/server'; + +export default function Async_each_fallback_hoisting($$payload) { + $$payload.child(async ($$payload) => { + const promises = [Promise.resolve([])]; + const each_array = $.ensure_array_like(await promises[0]); + + $$payload.child(async ($$payload) => { + if (each_array.length !== 0) { + $$payload.push(''); + + for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) { + let item = each_array[$$index]; + const promises_1 = [Promise.reject('This should never be reached')]; + + $$payload.child(async ($$payload) => { + $$payload.push(`${$.escape(await promises_1[0])}`); + }); + } + } else { + $$payload.push(''); + + const promises_2 = [Promise.resolve(4)]; + + $$payload.child(async ($$payload) => { + $$payload.push(`${$.escape(await promises_2[0])}`); + }); + } + + $$payload.push(``); + }); + }); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/index.svelte b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/index.svelte new file mode 100644 index 0000000000..e580345a2e --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-each-fallback-hoisting/index.svelte @@ -0,0 +1,5 @@ +{#each await Promise.resolve([]) as item} + {await Promise.reject('This should never be reached')} +{:else} + {await Promise.resolve(4)} +{/each} diff --git a/packages/svelte/tests/snapshot/samples/async-each-hoisting/_config.js b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_config.js new file mode 100644 index 0000000000..2e30bbeb16 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({ compileOptions: { experimental: { async: true } } }); diff --git a/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/client/index.svelte.js new file mode 100644 index 0000000000..aa848cc704 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/client/index.svelte.js @@ -0,0 +1,24 @@ +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/async'; +import * as $ from 'svelte/internal/client'; + +export default function Async_each_hoisting($$anchor) { + const first = Promise.resolve(1); + const second = Promise.resolve(2); + const third = Promise.resolve(3); + var fragment = $.comment(); + var node = $.first_child(fragment); + + $.async(node, [() => Promise.resolve([first, second, third])], (node, $$collection) => { + $.each(node, 17, () => $.get($$collection), $.index, ($$anchor, item) => { + $.next(); + + var text = $.text(); + + $.template_effect(($0) => $.set_text(text, $0), undefined, [() => $.get(item)]); + $.append($$anchor, text); + }); + }); + + $.append($$anchor, fragment); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/server/index.svelte.js new file mode 100644 index 0000000000..99dfc7c8ef --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-each-hoisting/_expected/server/index.svelte.js @@ -0,0 +1,26 @@ +import * as $ from 'svelte/internal/server'; + +export default function Async_each_hoisting($$payload) { + $$payload.child(async ($$payload) => { + const first = Promise.resolve(1); + const second = Promise.resolve(2); + const third = Promise.resolve(3); + const promises = [Promise.resolve([first, second, third])]; + const each_array = $.ensure_array_like(await promises[0]); + + $$payload.child(async ($$payload) => { + $$payload.push(``); + + for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) { + let item = each_array[$$index]; + const promises_1 = [item]; + + $$payload.child(async ($$payload) => { + $$payload.push(`${$.escape(await promises_1[0])}`); + }); + } + + $$payload.push(``); + }); + }); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-each-hoisting/index.svelte b/packages/svelte/tests/snapshot/samples/async-each-hoisting/index.svelte new file mode 100644 index 0000000000..4b6bf7eadc --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-each-hoisting/index.svelte @@ -0,0 +1,9 @@ + + +{#each await Promise.resolve([first, second, third]) as item} + {await item} +{/each} diff --git a/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_config.js b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_config.js new file mode 100644 index 0000000000..2e30bbeb16 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({ compileOptions: { experimental: { async: true } } }); diff --git a/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/client/index.svelte.js new file mode 100644 index 0000000000..684e67e737 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/client/index.svelte.js @@ -0,0 +1,30 @@ +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/async'; +import * as $ from 'svelte/internal/client'; + +export default function Async_if_alternate_hoisting($$anchor) { + var fragment = $.comment(); + var node = $.first_child(fragment); + + $.async(node, [() => Promise.resolve(false)], (node, $$condition) => { + var consequent = ($$anchor) => { + var text = $.text(); + + $.template_effect(($0) => $.set_text(text, $0), undefined, [() => Promise.reject('no no no')]); + $.append($$anchor, text); + }; + + var alternate = ($$anchor) => { + var text_1 = $.text(); + + $.template_effect(($0) => $.set_text(text_1, $0), undefined, [() => Promise.resolve('yes yes yes')]); + $.append($$anchor, text_1); + }; + + $.if(node, ($$render) => { + if ($.get($$condition)) $$render(consequent); else $$render(alternate, false); + }); + }); + + $.append($$anchor, fragment); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/server/index.svelte.js new file mode 100644 index 0000000000..ee5cc89ac5 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/_expected/server/index.svelte.js @@ -0,0 +1,29 @@ +import * as $ from 'svelte/internal/server'; + +export default function Async_if_alternate_hoisting($$payload) { + $$payload.child(async ($$payload) => { + const promises = [Promise.resolve(false)]; + + $$payload.child(async ($$payload) => { + if (await promises[0]) { + $$payload.push(''); + + const promises_1 = [Promise.reject('no no no')]; + + $$payload.child(async ($$payload) => { + $$payload.push(`${$.escape(await promises_1[0])}`); + }); + } else { + $$payload.push(''); + + const promises_2 = [Promise.resolve('yes yes yes')]; + + $$payload.child(async ($$payload) => { + $$payload.push(`${$.escape(await promises_2[0])}`); + }); + } + + $$payload.push(``); + }); + }); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/index.svelte b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/index.svelte new file mode 100644 index 0000000000..a97f32f034 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-if-alternate-hoisting/index.svelte @@ -0,0 +1,5 @@ +{#if await Promise.resolve(false)} + {await Promise.reject('no no no')} +{:else} + {await Promise.resolve('yes yes yes')} +{/if} diff --git a/packages/svelte/tests/snapshot/samples/async-if-hoisting/_config.js b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_config.js new file mode 100644 index 0000000000..2e30bbeb16 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({ compileOptions: { experimental: { async: true } } }); diff --git a/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/client/index.svelte.js new file mode 100644 index 0000000000..dee33e852d --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/client/index.svelte.js @@ -0,0 +1,30 @@ +import 'svelte/internal/disclose-version'; +import 'svelte/internal/flags/async'; +import * as $ from 'svelte/internal/client'; + +export default function Async_if_hoisting($$anchor) { + var fragment = $.comment(); + var node = $.first_child(fragment); + + $.async(node, [() => Promise.resolve(true)], (node, $$condition) => { + var consequent = ($$anchor) => { + var text = $.text(); + + $.template_effect(($0) => $.set_text(text, $0), undefined, [() => Promise.resolve('yes yes yes')]); + $.append($$anchor, text); + }; + + var alternate = ($$anchor) => { + var text_1 = $.text(); + + $.template_effect(($0) => $.set_text(text_1, $0), undefined, [() => Promise.reject('no no no')]); + $.append($$anchor, text_1); + }; + + $.if(node, ($$render) => { + if ($.get($$condition)) $$render(consequent); else $$render(alternate, false); + }); + }); + + $.append($$anchor, fragment); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/server/index.svelte.js new file mode 100644 index 0000000000..04d076a59b --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-if-hoisting/_expected/server/index.svelte.js @@ -0,0 +1,29 @@ +import * as $ from 'svelte/internal/server'; + +export default function Async_if_hoisting($$payload) { + $$payload.child(async ($$payload) => { + const promises = [Promise.resolve(true)]; + + $$payload.child(async ($$payload) => { + if (await promises[0]) { + $$payload.push(''); + + const promises_1 = [Promise.resolve('yes yes yes')]; + + $$payload.child(async ($$payload) => { + $$payload.push(`${$.escape(await promises_1[0])}`); + }); + } else { + $$payload.push(''); + + const promises_2 = [Promise.reject('no no no')]; + + $$payload.child(async ($$payload) => { + $$payload.push(`${$.escape(await promises_2[0])}`); + }); + } + + $$payload.push(``); + }); + }); +} \ No newline at end of file diff --git a/packages/svelte/tests/snapshot/samples/async-if-hoisting/index.svelte b/packages/svelte/tests/snapshot/samples/async-if-hoisting/index.svelte new file mode 100644 index 0000000000..4ca3462771 --- /dev/null +++ b/packages/svelte/tests/snapshot/samples/async-if-hoisting/index.svelte @@ -0,0 +1,5 @@ +{#if await Promise.resolve(true)} + {await Promise.resolve('yes yes yes')} +{:else} + {await Promise.reject('no no no')} +{/if} diff --git a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js index 7d3d758ac3..e2be5dc5b7 100644 --- a/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/bind-component-snippet/_expected/server/index.svelte.js @@ -2,9 +2,7 @@ import * as $ from 'svelte/internal/server'; import TextInput from './Child.svelte'; function snippet($$payload) { - $$payload.child(($$payload) => { - $$payload.push(`Something`); - }); + $$payload.push(`Something`); } export default function Bind_component_snippet($$payload) {