async deriveds

aaa
Rich Harris 8 months ago
parent 2cd6b73236
commit 32e12d03b3

@ -264,7 +264,8 @@ export function analyze_module(ast, options) {
accessors: false, accessors: false,
runes: true, runes: true,
immutable: true, immutable: true,
tracing: analysis.tracing tracing: analysis.tracing,
async_deriveds: new Set()
}; };
} }
@ -451,7 +452,8 @@ export function analyze_component(root, source, options) {
undefined_exports: new Map(), undefined_exports: new Map(),
snippet_renderers: new Map(), snippet_renderers: new Map(),
snippets: new Set(), snippets: new Set(),
is_async: false is_async: false,
async_deriveds: new Set()
}; };
if (!runes) { if (!runes) {

@ -7,6 +7,7 @@ import { get_parent, unwrap_optional } from '../../../utils/ast.js';
import { is_pure, is_safe_identifier } from './shared/utils.js'; import { is_pure, is_safe_identifier } from './shared/utils.js';
import { dev, locate_node, source } from '../../../state.js'; import { dev, locate_node, source } from '../../../state.js';
import * as b from '../../../utils/builders.js'; import * as b from '../../../utils/builders.js';
import { create_expression_metadata } from '../../nodes.js';
/** /**
* @param {CallExpression} node * @param {CallExpression} node
@ -207,7 +208,19 @@ export function CallExpression(node, context) {
} }
// `$inspect(foo)` or `$derived(foo) should not trigger the `static-state-reference` warning // `$inspect(foo)` or `$derived(foo) should not trigger the `static-state-reference` warning
if (rune === '$inspect' || rune === '$derived') { if (rune === '$derived') {
const expression = create_expression_metadata();
context.next({
...context.state,
function_depth: context.state.function_depth + 1,
expression
});
if (expression.is_async) {
context.state.analysis.async_deriveds.add(node);
}
} else if (rune === '$inspect') {
context.next({ ...context.state, function_depth: context.state.function_depth + 1 }); context.next({ ...context.state, function_depth: context.state.function_depth + 1 });
} else { } else {
context.next(); context.next();

@ -158,13 +158,28 @@ export function VariableDeclaration(node, context) {
} }
if (rune === '$derived' || rune === '$derived.by') { if (rune === '$derived' || rune === '$derived.by') {
const is_async = context.state.analysis.async_deriveds.has(
/** @type {CallExpression} */ (init)
);
if (declarator.id.type === 'Identifier') { if (declarator.id.type === 'Identifier') {
declarations.push( if (is_async) {
b.declarator( declarations.push(
declarator.id, b.declarator(
b.call('$.derived', rune === '$derived.by' ? value : b.thunk(value)) declarator.id,
) b.await(
); b.call('$.async_derived', rune === '$derived.by' ? value : b.thunk(value, true))
)
)
);
} else {
declarations.push(
b.declarator(
declarator.id,
b.call('$.derived', rune === '$derived.by' ? value : b.thunk(value))
)
);
}
} else { } else {
const bindings = extract_paths(declarator.id); const bindings = extract_paths(declarator.id);

@ -1,4 +1,4 @@
/** @import { Identifier } from 'estree' */ /** @import { CallExpression, Identifier } from 'estree' */
/** @import { ComponentContext, Context } from '../../types' */ /** @import { ComponentContext, Context } from '../../types' */
import { is_state_source } from '../../utils.js'; import { is_state_source } from '../../utils.js';
import * as b from '../../../../../utils/builders.js'; import * as b from '../../../../../utils/builders.js';
@ -17,6 +17,18 @@ export function get_value(node) {
*/ */
export function add_state_transformers(context) { export function add_state_transformers(context) {
for (const [name, binding] of context.state.scope.declarations) { for (const [name, binding] of context.state.scope.declarations) {
if (
binding.kind === 'derived' &&
context.state.analysis.async_deriveds.has(/** @type {CallExpression} */ (binding.initial))
) {
// async deriveds are a special case
context.state.transform[name] = {
read: b.call
};
continue;
}
if ( if (
is_state_source(binding, context.state.analysis) || is_state_source(binding, context.state.analysis) ||
binding.kind === 'derived' || binding.kind === 'derived' ||

@ -1,5 +1,5 @@
import type { AST, Binding } from '#compiler'; import type { AST, Binding } from '#compiler';
import type { Identifier, LabeledStatement, Node, Program } from 'estree'; import type { CallExpression, Identifier, LabeledStatement, Node, Program } from 'estree';
import type { Scope, ScopeRoot } from './scope.js'; import type { Scope, ScopeRoot } from './scope.js';
export interface Js { export interface Js {
@ -31,6 +31,9 @@ export interface Analysis {
// TODO figure out if we can move this to ComponentAnalysis // TODO figure out if we can move this to ComponentAnalysis
accessors: boolean; accessors: boolean;
/** A set of deriveds that contain `await` expressions */
async_deriveds: Set<CallExpression>;
} }
export interface ComponentAnalysis extends Analysis { export interface ComponentAnalysis extends Analysis {

@ -97,7 +97,7 @@ export {
template_with_script, template_with_script,
text text
} from './dom/template.js'; } from './dom/template.js';
export { derived, derived_safe_equal } from './reactivity/deriveds.js'; export { async_derived, derived, derived_safe_equal } from './reactivity/deriveds.js';
export { export {
effect_tracking, effect_tracking,
effect_root, effect_root,

@ -18,14 +18,16 @@ import {
update_reaction, update_reaction,
increment_write_version, increment_write_version,
set_active_effect, set_active_effect,
component_context component_context,
get
} from '../runtime.js'; } from '../runtime.js';
import { equals, safe_equals } from './equality.js'; import { equals, safe_equals } from './equality.js';
import * as e from '../errors.js'; import * as e from '../errors.js';
import { destroy_effect } from './effects.js'; import { destroy_effect, render_effect } from './effects.js';
import { inspect_effects, set_inspect_effects } from './sources.js'; import { inspect_effects, internal_set, set_inspect_effects, source } from './sources.js';
import { get_stack } from '../dev/tracing.js'; import { get_stack } from '../dev/tracing.js';
import { tracing_mode_flag } from '../../flags/index.js'; import { tracing_mode_flag } from '../../flags/index.js';
import { preserve_context } from '../dom/blocks/boundary.js';
/** /**
* @template V * @template V
@ -75,6 +77,36 @@ export function derived(fn) {
return signal; return signal;
} }
/**
* @template V
* @param {() => Promise<V>} fn
* @returns {Promise<() => V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export async function async_derived(fn) {
if (!active_effect) {
throw new Error('TODO cannot create unowned async derived');
}
let promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined));
let value = source(/** @type {V} */ (undefined));
render_effect(() => {
const current = (promise = fn());
promise.then((v) => {
if (promise === current) {
internal_set(value, v);
}
});
// TODO what happens when the promise rejects?
});
(await preserve_context(promise)).read();
return () => get(value);
}
/** /**
* @template V * @template V
* @param {() => V} fn * @param {() => V} fn

Loading…
Cancel
Save