Skip to content

Commit 74c483c

Browse files
committed
wip
1 parent dfa97a5 commit 74c483c

File tree

7 files changed

+160
-39
lines changed

7 files changed

+160
-39
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/** @import { Context } from '../types' */
33
import * as e from '../../../errors.js';
44

5-
const valid = ['onerror', 'failed'];
5+
const valid = ['onerror', 'failed', 'pending'];
66

77
/**
88
* @param {AST.SvelteBoundary} node

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ export function SvelteBoundary(node, context) {
3939

4040
// Capture the `failed` implicit snippet prop
4141
for (const child of node.fragment.nodes) {
42-
if (child.type === 'SnippetBlock' && child.expression.name === 'failed') {
42+
if (
43+
child.type === 'SnippetBlock' &&
44+
(child.expression.name === 'failed' || child.expression.name === 'pending')
45+
) {
4346
// we need to delay the visit of the snippets in case they access a ConstTag that is declared
4447
// after the snippets so that the visitor for the const tag can be updated
4548
snippets_visits.push(() => {

packages/svelte/src/index-client.js

+2
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,5 @@ export {
191191
} from './internal/client/runtime.js';
192192

193193
export { createRawSnippet } from './internal/client/dom/blocks/snippet.js';
194+
195+
export { suspend, unsuspend } from './internal/client/dom/blocks/boundary.js';

packages/svelte/src/internal/client/dom/blocks/boundary.js

+130-24
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
/** @import { Effect, TemplateNode, } from '#client' */
22

3-
import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '../../constants.js';
4-
import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js';
3+
import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT, INERT } from '../../constants.js';
4+
import {
5+
block,
6+
branch,
7+
destroy_effect,
8+
pause_effect,
9+
resume_effect
10+
} from '../../reactivity/effects.js';
511
import {
612
active_effect,
713
active_reaction,
@@ -20,8 +26,12 @@ import {
2026
remove_nodes,
2127
set_hydrate_node
2228
} from '../hydration.js';
29+
import { get_next_sibling } from '../operations.js';
2330
import { queue_micro_task } from '../task.js';
2431

32+
const SUSPEND_INCREMENT = Symbol();
33+
const SUSPEND_DECREMENT = Symbol();
34+
2535
/**
2636
* @param {Effect} boundary
2737
* @param {() => void} fn
@@ -49,6 +59,7 @@ function with_boundary(boundary, fn) {
4959
* @param {{
5060
* onerror?: (error: unknown, reset: () => void) => void,
5161
* failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void
62+
* pending?: (anchor: Node) => void
5263
* }} props
5364
* @param {((anchor: Node) => void)} boundary_fn
5465
* @returns {void}
@@ -58,14 +69,95 @@ export function boundary(node, props, boundary_fn) {
5869

5970
/** @type {Effect} */
6071
var boundary_effect;
72+
/** @type {Effect | null} */
73+
var suspended_effect = null;
74+
/** @type {DocumentFragment | null} */
75+
var suspended_fragment = null;
76+
var suspend_count = 0;
6177

6278
block(() => {
6379
var boundary = /** @type {Effect} */ (active_effect);
6480
var hydrate_open = hydrate_node;
6581
var is_creating_fallback = false;
6682

67-
// We re-use the effect's fn property to avoid allocation of an additional field
68-
boundary.fn = (/** @type {unknown}} */ error) => {
83+
const render_snippet = (/** @type { () => void } */ snippet_fn) => {
84+
// Render the snippet in a microtask
85+
queue_micro_task(() => {
86+
with_boundary(boundary, () => {
87+
is_creating_fallback = true;
88+
89+
try {
90+
boundary_effect = branch(() => {
91+
snippet_fn();
92+
});
93+
} catch (error) {
94+
handle_error(error, boundary, null, boundary.ctx);
95+
}
96+
97+
reset_is_throwing_error();
98+
is_creating_fallback = false;
99+
});
100+
});
101+
};
102+
103+
// @ts-ignore We re-use the effect's fn property to avoid allocation of an additional field
104+
boundary.fn = (/** @type {unknown} */ input) => {
105+
let pending = props.pending;
106+
107+
if (input === SUSPEND_INCREMENT) {
108+
if (!pending) {
109+
return false;
110+
}
111+
suspend_count++;
112+
113+
if (suspended_effect === null) {
114+
var effect = boundary_effect;
115+
suspended_effect = boundary_effect;
116+
117+
pause_effect(suspended_effect, () => {
118+
/** @type {TemplateNode | null} */
119+
var node = effect.nodes_start;
120+
var end = effect.nodes_end;
121+
suspended_fragment = document.createDocumentFragment();
122+
123+
while (node !== null) {
124+
/** @type {TemplateNode | null} */
125+
var sibling =
126+
node === end ? null : /** @type {TemplateNode} */ (get_next_sibling(node));
127+
128+
node.remove();
129+
suspended_fragment.append(node);
130+
node = sibling;
131+
}
132+
}, false);
133+
134+
render_snippet(() => {
135+
pending(anchor);
136+
});
137+
}
138+
return true;
139+
}
140+
141+
if (input === SUSPEND_DECREMENT) {
142+
if (!pending) {
143+
return false;
144+
}
145+
suspend_count--;
146+
147+
if (suspend_count === 0 && suspended_effect !== null) {
148+
if (boundary_effect) {
149+
destroy_effect(boundary_effect);
150+
}
151+
boundary_effect = suspended_effect;
152+
suspended_effect = null;
153+
anchor.before(/** @type {DocumentFragment} */ (suspended_fragment));
154+
resume_effect(boundary_effect);
155+
}
156+
157+
return true;
158+
}
159+
160+
var error = input;
69161
var onerror = props.onerror;
70162
let failed = props.failed;
71163

@@ -96,26 +188,12 @@ export function boundary(node, props, boundary_fn) {
96188
}
97189

98190
if (failed) {
99-
// Render the `failed` snippet in a microtask
100-
queue_micro_task(() => {
101-
with_boundary(boundary, () => {
102-
is_creating_fallback = true;
103-
104-
try {
105-
boundary_effect = branch(() => {
106-
failed(
107-
anchor,
108-
() => error,
109-
() => reset
110-
);
111-
});
112-
} catch (error) {
113-
handle_error(error, boundary, null, boundary.ctx);
114-
}
115-
116-
reset_is_throwing_error();
117-
is_creating_fallback = false;
118-
});
191+
render_snippet(() => {
192+
failed(
193+
anchor,
194+
() => error,
195+
() => reset
196+
);
119197
});
120198
}
121199
};
@@ -132,3 +210,31 @@ export function boundary(node, props, boundary_fn) {
132210
anchor = hydrate_node;
133211
}
134212
}
213+
214+
export function suspend() {
215+
var current = active_effect;
216+
217+
while (current !== null) {
218+
if ((current.f & BOUNDARY_EFFECT) !== 0) {
219+
// @ts-ignore
220+
if (current.fn(SUSPEND_INCREMENT)) {
221+
return;
222+
}
223+
}
224+
current = current.parent;
225+
}
226+
}
227+
228+
export function unsuspend() {
229+
var current = active_effect;
230+
231+
while (current !== null) {
232+
if ((current.f & BOUNDARY_EFFECT) !== 0) {
233+
// @ts-ignore
234+
if (current.fn(SUSPEND_DECREMENT)) {
235+
return;
236+
}
237+
}
238+
current = current.parent;
239+
}
240+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export {
129129
update_store,
130130
mark_store_binding
131131
} from './reactivity/store.js';
132-
export { boundary } from './dom/blocks/boundary.js';
132+
export { boundary, suspend } from './dom/blocks/boundary.js';
133133
export { set_text } from './render.js';
134134
export {
135135
get,

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

+21-11
Original file line numberDiff line numberDiff line change
@@ -528,15 +528,20 @@ export function unlink_effect(effect) {
528528
* A paused effect does not update, and the DOM subtree becomes inert.
529529
* @param {Effect} effect
530530
* @param {() => void} [callback]
531+
* @param {boolean} [destroy]
531532
*/
532-
export function pause_effect(effect, callback) {
533+
export function pause_effect(effect, callback, destroy = true) {
533534
/** @type {TransitionManager[]} */
534535
var transitions = [];
535536

536-
pause_children(effect, transitions, true);
537+
pause_children(effect, transitions, true, destroy);
537538

538539
run_out_transitions(transitions, () => {
539-
destroy_effect(effect);
540+
if (destroy) {
541+
destroy_effect(effect);
542+
} else {
543+
execute_effect_teardown(effect);
544+
}
540545
if (callback) callback();
541546
});
542547
}
@@ -561,8 +566,9 @@ export function run_out_transitions(transitions, fn) {
561566
* @param {Effect} effect
562567
* @param {TransitionManager[]} transitions
563568
* @param {boolean} local
569+
* @param {boolean} [destroy]
564570
*/
565-
export function pause_children(effect, transitions, local) {
571+
export function pause_children(effect, transitions, local, destroy = true) {
566572
if ((effect.f & INERT) !== 0) return;
567573
effect.f ^= INERT;
568574

@@ -582,7 +588,7 @@ export function pause_children(effect, transitions, local) {
582588
// TODO we don't need to call pause_children recursively with a linked list in place
583589
// it's slightly more involved though as we have to account for `transparent` changing
584590
// through the tree.
585-
pause_children(child, transitions, transparent ? local : false);
591+
pause_children(child, transitions, transparent ? local : false, destroy);
586592
child = sibling;
587593
}
588594
}
@@ -602,17 +608,21 @@ export function resume_effect(effect) {
602608
*/
603609
function resume_children(effect, local) {
604610
if ((effect.f & INERT) === 0) return;
611+
effect.f ^= INERT;
612+
613+
// Ensure the effect is marked as clean again so that any dirty child
614+
// effects can schedule themselves for execution
615+
if ((effect.f & CLEAN) === 0) {
616+
effect.f ^= CLEAN;
617+
}
605618

606619
// If a dependency of this effect changed while it was paused,
607-
// apply the change now
620+
// schedule the effect to update
608621
if (check_dirtiness(effect)) {
609-
update_effect(effect);
622+
set_signal_status(effect, DIRTY);
623+
schedule_effect(effect);
610624
}
611625

612-
// Ensure we toggle the flag after possibly updating the effect so that
613-
// each block logic can correctly operate on inert items
614-
effect.f ^= INERT;
615-
616626
var child = effect.first;
617627

618628
while (child !== null) {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ export function schedule_effect(signal) {
776776
var flags = effect.f;
777777

778778
if ((flags & (ROOT_EFFECT | BRANCH_EFFECT)) !== 0) {
779-
if ((flags & CLEAN) === 0) return;
779+
if ((flags & CLEAN) === 0) return
780780
effect.f ^= CLEAN;
781781
}
782782
}

0 commit comments

Comments
 (0)