Skip to content

Commit 2b08128

Browse files
committed
step one - template effects
1 parent 422e658 commit 2b08128

File tree

4 files changed

+97
-28
lines changed

4 files changed

+97
-28
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const EFFECT_HAS_DERIVED = 1 << 21;
2525
// Flags used for async
2626
export const IS_ASYNC = 1 << 22;
2727
export const REACTION_IS_UPDATING = 1 << 23;
28+
export const BOUNDARY_SUSPENDED = 1 << 24;
2829

2930
export const STATE_SYMBOL = Symbol('$state');
3031
export const STATE_SYMBOL_METADATA = Symbol('$state metadata');

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

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** @import { Effect, TemplateNode, } from '#client' */
22

3-
import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '../../constants.js';
3+
import { BOUNDARY_EFFECT, BOUNDARY_SUSPENDED, EFFECT_TRANSPARENT } from '../../constants.js';
44
import {
55
block,
66
branch,
@@ -16,7 +16,8 @@ import {
1616
set_active_effect,
1717
set_active_reaction,
1818
set_component_context,
19-
reset_is_throwing_error
19+
reset_is_throwing_error,
20+
schedule_effect
2021
} from '../../runtime.js';
2122
import {
2223
hydrate_next,
@@ -117,18 +118,8 @@ export function boundary(node, props, children) {
117118
pause_effect(
118119
effect,
119120
() => {
120-
var node = effect.nodes_start;
121-
var end = effect.nodes_end;
122-
123121
offscreen_fragment = document.createDocumentFragment();
124-
125-
while (node !== null) {
126-
/** @type {TemplateNode | null} */
127-
var next = node === end ? null : /** @type {TemplateNode} */ (get_next_sibling(node));
128-
129-
offscreen_fragment.append(node);
130-
node = next;
131-
}
122+
move_effect(effect, offscreen_fragment);
132123
},
133124
false
134125
);
@@ -146,7 +137,9 @@ export function boundary(node, props, children) {
146137
}
147138

148139
if (pending_effect !== null) {
149-
pause_effect(pending_effect);
140+
pause_effect(pending_effect, () => {
141+
pending_effect = null;
142+
});
150143
}
151144

152145
anchor.before(/** @type {DocumentFragment} */ (offscreen_fragment));
@@ -159,7 +152,9 @@ export function boundary(node, props, children) {
159152

160153
function reset() {
161154
if (failed_effect !== null) {
162-
pause_effect(failed_effect);
155+
pause_effect(failed_effect, () => {
156+
failed_effect = null;
157+
});
163158
}
164159

165160
main_effect = with_boundary(boundary, () => {
@@ -176,16 +171,32 @@ export function boundary(node, props, children) {
176171
// @ts-ignore We re-use the effect's fn property to avoid allocation of an additional field
177172
boundary.fn = (/** @type {unknown} */ input) => {
178173
if (input === ASYNC_INCREMENT) {
179-
if (async_count++ === 0) {
180-
queue_boundary_micro_task(suspend);
181-
}
174+
async_count++;
175+
176+
// TODO post-init, show the pending snippet after a timeout
182177

183178
return;
184179
}
185180

186181
if (input === ASYNC_DECREMENT) {
187182
if (--async_count === 0) {
188-
queue_boundary_micro_task(unsuspend);
183+
boundary.f ^= BOUNDARY_SUSPENDED;
184+
185+
if (pending_effect) {
186+
pause_effect(pending_effect, () => {
187+
pending_effect = null;
188+
});
189+
}
190+
191+
if (offscreen_fragment) {
192+
anchor.before(offscreen_fragment);
193+
offscreen_fragment = null;
194+
}
195+
196+
if (main_effect !== null) {
197+
// TODO do we also need to `resume_effect` here?
198+
schedule_effect(main_effect);
199+
}
189200
}
190201

191202
return;
@@ -260,6 +271,17 @@ export function boundary(node, props, children) {
260271
});
261272
} else {
262273
main_effect = branch(() => children(anchor));
274+
275+
if (async_count > 0) {
276+
if (pending) {
277+
offscreen_fragment = document.createDocumentFragment();
278+
move_effect(main_effect, offscreen_fragment);
279+
280+
pending_effect = branch(() => pending(anchor));
281+
} else {
282+
// TODO trigger pending boundary on parent
283+
}
284+
}
263285
}
264286

265287
reset_is_throwing_error();
@@ -270,6 +292,24 @@ export function boundary(node, props, children) {
270292
}
271293
}
272294

295+
/**
296+
*
297+
* @param {Effect} effect
298+
* @param {DocumentFragment} fragment
299+
*/
300+
function move_effect(effect, fragment) {
301+
var node = effect.nodes_start;
302+
var end = effect.nodes_end;
303+
304+
while (node !== null) {
305+
/** @type {TemplateNode | null} */
306+
var next = node === end ? null : /** @type {TemplateNode} */ (get_next_sibling(node));
307+
308+
fragment.append(node);
309+
node = next;
310+
}
311+
}
312+
273313
export function capture() {
274314
var previous_effect = active_effect;
275315
var previous_reaction = active_reaction;

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ import {
3030
UNOWNED,
3131
MAYBE_DIRTY,
3232
BLOCK_EFFECT,
33-
ROOT_EFFECT
33+
ROOT_EFFECT,
34+
IS_ASYNC,
35+
BOUNDARY_EFFECT,
36+
BOUNDARY_SUSPENDED
3437
} from '../constants.js';
3538
import * as e from '../errors.js';
3639
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
@@ -254,6 +257,22 @@ function mark_reactions(signal, status) {
254257
continue;
255258
}
256259

260+
// if we're about to trip an async derived, mark the boundary as
261+
// suspended _before_ we actually process effects
262+
if ((flags & IS_ASYNC) !== 0) {
263+
let boundary = /** @type {Derived} */ (reaction).parent;
264+
265+
while (boundary !== null && (boundary.f & BOUNDARY_EFFECT) === 0) {
266+
boundary = boundary.parent;
267+
}
268+
269+
if (boundary === null) {
270+
// TODO this is presumably an error — throw here?
271+
} else {
272+
boundary.f |= BOUNDARY_SUSPENDED;
273+
}
274+
}
275+
257276
set_signal_status(reaction, status);
258277

259278
// If the signal a) was previously clean or b) is an unowned derived, then mark it

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

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import {
2828
BOUNDARY_EFFECT,
2929
REACTION_IS_UPDATING,
3030
IS_ASYNC,
31-
TEMPLATE_EFFECT
31+
TEMPLATE_EFFECT,
32+
BOUNDARY_SUSPENDED
3233
} from './constants.js';
3334
import {
3435
flush_idle_tasks,
@@ -843,15 +844,16 @@ function process_effects(effect, collected_effects) {
843844
((flags & BLOCK_EFFECT) === 0 || (flags & TEMPLATE_EFFECT) !== 0);
844845

845846
if ((flags & RENDER_EFFECT) !== 0) {
846-
if (is_branch) {
847-
current_effect.f ^= CLEAN;
847+
if ((flags & BOUNDARY_EFFECT) !== 0) {
848+
suspended = (flags & BOUNDARY_SUSPENDED) !== 0;
849+
} else if (is_branch) {
850+
if (!suspended) {
851+
current_effect.f ^= CLEAN;
852+
}
848853
} else if (!skip_suspended) {
849854
try {
850855
if (check_dirtiness(current_effect)) {
851856
update_effect(current_effect);
852-
if ((flags & IS_ASYNC) !== 0 && !suspended) {
853-
suspended = true;
854-
}
855857
}
856858
} catch (error) {
857859
handle_error(error, current_effect, null, current_effect.ctx);
@@ -876,9 +878,16 @@ function process_effects(effect, collected_effects) {
876878
if (effect === parent) {
877879
break main_loop;
878880
}
879-
if (suspended && (parent.f & BOUNDARY_EFFECT) !== 0 && is_pending_boundary(parent)) {
880-
suspended = false;
881+
882+
if ((parent.f & BOUNDARY_EFFECT) !== 0) {
883+
let boundary = parent.parent;
884+
while (boundary !== null && (boundary.f & BOUNDARY_EFFECT) === 0) {
885+
boundary = boundary.parent;
886+
}
887+
888+
suspended = boundary === null ? false : (boundary.f & BOUNDARY_SUSPENDED) !== 0;
881889
}
890+
882891
var parent_sibling = parent.next;
883892
if (parent_sibling !== null) {
884893
current_effect = parent_sibling;

0 commit comments

Comments
 (0)