Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/red-spies-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: run deferred effects in async components
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,11 @@ export function client_component(analysis, options) {
component_returned_object.push(b.spread(b.call(b.id('$.legacy_api'))));
}

const push_args = [b.id('$$props'), b.literal(analysis.runes)];
const push_args = [
b.id('$$props'),
b.literal(analysis.runes),
b.literal(analysis.instance.has_await)
];
if (dev) push_args.push(b.id(analysis.name));

let component_block = b.block([
Expand All @@ -368,7 +372,8 @@ export function client_component(analysis, options) {
dev ||
analysis.needs_context ||
analysis.reactive_statements.size > 0 ||
component_returned_object.length > 0;
component_returned_object.length > 0 ||
analysis.instance.has_await;

if (analysis.instance.has_await) {
if (should_inject_context && component_returned_object.length > 0) {
Expand All @@ -385,7 +390,7 @@ export function client_component(analysis, options) {
.../** @type {ESTree.Statement[]} */ (template.body)
]);

component_block.body.push(b.stmt(b.call(`$.async_body`, b.arrow([], body, true))));
component_block.body.push(b.stmt(b.call(`$.async_body`, b.arrow([], body, true), b.true)));
} else {
component_block.body.push(
...state.instance_level_snippets,
Expand Down Expand Up @@ -450,15 +455,15 @@ export function client_component(analysis, options) {
// we want the cleanup function for the stores to run as the very last thing
// so that it can effectively clean up the store subscription even after the user effects runs
if (should_inject_context) {
component_block.body.unshift(b.stmt(b.call('$.push', ...push_args)));
component_block.body.unshift(b.var('$$component', b.call('$.push', ...push_args)));

let to_push;

if (component_returned_object.length > 0) {
let pop_call = b.call('$.pop', b.id('$$exports'));
let pop_call = b.call('$.pop', b.id('$$component'), b.id('$$exports'));
to_push = needs_store_cleanup ? b.var('$$pop', pop_call) : b.return(pop_call);
} else {
to_push = b.stmt(b.call('$.pop'));
to_push = b.stmt(b.call('$.pop', b.id('$$component')));
}

component_block.body.push(to_push);
Expand Down
31 changes: 22 additions & 9 deletions packages/svelte/src/internal/client/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,15 @@
/**
* @param {Record<string, unknown>} props
* @param {any} runes
* @param {boolean} [has_async_body]
* @param {Function} [fn]
* @returns {void}
*/
export function push(props, runes = false, fn) {
export function push(props, runes = false, has_async_body = false, fn) {
component_context = {
p: component_context,
c: null,
e: null,
a: has_async_body,
s: props,
x: null,
l: legacy_mode_flag && !runes ? { s: null, u: null, $: [] } : null
Expand All @@ -157,30 +158,42 @@
component_context.function = fn;
dev_current_component_function = fn;
}

return component_context;
}

/**
* @template {Record<string, any>} T
* @param {ComponentContext} context
* @param {T} [component]
* @returns {T}
*/
export function pop(component) {
var context = /** @type {ComponentContext} */ (component_context);
var effects = context.e;

export function pop(context, component) {
var ctx = /** @type {ComponentContext} */ (component_context);
if (context !== ctx) {
console.log('h');

Check failure on line 174 in packages/svelte/src/internal/client/context.js

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
return component ?? /** @type {T} */ ({});
}
if (ctx.a) {
console.log('suspended');

Check failure on line 178 in packages/svelte/src/internal/client/context.js

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
return component ?? /** @type {T} */ ({});
}
var effects = ctx.e;
console.log('effects', effects);

Check failure on line 182 in packages/svelte/src/internal/client/context.js

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
console.log('context', context);

Check failure on line 183 in packages/svelte/src/internal/client/context.js

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
if (effects !== null) {
context.e = null;
ctx.e = null;

for (var fn of effects) {
create_user_effect(fn);
}
}

if (component !== undefined) {
context.x = component;
ctx.x = component;
}

component_context = context.p;
component_context = ctx.p;

if (DEV) {
dev_current_component_function = component_context?.function ?? null;
Expand Down
27 changes: 21 additions & 6 deletions packages/svelte/src/internal/client/reactivity/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { DESTROYED } from '#client/constants';
import { DEV } from 'esm-env';
import { component_context, is_runes, set_component_context } from '../context.js';
import { component_context, is_runes, pop, set_component_context } from '../context.js';
import { get_pending_boundary } from '../dom/blocks/boundary.js';
import { invoke_error_boundary } from '../error-handling.js';
import {
Expand All @@ -19,7 +19,7 @@
derived_safe_equal,
set_from_async_derived
} from './deriveds.js';
import { aborted } from './effects.js';
import { aborted, create_user_effect } from './effects.js';

/**
*
Expand Down Expand Up @@ -167,19 +167,26 @@
}
}

export function unset_context() {
/**
* @param {boolean} [is_component_body]
*/
export function unset_context(is_component_body = false) {
set_active_effect(null);
set_active_reaction(null);
set_component_context(null);
if (!is_component_body) set_component_context(null);
if (DEV) set_from_async_derived(null);
}

export let running_deferred_effects = false;

/**
* @param {() => Promise<void>} fn
* @param {boolean} [is_component]
*/
export async function async_body(fn) {
export async function async_body(fn, is_component = false) {
var unsuspend = suspend();
var active = /** @type {Effect} */ (active_effect);
var ctx = is_component ? component_context : null;

try {
await fn();
Expand All @@ -188,6 +195,14 @@
invoke_error_boundary(error, active);
}
} finally {
unsuspend();
unsuspend(is_component);
console.log(ctx, component_context, ctx === component_context);

Check failure on line 199 in packages/svelte/src/internal/client/reactivity/async.js

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
if (ctx !== null) {
console.log('hi');

Check failure on line 201 in packages/svelte/src/internal/client/reactivity/async.js

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
ctx.a = false;
running_deferred_effects = true;
pop(ctx);
running_deferred_effects = false;
}
}
}
4 changes: 2 additions & 2 deletions packages/svelte/src/internal/client/reactivity/batch.js
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ export function suspend() {
boundary.update_pending_count(1);
if (!pending) batch.increment();

return function unsuspend() {
return function unsuspend(is_component_body = false) {
boundary.update_pending_count(-1);

if (!pending) {
Expand All @@ -685,7 +685,7 @@ export function suspend() {
batch.deactivate();
}

unset_context();
unset_context(is_component_body);
};
}

Expand Down
11 changes: 8 additions & 3 deletions packages/svelte/src/internal/client/reactivity/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
import { get_next_sibling } from '../dom/operations.js';
import { component_context, dev_current_component_function, dev_stack } from '../context.js';
import { Batch, schedule_effect } from './batch.js';
import { flatten } from './async.js';
import { flatten, running_deferred_effects } from './async.js';
import { without_reactive_context } from '../dom/elements/bindings/shared.js';

/**
Expand Down Expand Up @@ -207,9 +207,14 @@
// Non-nested `$effect(...)` in a component should be deferred
// until the component is mounted
var flags = /** @type {Effect} */ (active_effect).f;
var defer = !active_reaction && (flags & BRANCH_EFFECT) !== 0 && (flags & EFFECT_RAN) === 0;

var defer =
!active_reaction &&
(flags & BRANCH_EFFECT) !== 0 &&
(flags & EFFECT_RAN) === 0 &&
!running_deferred_effects;
console.log({ defer, flags, component_context });

Check failure on line 215 in packages/svelte/src/internal/client/reactivity/effects.js

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
if (defer) {
console.log('deferring');

Check failure on line 217 in packages/svelte/src/internal/client/reactivity/effects.js

View workflow job for this annotation

GitHub Actions / Lint

Unexpected console statement
// Top-level `$effect(...)` in an unmounted component — defer until mount
var context = /** @type {ComponentContext} */ (component_context);
(context.e ??= []).push(fn);
Expand Down
8 changes: 4 additions & 4 deletions packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,9 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
var anchor_node = anchor ?? target.appendChild(create_text());

branch(() => {
var ctx;
if (context) {
push({});
var ctx = /** @type {ComponentContext} */ (component_context);
ctx = push({});
ctx.c = context;
}

Expand All @@ -243,8 +243,8 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
/** @type {Effect} */ (active_effect).nodes_end = hydrate_node;
}

if (context) {
pop();
if (context && ctx) {
pop(ctx);
}
});

Expand Down
2 changes: 2 additions & 0 deletions packages/svelte/src/internal/client/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export type ComponentContext = {
c: null | Map<unknown, unknown>;
/** deferred effects */
e: null | Array<() => void | (() => void)>;
/** whether the component is suspending */
a: boolean;
/**
* props — needed for legacy mode lifecycle functions, and for `createEventDispatcher`
* @deprecated remove in 6.0
Expand Down
Loading