experiment_s
Dominic Gannaway 2 days ago
parent dfa97a5e64
commit 74c483c69d

@ -2,7 +2,7 @@
/** @import { Context } from '../types' */
import * as e from '../../../errors.js';
const valid = ['onerror', 'failed'];
const valid = ['onerror', 'failed', 'pending'];
/**
* @param {AST.SvelteBoundary} node

@ -39,7 +39,10 @@ export function SvelteBoundary(node, context) {
// Capture the `failed` implicit snippet prop
for (const child of node.fragment.nodes) {
if (child.type === 'SnippetBlock' && child.expression.name === 'failed') {
if (
child.type === 'SnippetBlock' &&
(child.expression.name === 'failed' || child.expression.name === 'pending')
) {
// we need to delay the visit of the snippets in case they access a ConstTag that is declared
// after the snippets so that the visitor for the const tag can be updated
snippets_visits.push(() => {

@ -191,3 +191,5 @@ export {
} from './internal/client/runtime.js';
export { createRawSnippet } from './internal/client/dom/blocks/snippet.js';
export { suspend, unsuspend } from './internal/client/dom/blocks/boundary.js';

@ -1,7 +1,13 @@
/** @import { Effect, TemplateNode, } from '#client' */
import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '../../constants.js';
import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js';
import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT, INERT } from '../../constants.js';
import {
block,
branch,
destroy_effect,
pause_effect,
resume_effect
} from '../../reactivity/effects.js';
import {
active_effect,
active_reaction,
@ -20,8 +26,12 @@ import {
remove_nodes,
set_hydrate_node
} from '../hydration.js';
import { get_next_sibling } from '../operations.js';
import { queue_micro_task } from '../task.js';
const SUSPEND_INCREMENT = Symbol();
const SUSPEND_DECREMENT = Symbol();
/**
* @param {Effect} boundary
* @param {() => void} fn
@ -49,6 +59,7 @@ function with_boundary(boundary, fn) {
* @param {{
* onerror?: (error: unknown, reset: () => void) => void,
* failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void
* pending?: (anchor: Node) => void
* }} props
* @param {((anchor: Node) => void)} boundary_fn
* @returns {void}
@ -58,14 +69,95 @@ export function boundary(node, props, boundary_fn) {
/** @type {Effect} */
var boundary_effect;
/** @type {Effect | null} */
var suspended_effect = null;
/** @type {DocumentFragment | null} */
var suspended_fragment = null;
var suspend_count = 0;
block(() => {
var boundary = /** @type {Effect} */ (active_effect);
var hydrate_open = hydrate_node;
var is_creating_fallback = false;
// We re-use the effect's fn property to avoid allocation of an additional field
boundary.fn = (/** @type {unknown}} */ error) => {
const render_snippet = (/** @type { () => void } */ snippet_fn) => {
// Render the snippet in a microtask
queue_micro_task(() => {
with_boundary(boundary, () => {
is_creating_fallback = true;
try {
boundary_effect = branch(() => {
snippet_fn();
});
} catch (error) {
handle_error(error, boundary, null, boundary.ctx);
}
reset_is_throwing_error();
is_creating_fallback = false;
});
});
};
// @ts-ignore We re-use the effect's fn property to avoid allocation of an additional field
boundary.fn = (/** @type {unknown} */ input) => {
let pending = props.pending;
if (input === SUSPEND_INCREMENT) {
if (!pending) {
return false;
}
suspend_count++;
if (suspended_effect === null) {
var effect = boundary_effect;
suspended_effect = boundary_effect;
pause_effect(suspended_effect, () => {
/** @type {TemplateNode | null} */
var node = effect.nodes_start;
var end = effect.nodes_end;
suspended_fragment = document.createDocumentFragment();
while (node !== null) {
/** @type {TemplateNode | null} */
var sibling =
node === end ? null : /** @type {TemplateNode} */ (get_next_sibling(node));
node.remove();
suspended_fragment.append(node);
node = sibling;
}
}, false);
render_snippet(() => {
pending(anchor);
});
}
return true;
}
if (input === SUSPEND_DECREMENT) {
if (!pending) {
return false;
}
suspend_count--;
if (suspend_count === 0 && suspended_effect !== null) {
if (boundary_effect) {
destroy_effect(boundary_effect);
}
boundary_effect = suspended_effect;
suspended_effect = null;
anchor.before(/** @type {DocumentFragment} */ (suspended_fragment));
resume_effect(boundary_effect);
}
return true;
}
var error = input;
var onerror = props.onerror;
let failed = props.failed;
@ -96,26 +188,12 @@ export function boundary(node, props, boundary_fn) {
}
if (failed) {
// Render the `failed` snippet in a microtask
queue_micro_task(() => {
with_boundary(boundary, () => {
is_creating_fallback = true;
try {
boundary_effect = branch(() => {
failed(
anchor,
() => error,
() => reset
);
});
} catch (error) {
handle_error(error, boundary, null, boundary.ctx);
}
reset_is_throwing_error();
is_creating_fallback = false;
});
render_snippet(() => {
failed(
anchor,
() => error,
() => reset
);
});
}
};
@ -132,3 +210,31 @@ export function boundary(node, props, boundary_fn) {
anchor = hydrate_node;
}
}
export function suspend() {
var current = active_effect;
while (current !== null) {
if ((current.f & BOUNDARY_EFFECT) !== 0) {
// @ts-ignore
if (current.fn(SUSPEND_INCREMENT)) {
return;
}
}
current = current.parent;
}
}
export function unsuspend() {
var current = active_effect;
while (current !== null) {
if ((current.f & BOUNDARY_EFFECT) !== 0) {
// @ts-ignore
if (current.fn(SUSPEND_DECREMENT)) {
return;
}
}
current = current.parent;
}
}

@ -129,7 +129,7 @@ export {
update_store,
mark_store_binding
} from './reactivity/store.js';
export { boundary } from './dom/blocks/boundary.js';
export { boundary, suspend } from './dom/blocks/boundary.js';
export { set_text } from './render.js';
export {
get,

@ -528,15 +528,20 @@ export function unlink_effect(effect) {
* A paused effect does not update, and the DOM subtree becomes inert.
* @param {Effect} effect
* @param {() => void} [callback]
* @param {boolean} [destroy]
*/
export function pause_effect(effect, callback) {
export function pause_effect(effect, callback, destroy = true) {
/** @type {TransitionManager[]} */
var transitions = [];
pause_children(effect, transitions, true);
pause_children(effect, transitions, true, destroy);
run_out_transitions(transitions, () => {
destroy_effect(effect);
if (destroy) {
destroy_effect(effect);
} else {
execute_effect_teardown(effect);
}
if (callback) callback();
});
}
@ -561,8 +566,9 @@ export function run_out_transitions(transitions, fn) {
* @param {Effect} effect
* @param {TransitionManager[]} transitions
* @param {boolean} local
* @param {boolean} [destroy]
*/
export function pause_children(effect, transitions, local) {
export function pause_children(effect, transitions, local, destroy = true) {
if ((effect.f & INERT) !== 0) return;
effect.f ^= INERT;
@ -582,7 +588,7 @@ export function pause_children(effect, transitions, local) {
// TODO we don't need to call pause_children recursively with a linked list in place
// it's slightly more involved though as we have to account for `transparent` changing
// through the tree.
pause_children(child, transitions, transparent ? local : false);
pause_children(child, transitions, transparent ? local : false, destroy);
child = sibling;
}
}
@ -602,17 +608,21 @@ export function resume_effect(effect) {
*/
function resume_children(effect, local) {
if ((effect.f & INERT) === 0) return;
effect.f ^= INERT;
// Ensure the effect is marked as clean again so that any dirty child
// effects can schedule themselves for execution
if ((effect.f & CLEAN) === 0) {
effect.f ^= CLEAN;
}
// If a dependency of this effect changed while it was paused,
// apply the change now
// schedule the effect to update
if (check_dirtiness(effect)) {
update_effect(effect);
set_signal_status(effect, DIRTY);
schedule_effect(effect);
}
// Ensure we toggle the flag after possibly updating the effect so that
// each block logic can correctly operate on inert items
effect.f ^= INERT;
var child = effect.first;
while (child !== null) {

@ -776,7 +776,7 @@ export function schedule_effect(signal) {
var flags = effect.f;
if ((flags & (ROOT_EFFECT | BRANCH_EFFECT)) !== 0) {
if ((flags & CLEAN) === 0) return;
if ((flags & CLEAN) === 0) return
effect.f ^= CLEAN;
}
}

Loading…
Cancel
Save