WIP implement `$effect.pending(...)`

effect-pending-value
Rich Harris 10 hours ago
parent 12e1c81fe6
commit 21811b1f73

@ -11,7 +11,8 @@ export function create_fragment(transparent = false) {
metadata: { metadata: {
transparent, transparent,
dynamic: false, dynamic: false,
has_await: false has_await: false,
effect_pending_expressions: []
} }
}; };
} }

@ -165,10 +165,22 @@ export function CallExpression(node, context) {
break; break;
case '$effect.pending': case '$effect.pending':
if (node.arguments.length > 1) {
e.rune_invalid_arguments_length(node, rune, 'zero or one arguments');
}
if (context.state.expression) { if (context.state.expression) {
context.state.expression.has_state = true; context.state.expression.has_state = true;
} }
if (node.arguments[0]) {
const fragment = /** @type {AST.Fragment} */ (context.state.fragment);
fragment.metadata.effect_pending_expressions.push(
/** @type {Expression} */ (node.arguments[0])
);
}
break; break;
case '$inspect': case '$inspect':

@ -24,6 +24,8 @@ export interface ClientTransformState extends TransformState {
/** `true` if we're transforming the contents of `<script>` */ /** `true` if we're transforming the contents of `<script>` */
readonly is_instance: boolean; readonly is_instance: boolean;
effect_pending: Map<Expression, Identifier>;
readonly transform: Record< readonly transform: Record<
string, string,
{ {

@ -63,6 +63,17 @@ export function CallExpression(node, context) {
); );
case '$effect.pending': case '$effect.pending':
if (node.arguments[0]) {
const id = b.id(`$$pending_${context.state.effect_pending.size}`);
context.state.effect_pending.set(
/** @type {Expression} */ (context.visit(node.arguments[0])),
id
);
return b.call('$.get', id);
}
return b.call('$.pending'); return b.call('$.pending');
case '$inspect': case '$inspect':

@ -68,6 +68,7 @@ export function Fragment(node, context) {
memoizer: new Memoizer(), memoizer: new Memoizer(),
template: new Template(), template: new Template(),
transform: { ...context.state.transform }, transform: { ...context.state.transform },
effect_pending: new Map(),
metadata: { metadata: {
namespace, namespace,
bound_contenteditable: context.state.metadata.bound_contenteditable bound_contenteditable: context.state.metadata.bound_contenteditable
@ -152,6 +153,10 @@ export function Fragment(node, context) {
body.push(...state.consts); body.push(...state.consts);
for (const [expression, id] of state.effect_pending) {
body.push(b.const(id, b.call('$.pending', b.thunk(expression))));
}
if (has_await) { if (has_await) {
body.push(b.if(b.call('$.aborted'), b.return())); body.push(b.if(b.call('$.aborted'), b.return()));
} }

@ -310,7 +310,8 @@ export function RegularElement(node, context) {
metadata, metadata,
scope: /** @type {Scope} */ (context.state.scopes.get(node.fragment)), scope: /** @type {Scope} */ (context.state.scopes.get(node.fragment)),
preserve_whitespace: preserve_whitespace:
context.state.preserve_whitespace || node.name === 'pre' || node.name === 'textarea' context.state.preserve_whitespace || node.name === 'pre' || node.name === 'textarea',
effect_pending: new Map()
}; };
const { hoisted, trimmed } = clean_nodes( const { hoisted, trimmed } = clean_nodes(
@ -378,6 +379,10 @@ export function RegularElement(node, context) {
} }
} }
for (const [expression, id] of state.effect_pending) {
child_state.init.push(b.const(id, b.call('$.pending', b.thunk(expression))));
}
if (node.fragment.nodes.some((node) => node.type === 'SnippetBlock')) { if (node.fragment.nodes.some((node) => node.type === 'SnippetBlock')) {
// Wrap children in `{...}` to avoid declaration conflicts // Wrap children in `{...}` to avoid declaration conflicts
context.state.init.push( context.state.init.push(

@ -26,7 +26,7 @@ export function CallExpression(node, context) {
} }
if (rune === '$effect.pending') { if (rune === '$effect.pending') {
return b.literal(0); return node.arguments[0] ?? b.literal(0);
} }
if (rune === '$state' || rune === '$state.raw') { if (rune === '$state' || rune === '$state.raw') {

@ -57,6 +57,7 @@ export namespace AST {
*/ */
dynamic: boolean; dynamic: boolean;
has_await: boolean; has_await: boolean;
effect_pending_expressions: Expression[];
}; };
} }

@ -8,7 +8,13 @@ import {
import { HYDRATION_START_ELSE } from '../../../../constants.js'; import { HYDRATION_START_ELSE } from '../../../../constants.js';
import { component_context, set_component_context } from '../../context.js'; import { component_context, set_component_context } from '../../context.js';
import { handle_error, invoke_error_boundary } from '../../error-handling.js'; import { handle_error, invoke_error_boundary } from '../../error-handling.js';
import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js'; import {
block,
branch,
destroy_effect,
inspect_effect,
pause_effect
} from '../../reactivity/effects.js';
import { import {
active_effect, active_effect,
active_reaction, active_reaction,
@ -33,6 +39,7 @@ import { Batch, current_batch, effect_pending_updates } from '../../reactivity/b
import { internal_set, source } from '../../reactivity/sources.js'; import { internal_set, source } from '../../reactivity/sources.js';
import { tag } from '../../dev/tracing.js'; import { tag } from '../../dev/tracing.js';
import { createSubscriber } from '../../../../reactivity/create-subscriber.js'; import { createSubscriber } from '../../../../reactivity/create-subscriber.js';
import { untrack } from 'svelte';
/** /**
* @typedef {{ * @typedef {{
@ -440,7 +447,10 @@ export function get_boundary() {
return /** @type {Boundary} */ (/** @type {Effect} */ (active_effect).b); return /** @type {Boundary} */ (/** @type {Effect} */ (active_effect).b);
} }
export function pending() { /**
* @param {(() => any) | void} fn
*/
export function pending(fn) {
if (active_effect === null) { if (active_effect === null) {
e.effect_pending_outside_reaction(); e.effect_pending_outside_reaction();
} }
@ -451,5 +461,25 @@ export function pending() {
return 0; // TODO eventually we will need this to be global return 0; // TODO eventually we will need this to be global
} }
if (fn) {
const signal = source(untrack(fn));
inspect_effect(() => {
const value = fn();
let stale = false;
queue_micro_task(() => {
if (stale) return;
internal_set(signal, value);
});
return () => {
stale = true;
};
});
return signal;
}
return boundary.get_effect_pending(); return boundary.get_effect_pending();
} }

Loading…
Cancel
Save