Skip to content

Commit dfe014e

Browse files
committed
wip
1 parent 530f65c commit dfe014e

File tree

11 files changed

+71
-16
lines changed

11 files changed

+71
-16
lines changed

packages/svelte/src/ambient.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,12 @@ declare namespace $derived {
218218
export const toString: never;
219219
}
220220

221+
declare function $async<T>(expression: T): T;
222+
223+
declare namespace $async {
224+
export function defer<T>(expression: T): T;
225+
}
226+
221227
/**
222228
* Runs code when a component is mounted to the DOM, and then whenever its dependencies change, i.e. `$state` or `$derived` values.
223229
* The timing of the execution is after the DOM has been updated.

packages/svelte/src/compiler/phases/2-analyze/visitors/CallExpression.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ export function CallExpression(node, context) {
9595

9696
break;
9797

98-
case '$async': {
98+
case '$async':
99+
case '$async.defer': {
99100
if (
100101
parent.type !== 'VariableDeclarator' ||
101102
get_parent(context.path, -3).type === 'ConstTag'
@@ -220,7 +221,7 @@ export function CallExpression(node, context) {
220221
function_depth: context.state.function_depth + 1,
221222
expression
222223
});
223-
} else if (rune === '$inspect' || rune === '$async') {
224+
} else if (rune === '$inspect' || rune === '$async' || rune === '$async.defer') {
224225
context.next({ ...context.state, function_depth: context.state.function_depth + 1 });
225226
} else {
226227
context.next();

packages/svelte/src/compiler/phases/2-analyze/visitors/VariableDeclarator.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export function VariableDeclarator(node, context) {
3030
rune === '$derived' ||
3131
rune === '$derived.by' ||
3232
rune === '$async' ||
33+
rune === '$async.defer' ||
3334
rune === '$props'
3435
) {
3536
for (const path of paths) {
@@ -40,7 +41,10 @@ export function VariableDeclarator(node, context) {
4041
? 'state'
4142
: rune === '$state.raw'
4243
? 'raw_state'
43-
: rune === '$derived' || rune === '$derived.by' || rune === '$async'
44+
: rune === '$derived' ||
45+
rune === '$derived.by' ||
46+
rune === '$async' ||
47+
rune === '$async.defer'
4448
? 'derived'
4549
: path.is_rest
4650
? 'rest_prop'

packages/svelte/src/compiler/phases/3-transform/client/visitors/VariableDeclaration.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,23 @@ export function VariableDeclaration(node, context) {
157157
continue;
158158
}
159159

160-
if (rune === '$async') {
160+
if (rune === '$async' || rune === '$async.defer') {
161161
if (declarator.id.type === 'Identifier') {
162162
declarations.push(
163163
b.declarator(
164164
declarator.id,
165-
b.call(b.await(b.call('$.save', b.call('$.async_derived', b.thunk(value, true)))))
165+
b.call(
166+
b.await(
167+
b.call(
168+
'$.save',
169+
b.call(
170+
'$.async_derived',
171+
b.thunk(value, true),
172+
rune === '$async.defer' && b.literal(true)
173+
)
174+
)
175+
)
176+
)
166177
)
167178
);
168179
} else {

packages/svelte/src/index-client.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,5 +217,5 @@ export function flushSync(fn) {
217217

218218
export { getContext, getAllContexts, hasContext, setContext } from './internal/client/context.js';
219219
export { hydrate, mount, unmount } from './internal/client/render.js';
220-
export { tick, untrack } from './internal/client/runtime.js';
220+
export { tick, untrack, isDeferred } from './internal/client/runtime.js';
221221
export { createRawSnippet } from './internal/client/dom/blocks/snippet.js';

packages/svelte/src/internal/client/constants.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export const EFFECT_HAS_DERIVED = 1 << 21;
2424

2525
// Flags used for async
2626
export const IS_ASYNC = 1 << 22;
27-
export const REACTION_IS_UPDATING = 1 << 23;
27+
export const IS_DEFERRED = 1 << 23;
28+
export const REACTION_IS_UPDATING = 1 << 24;
2829

2930
export const STATE_SYMBOL = Symbol('$state');
3031
export const STATE_SYMBOL_METADATA = Symbol('$state metadata');

packages/svelte/src/internal/client/reactivity/deriveds.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
DIRTY,
88
EFFECT_HAS_DERIVED,
99
IS_ASYNC,
10+
IS_DEFERRED,
1011
MAYBE_DIRTY,
1112
UNOWNED
1213
} from '../constants.js';
@@ -24,7 +25,7 @@ import {
2425
import { equals, safe_equals } from './equality.js';
2526
import * as e from '../errors.js';
2627
import { block, destroy_effect } from './effects.js';
27-
import { inspect_effects, internal_set, set_inspect_effects, source } from './sources.js';
28+
import { inspect_effects, internal_set, mark_reactions, set_inspect_effects, source } from './sources.js';
2829
import { get_stack } from '../dev/tracing.js';
2930
import { tracing_mode_flag } from '../../flags/index.js';
3031
import { capture, suspend } from '../dom/blocks/boundary.js';
@@ -76,10 +77,11 @@ export function derived(fn) {
7677
/**
7778
* @template V
7879
* @param {() => Promise<V>} fn
80+
* @param {boolean} [deferred]
7981
* @returns {Promise<Source<V>>}
8082
*/
8183
/*#__NO_SIDE_EFFECTS__*/
82-
export function async_derived(fn) {
84+
export function async_derived(fn, deferred = false) {
8385
let parent = /** @type {Effect | null} */ (active_effect);
8486

8587
if (parent === null) {
@@ -93,7 +95,12 @@ export function async_derived(fn) {
9395
var current = (promise = fn());
9496

9597
var restore = capture();
96-
var unsuspend = suspend();
98+
var unsuspend = !deferred ? suspend() : undefined;
99+
100+
if (deferred && (value.f & IS_DEFERRED) === 0) {
101+
value.f ^= IS_DEFERRED;
102+
mark_reactions(value, DIRTY);
103+
}
97104

98105
try {
99106
var v = await promise;
@@ -109,12 +116,15 @@ export function async_derived(fn) {
109116
} catch (e) {
110117
handle_error(e, parent, null, parent.ctx);
111118
} finally {
112-
unsuspend();
119+
if (deferred && (value.f & IS_DEFERRED) !== 0) {
120+
value.f ^= IS_DEFERRED;
121+
}
122+
unsuspend?.();
113123

114124
// TODO we should probably null out active effect here,
115125
// rather than inside `restore()`
116126
}
117-
}, IS_ASYNC);
127+
}, IS_ASYNC | (deferred ? IS_DEFERRED : 0));
118128

119129
return Promise.resolve(promise).then(() => value);
120130
}

packages/svelte/src/internal/client/reactivity/sources.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ export function update_pre(source, d = 1) {
259259
* @param {number} status should be DIRTY or MAYBE_DIRTY
260260
* @returns {void}
261261
*/
262-
function mark_reactions(signal, status) {
262+
export function mark_reactions(signal, status) {
263263
var reactions = signal.reactions;
264264
if (reactions === null) return;
265265

packages/svelte/src/internal/client/runtime.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import {
2626
BOUNDARY_EFFECT,
2727
REACTION_IS_UPDATING,
2828
IS_ASYNC,
29-
TEMPLATE_EFFECT
29+
TEMPLATE_EFFECT,
30+
IS_DEFERRED
3031
} from './constants.js';
3132
import {
3233
flush_idle_tasks,
@@ -841,7 +842,7 @@ function process_effects(effect, collected_effects) {
841842
active_reaction = current_effect;
842843
if (check_dirtiness(current_effect)) {
843844
update_effect(current_effect);
844-
if ((flags & IS_ASYNC) !== 0 && !suspended) {
845+
if ((flags & IS_ASYNC) !== 0 && (flags & IS_DEFERRED) === 0 && !suspended) {
845846
suspended = true;
846847
}
847848
}
@@ -1248,3 +1249,17 @@ export function deep_read(value, visited = new Set()) {
12481249
}
12491250
}
12501251
}
1252+
1253+
/**
1254+
* @param {() => any} fn
1255+
*/
1256+
export function isDeferred(fn) {
1257+
var signals = capture_signals(fn);
1258+
fn();
1259+
for (var signal of signals) {
1260+
if ((signal.f & IS_DEFERRED) !== 0) {
1261+
return true;
1262+
}
1263+
}
1264+
return false;
1265+
}

packages/svelte/src/utils.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,8 @@ const RUNES = /** @type {const} */ ([
444444
'$inspect().with',
445445
'$inspect.trace',
446446
'$host',
447-
'$async'
447+
'$async',
448+
'$async.defer'
448449
]);
449450

450451
/**

0 commit comments

Comments
 (0)