Skip to content

Commit 32e12d0

Browse files
committedJan 16, 2025
async deriveds

File tree

7 files changed

+92
-15
lines changed

7 files changed

+92
-15
lines changed
 

‎packages/svelte/src/compiler/phases/2-analyze/index.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,8 @@ export function analyze_module(ast, options) {
264264
accessors: false,
265265
runes: true,
266266
immutable: true,
267-
tracing: analysis.tracing
267+
tracing: analysis.tracing,
268+
async_deriveds: new Set()
268269
};
269270
}
270271

@@ -451,7 +452,8 @@ export function analyze_component(root, source, options) {
451452
undefined_exports: new Map(),
452453
snippet_renderers: new Map(),
453454
snippets: new Set(),
454-
is_async: false
455+
is_async: false,
456+
async_deriveds: new Set()
455457
};
456458

457459
if (!runes) {

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

+14-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { get_parent, unwrap_optional } from '../../../utils/ast.js';
77
import { is_pure, is_safe_identifier } from './shared/utils.js';
88
import { dev, locate_node, source } from '../../../state.js';
99
import * as b from '../../../utils/builders.js';
10+
import { create_expression_metadata } from '../../nodes.js';
1011

1112
/**
1213
* @param {CallExpression} node
@@ -207,7 +208,19 @@ export function CallExpression(node, context) {
207208
}
208209

209210
// `$inspect(foo)` or `$derived(foo) should not trigger the `static-state-reference` warning
210-
if (rune === '$inspect' || rune === '$derived') {
211+
if (rune === '$derived') {
212+
const expression = create_expression_metadata();
213+
214+
context.next({
215+
...context.state,
216+
function_depth: context.state.function_depth + 1,
217+
expression
218+
});
219+
220+
if (expression.is_async) {
221+
context.state.analysis.async_deriveds.add(node);
222+
}
223+
} else if (rune === '$inspect') {
211224
context.next({ ...context.state, function_depth: context.state.function_depth + 1 });
212225
} else {
213226
context.next();

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

+21-6
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,28 @@ export function VariableDeclaration(node, context) {
158158
}
159159

160160
if (rune === '$derived' || rune === '$derived.by') {
161+
const is_async = context.state.analysis.async_deriveds.has(
162+
/** @type {CallExpression} */ (init)
163+
);
164+
161165
if (declarator.id.type === 'Identifier') {
162-
declarations.push(
163-
b.declarator(
164-
declarator.id,
165-
b.call('$.derived', rune === '$derived.by' ? value : b.thunk(value))
166-
)
167-
);
166+
if (is_async) {
167+
declarations.push(
168+
b.declarator(
169+
declarator.id,
170+
b.await(
171+
b.call('$.async_derived', rune === '$derived.by' ? value : b.thunk(value, true))
172+
)
173+
)
174+
);
175+
} else {
176+
declarations.push(
177+
b.declarator(
178+
declarator.id,
179+
b.call('$.derived', rune === '$derived.by' ? value : b.thunk(value))
180+
)
181+
);
182+
}
168183
} else {
169184
const bindings = extract_paths(declarator.id);
170185

‎packages/svelte/src/compiler/phases/3-transform/client/visitors/shared/declarations.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { Identifier } from 'estree' */
1+
/** @import { CallExpression, Identifier } from 'estree' */
22
/** @import { ComponentContext, Context } from '../../types' */
33
import { is_state_source } from '../../utils.js';
44
import * as b from '../../../../../utils/builders.js';
@@ -17,6 +17,18 @@ export function get_value(node) {
1717
*/
1818
export function add_state_transformers(context) {
1919
for (const [name, binding] of context.state.scope.declarations) {
20+
if (
21+
binding.kind === 'derived' &&
22+
context.state.analysis.async_deriveds.has(/** @type {CallExpression} */ (binding.initial))
23+
) {
24+
// async deriveds are a special case
25+
context.state.transform[name] = {
26+
read: b.call
27+
};
28+
29+
continue;
30+
}
31+
2032
if (
2133
is_state_source(binding, context.state.analysis) ||
2234
binding.kind === 'derived' ||

‎packages/svelte/src/compiler/phases/types.d.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { AST, Binding } from '#compiler';
2-
import type { Identifier, LabeledStatement, Node, Program } from 'estree';
2+
import type { CallExpression, Identifier, LabeledStatement, Node, Program } from 'estree';
33
import type { Scope, ScopeRoot } from './scope.js';
44

55
export interface Js {
@@ -31,6 +31,9 @@ export interface Analysis {
3131

3232
// TODO figure out if we can move this to ComponentAnalysis
3333
accessors: boolean;
34+
35+
/** A set of deriveds that contain `await` expressions */
36+
async_deriveds: Set<CallExpression>;
3437
}
3538

3639
export interface ComponentAnalysis extends Analysis {

‎packages/svelte/src/internal/client/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export {
9797
template_with_script,
9898
text
9999
} from './dom/template.js';
100-
export { derived, derived_safe_equal } from './reactivity/deriveds.js';
100+
export { async_derived, derived, derived_safe_equal } from './reactivity/deriveds.js';
101101
export {
102102
effect_tracking,
103103
effect_root,

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

+35-3
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@ import {
1818
update_reaction,
1919
increment_write_version,
2020
set_active_effect,
21-
component_context
21+
component_context,
22+
get
2223
} from '../runtime.js';
2324
import { equals, safe_equals } from './equality.js';
2425
import * as e from '../errors.js';
25-
import { destroy_effect } from './effects.js';
26-
import { inspect_effects, set_inspect_effects } from './sources.js';
26+
import { destroy_effect, render_effect } from './effects.js';
27+
import { inspect_effects, internal_set, set_inspect_effects, source } from './sources.js';
2728
import { get_stack } from '../dev/tracing.js';
2829
import { tracing_mode_flag } from '../../flags/index.js';
30+
import { preserve_context } from '../dom/blocks/boundary.js';
2931

3032
/**
3133
* @template V
@@ -75,6 +77,36 @@ export function derived(fn) {
7577
return signal;
7678
}
7779

80+
/**
81+
* @template V
82+
* @param {() => Promise<V>} fn
83+
* @returns {Promise<() => V>}
84+
*/
85+
/*#__NO_SIDE_EFFECTS__*/
86+
export async function async_derived(fn) {
87+
if (!active_effect) {
88+
throw new Error('TODO cannot create unowned async derived');
89+
}
90+
91+
let promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined));
92+
let value = source(/** @type {V} */ (undefined));
93+
94+
render_effect(() => {
95+
const current = (promise = fn());
96+
97+
promise.then((v) => {
98+
if (promise === current) {
99+
internal_set(value, v);
100+
}
101+
});
102+
103+
// TODO what happens when the promise rejects?
104+
});
105+
106+
(await preserve_context(promise)).read();
107+
return () => get(value);
108+
}
109+
78110
/**
79111
* @template V
80112
* @param {() => V} fn

0 commit comments

Comments
 (0)
Please sign in to comment.