diff --git a/packages/svelte/src/compiler/phases/2-analyze/index.js b/packages/svelte/src/compiler/phases/2-analyze/index.js
index 7267203a1c..ed71b898ed 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/index.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/index.js
@@ -770,7 +770,7 @@ export function analyze_component(root, source, options) {
// for now, assume everything touched by the callee ends up mutating the object
// TODO optimise this better
- // special case — no need to peek inside effects
+ // special case — no need to peek inside effects as they only run once async work has completed
const rune = get_rune(node, context.state.scope);
if (rune === '$effect') return;
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/Program.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Program.js
index b40491db41..6b5227d598 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/Program.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/Program.js
@@ -1,5 +1,5 @@
/** @import { Node, Program } from 'estree' */
-/** @import { Context, ComponentContext } from '../types' */
+/** @import { Context, ComponentServerTransformState } from '../types' */
import * as b from '#compiler/builders';
import { transform_body } from '../../shared/transform-async.js';
@@ -9,13 +9,12 @@ import { transform_body } from '../../shared/transform-async.js';
*/
export function Program(node, context) {
if (context.state.is_instance) {
- // @ts-ignore wtf
- const c = /** @type {ComponentContext} */ (context);
+ const state = /** @type {ComponentServerTransformState} */ (context.state);
return {
...node,
body: transform_body(
- c.state.analysis.instance_body,
+ state.analysis.instance_body,
b.id('$$renderer.run'),
(node) => /** @type {Node} */ (context.visit(node))
)
diff --git a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js
index b31ca94c6e..6bc0ab3233 100644
--- a/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/utils.js
@@ -1,5 +1,5 @@
/** @import { Expression, Identifier, Node, Statement, BlockStatement, ArrayExpression } from 'estree' */
-/** @import { AST, Binding } from '#compiler' */
+/** @import { AST } from '#compiler' */
/** @import { ComponentContext, ServerTransformState } from '../../types.js' */
import { escape_html } from '../../../../../../escaping.js';
@@ -276,12 +276,17 @@ export function create_child_block(body, async) {
* @param {BlockStatement | Expression} body
* @param {ArrayExpression} blockers
* @param {boolean} has_await
- * @param {boolean} markers
+ * @param {boolean} needs_hydration_markers
*/
-export function create_async_block(body, blockers = b.array([]), has_await = true, markers = true) {
+export function create_async_block(
+ body,
+ blockers = b.array([]),
+ has_await = true,
+ needs_hydration_markers = true
+) {
return b.stmt(
b.call(
- markers ? '$$renderer.async_block' : '$$renderer.async',
+ needs_hydration_markers ? '$$renderer.async_block' : '$$renderer.async',
blockers,
b.arrow([b.id('$$renderer')], body, has_await)
)
@@ -291,17 +296,22 @@ export function create_async_block(body, blockers = b.array([]), has_await = tru
/**
* @param {Expression} expression
* @param {ExpressionMetadata} metadata
- * @param {boolean} markers
+ * @param {boolean} needs_hydration_markers
* @returns {Expression | Statement}
*/
-export function create_push(expression, metadata, markers = false) {
+export function create_push(expression, metadata, needs_hydration_markers = false) {
if (metadata.is_async()) {
let statement = b.stmt(b.call('$$renderer.push', b.thunk(expression, metadata.has_await)));
const blockers = metadata.blockers();
if (blockers.elements.length > 0) {
- statement = create_async_block(b.block([statement]), blockers, false, markers);
+ statement = create_async_block(
+ b.block([statement]),
+ blockers,
+ false,
+ needs_hydration_markers
+ );
}
return statement;
@@ -321,6 +331,12 @@ export function call_component_renderer(body, component_fn_id) {
);
}
+/**
+ * A utility for optimising promises in templates. Without it code like
+ * `` would be transformed
+ * into two blocking promises, with it it's using `Promise.all` to await them.
+ * It also keeps track of blocking promises, i.e. those that need to be resolved before continuing.
+ */
export class PromiseOptimiser {
/** @type {Expression[]} */
expressions = [];
diff --git a/packages/svelte/src/compiler/phases/3-transform/shared/transform-async.js b/packages/svelte/src/compiler/phases/3-transform/shared/transform-async.js
index 817c3ef1eb..444b8d7d94 100644
--- a/packages/svelte/src/compiler/phases/3-transform/shared/transform-async.js
+++ b/packages/svelte/src/compiler/phases/3-transform/shared/transform-async.js
@@ -3,16 +3,39 @@
import * as b from '#compiler/builders';
/**
+ * Transforms the body of the instance script in such a way that await expressions are made non-blocking as much as possible.
+ *
+ * Example Transformation:
+ * ```js
+ * let x = 1;
+ * let data = await fetch('/api');
+ * let y = data.value;
+ * ```
+ * becomes:
+ * ```js
+ * let x = 1;
+ * var data, y;
+ * var $$promises = $.run([
+ * () => data = await fetch('/api'),
+ * () => y = data.value
+ * ]);
+ * ```
+ * where `$$promises` is an array of promises that are resolved in the order they are declared,
+ * and which expressions in the template can await on like `await $$promises[0]` which means they
+ * wouldn't have to wait for e.g. `$$promises[1]` to resolve.
+ *
* @param {ComponentAnalysis['instance_body']} instance_body
* @param {ESTree.Expression} runner
* @param {(node: ESTree.Node) => ESTree.Node} transform
* @returns {Array}
*/
export function transform_body(instance_body, runner, transform) {
+ // Any sync statements before the first await expression
const statements = instance_body.sync.map(
(node) => /** @type {ESTree.Statement | ESTree.VariableDeclaration} */ (transform(node))
);
+ // Declarations for the await expressions (they will asign to them; need to be hoisted to be available in whole instance scope)
if (instance_body.declarations.length > 0) {
statements.push(
b.declaration(
@@ -22,6 +45,7 @@ export function transform_body(instance_body, runner, transform) {
);
}
+ // Thunks for the await expressions
if (instance_body.async.length > 0) {
const thunks = instance_body.async.map((s) => {
if (s.node.type === 'VariableDeclarator') {
diff --git a/packages/svelte/src/internal/client/dom/blocks/if.js b/packages/svelte/src/internal/client/dom/blocks/if.js
index a80532a68f..7fa5ca464d 100644
--- a/packages/svelte/src/internal/client/dom/blocks/if.js
+++ b/packages/svelte/src/internal/client/dom/blocks/if.js
@@ -6,8 +6,7 @@ import {
read_hydration_instruction,
skip_nodes,
set_hydrate_node,
- set_hydrating,
- hydrate_node
+ set_hydrating
} from '../hydration.js';
import { block } from '../../reactivity/effects.js';
import { HYDRATION_START_ELSE } from '../../../../constants.js';
diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js
index 97bad28be3..d970e7c885 100644
--- a/packages/svelte/src/internal/client/dom/elements/attributes.js
+++ b/packages/svelte/src/internal/client/dom/elements/attributes.js
@@ -480,10 +480,10 @@ function set_attributes(
/**
* @param {Element & ElementCSSInlineStyle} element
- * @param {Array>} blockers
* @param {(...expressions: any) => Record} fn
* @param {Array<() => any>} sync
* @param {Array<() => Promise>} async
+ * @param {Array>} blockers
* @param {string} [css_hash]
* @param {boolean} [should_remove_defaults]
* @param {boolean} [skip_warning]
diff --git a/packages/svelte/src/internal/client/reactivity/async.js b/packages/svelte/src/internal/client/reactivity/async.js
index e3459a2d06..0262270f75 100644
--- a/packages/svelte/src/internal/client/reactivity/async.js
+++ b/packages/svelte/src/internal/client/reactivity/async.js
@@ -25,14 +25,7 @@ import {
set_from_async_derived
} from './deriveds.js';
import { aborted } from './effects.js';
-import {
- hydrate_next,
- hydrate_node,
- hydrating,
- set_hydrate_node,
- set_hydrating,
- skip_nodes
-} from '../dom/hydration.js';
+import { hydrate_next, hydrating, set_hydrate_node, skip_nodes } from '../dom/hydration.js';
/**
* @param {Array>} blockers
diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js
index 2cf7c80b25..b480d4155a 100644
--- a/packages/svelte/src/internal/client/reactivity/sources.js
+++ b/packages/svelte/src/internal/client/reactivity/sources.js
@@ -34,7 +34,7 @@ import * as e from '../errors.js';
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
import { get_stack, tag_proxy } from '../dev/tracing.js';
import { component_context, is_runes } from '../context.js';
-import { Batch, current_batch, eager_block_effects, schedule_effect } from './batch.js';
+import { Batch, eager_block_effects, schedule_effect } from './batch.js';
import { proxy } from '../proxy.js';
import { execute_derived } from './deriveds.js';