Skip to content

Commit b8627e5

Browse files
authored
chore: centralise branch management (#16977)
* WIP * WIP * WIP * WIP * WIP * fix hydration * simplify * all tests passing * key blocks * snippets * fix * tidy up * WIP await * tidy up * fix * neaten up * unused * tweak * elements * changeset * fix * preserve newer batches * add comment * add comment * no longer necessary apparently? * move legacy logic to key block
1 parent 9a488d6 commit b8627e5

File tree

14 files changed

+409
-464
lines changed

14 files changed

+409
-464
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
chore: centralise branch management

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,9 @@ export function client_component(analysis, options) {
385385
.../** @type {ESTree.Statement[]} */ (template.body)
386386
]);
387387

388-
component_block.body.push(b.stmt(b.call(`$.async_body`, b.arrow([], body, true))));
388+
component_block.body.push(
389+
b.stmt(b.call(`$.async_body`, b.id('$$anchor'), b.arrow([b.id('$$anchor')], body, true)))
390+
);
389391
} else {
390392
component_block.body.push(
391393
...state.instance_level_snippets,

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,11 @@ export function Fragment(node, context) {
178178
}
179179

180180
if (has_await) {
181-
return b.block([b.stmt(b.call('$.async_body', b.arrow([], b.block(body), true)))]);
181+
return b.block([
182+
b.stmt(
183+
b.call('$.async_body', b.id('$$anchor'), b.arrow([b.id('$$anchor')], b.block(body), true))
184+
)
185+
]);
182186
} else {
183187
return b.block(body);
184188
}
Lines changed: 71 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,20 @@
1-
/** @import { Effect, Source, TemplateNode } from '#client' */
2-
import { DEV } from 'esm-env';
1+
/** @import { Source, TemplateNode } from '#client' */
32
import { is_promise } from '../../../shared/utils.js';
4-
import { block, branch, pause_effect, resume_effect } from '../../reactivity/effects.js';
3+
import { block } from '../../reactivity/effects.js';
54
import { internal_set, mutable_source, source } from '../../reactivity/sources.js';
6-
import { set_active_effect, set_active_reaction } from '../../runtime.js';
75
import {
86
hydrate_next,
9-
hydrate_node,
107
hydrating,
118
skip_nodes,
129
set_hydrate_node,
1310
set_hydrating
1411
} from '../hydration.js';
1512
import { queue_micro_task } from '../task.js';
1613
import { HYDRATION_START_ELSE, UNINITIALIZED } from '../../../../constants.js';
17-
import {
18-
component_context,
19-
dev_stack,
20-
is_runes,
21-
set_component_context,
22-
set_dev_current_component_function,
23-
set_dev_stack
24-
} from '../../context.js';
14+
import { is_runes } from '../../context.js';
2515
import { flushSync, is_flushing_sync } from '../../reactivity/batch.js';
16+
import { BranchManager } from './branches.js';
17+
import { capture, unset_context } from '../../reactivity/async.js';
2618

2719
const PENDING = 0;
2820
const THEN = 1;
@@ -33,7 +25,7 @@ const CATCH = 2;
3325
/**
3426
* @template V
3527
* @param {TemplateNode} node
36-
* @param {(() => Promise<V>)} get_input
28+
* @param {(() => any)} get_input
3729
* @param {null | ((anchor: Node) => void)} pending_fn
3830
* @param {null | ((anchor: Node, value: Source<V>) => void)} then_fn
3931
* @param {null | ((anchor: Node, error: unknown) => void)} catch_fn
@@ -44,161 +36,103 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) {
4436
hydrate_next();
4537
}
4638

47-
var anchor = node;
4839
var runes = is_runes();
49-
var active_component_context = component_context;
50-
51-
/** @type {any} */
52-
var component_function = DEV ? component_context?.function : null;
53-
var dev_original_stack = DEV ? dev_stack : null;
54-
55-
/** @type {V | Promise<V> | typeof UNINITIALIZED} */
56-
var input = UNINITIALIZED;
57-
58-
/** @type {Effect | null} */
59-
var pending_effect;
60-
61-
/** @type {Effect | null} */
62-
var then_effect;
63-
64-
/** @type {Effect | null} */
65-
var catch_effect;
66-
67-
var input_source = runes
68-
? source(/** @type {V} */ (undefined))
69-
: mutable_source(/** @type {V} */ (undefined), false, false);
70-
var error_source = runes ? source(undefined) : mutable_source(undefined, false, false);
71-
var resolved = false;
72-
/**
73-
* @param {AwaitState} state
74-
* @param {boolean} restore
75-
*/
76-
function update(state, restore) {
77-
resolved = true;
78-
79-
if (restore) {
80-
set_active_effect(effect);
81-
set_active_reaction(effect); // TODO do we need both?
82-
set_component_context(active_component_context);
83-
if (DEV) {
84-
set_dev_current_component_function(component_function);
85-
set_dev_stack(dev_original_stack);
86-
}
87-
}
88-
89-
try {
90-
if (state === PENDING && pending_fn) {
91-
if (pending_effect) resume_effect(pending_effect);
92-
else pending_effect = branch(() => pending_fn(anchor));
93-
}
94-
95-
if (state === THEN && then_fn) {
96-
if (then_effect) resume_effect(then_effect);
97-
else then_effect = branch(() => then_fn(anchor, input_source));
98-
}
99-
100-
if (state === CATCH && catch_fn) {
101-
if (catch_effect) resume_effect(catch_effect);
102-
else catch_effect = branch(() => catch_fn(anchor, error_source));
103-
}
104-
105-
if (state !== PENDING && pending_effect) {
106-
pause_effect(pending_effect, () => (pending_effect = null));
107-
}
108-
109-
if (state !== THEN && then_effect) {
110-
pause_effect(then_effect, () => (then_effect = null));
111-
}
112-
113-
if (state !== CATCH && catch_effect) {
114-
pause_effect(catch_effect, () => (catch_effect = null));
115-
}
116-
} finally {
117-
if (restore) {
118-
if (DEV) {
119-
set_dev_current_component_function(null);
120-
set_dev_stack(null);
121-
}
12240

123-
set_component_context(null);
124-
set_active_reaction(null);
125-
set_active_effect(null);
41+
var v = /** @type {V} */ (UNINITIALIZED);
42+
var value = runes ? source(v) : mutable_source(v, false, false);
43+
var error = runes ? source(v) : mutable_source(v, false, false);
12644

127-
// without this, the DOM does not update until two ticks after the promise
128-
// resolves, which is unexpected behaviour (and somewhat irksome to test)
129-
if (!is_flushing_sync) flushSync();
130-
}
131-
}
132-
}
45+
var branches = new BranchManager(node);
13346

134-
var effect = block(() => {
135-
if (input === (input = get_input())) return;
47+
block(() => {
48+
var input = get_input();
49+
var destroyed = false;
13650

13751
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
138-
// @ts-ignore coercing `anchor` to a `Comment` causes TypeScript and Prettier to fight
139-
let mismatch = hydrating && is_promise(input) === (anchor.data === HYDRATION_START_ELSE);
52+
// @ts-ignore coercing `node` to a `Comment` causes TypeScript and Prettier to fight
53+
let mismatch = hydrating && is_promise(input) === (node.data === HYDRATION_START_ELSE);
14054

14155
if (mismatch) {
14256
// Hydration mismatch: remove everything inside the anchor and start fresh
143-
anchor = skip_nodes();
144-
145-
set_hydrate_node(anchor);
57+
set_hydrate_node(skip_nodes());
14658
set_hydrating(false);
147-
mismatch = true;
14859
}
14960

15061
if (is_promise(input)) {
151-
var promise = input;
62+
var restore = capture();
63+
var resolved = false;
64+
65+
/**
66+
* @param {() => void} fn
67+
*/
68+
const resolve = (fn) => {
69+
if (destroyed) return;
70+
71+
resolved = true;
72+
restore();
73+
74+
if (hydrating) {
75+
// `restore()` could set `hydrating` to `true`, which we very much
76+
// don't want — we want to restore everything _except_ this
77+
set_hydrating(false);
78+
}
15279

153-
resolved = false;
80+
try {
81+
fn();
82+
} finally {
83+
unset_context();
15484

155-
promise.then(
156-
(value) => {
157-
if (promise !== input) return;
158-
// we technically could use `set` here since it's on the next microtick
159-
// but let's use internal_set for consistency and just to be safe
160-
internal_set(input_source, value);
161-
update(THEN, true);
85+
// without this, the DOM does not update until two ticks after the promise
86+
// resolves, which is unexpected behaviour (and somewhat irksome to test)
87+
if (!is_flushing_sync) flushSync();
88+
}
89+
};
90+
91+
input.then(
92+
(v) => {
93+
resolve(() => {
94+
internal_set(value, v);
95+
branches.ensure(THEN, then_fn && ((target) => then_fn(target, value)));
96+
});
16297
},
163-
(error) => {
164-
if (promise !== input) return;
165-
// we technically could use `set` here since it's on the next microtick
166-
// but let's use internal_set for consistency and just to be safe
167-
internal_set(error_source, error);
168-
update(CATCH, true);
169-
if (!catch_fn) {
170-
// Rethrow the error if no catch block exists
171-
throw error_source.v;
172-
}
98+
(e) => {
99+
resolve(() => {
100+
internal_set(error, e);
101+
branches.ensure(THEN, catch_fn && ((target) => catch_fn(target, error)));
102+
103+
if (!catch_fn) {
104+
// Rethrow the error if no catch block exists
105+
throw error.v;
106+
}
107+
});
173108
}
174109
);
175110

176111
if (hydrating) {
177-
if (pending_fn) {
178-
pending_effect = branch(() => pending_fn(anchor));
179-
}
112+
branches.ensure(PENDING, pending_fn);
180113
} else {
181114
// Wait a microtask before checking if we should show the pending state as
182-
// the promise might have resolved by the next microtask.
115+
// the promise might have resolved by then
183116
queue_micro_task(() => {
184-
if (!resolved) update(PENDING, true);
117+
if (!resolved) {
118+
resolve(() => {
119+
branches.ensure(PENDING, pending_fn);
120+
});
121+
}
185122
});
186123
}
187124
} else {
188-
internal_set(input_source, input);
189-
update(THEN, false);
125+
internal_set(value, input);
126+
branches.ensure(THEN, then_fn && ((target) => then_fn(target, value)));
190127
}
191128

192129
if (mismatch) {
193130
// continue in hydration mode
194131
set_hydrating(true);
195132
}
196133

197-
// Set the input to something else, in order to disable the promise callbacks
198-
return () => (input = UNINITIALIZED);
134+
return () => {
135+
destroyed = true;
136+
};
199137
});
200-
201-
if (hydrating) {
202-
anchor = hydrate_node;
203-
}
204138
}

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

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ import {
88
import { HYDRATION_START_ELSE } from '../../../../constants.js';
99
import { component_context, set_component_context } from '../../context.js';
1010
import { handle_error, invoke_error_boundary } from '../../error-handling.js';
11-
import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js';
11+
import {
12+
block,
13+
branch,
14+
destroy_effect,
15+
move_effect,
16+
pause_effect
17+
} from '../../reactivity/effects.js';
1218
import {
1319
active_effect,
1420
active_reaction,
@@ -425,24 +431,6 @@ export class Boundary {
425431
}
426432
}
427433

428-
/**
429-
*
430-
* @param {Effect} effect
431-
* @param {DocumentFragment} fragment
432-
*/
433-
function move_effect(effect, fragment) {
434-
var node = effect.nodes_start;
435-
var end = effect.nodes_end;
436-
437-
while (node !== null) {
438-
/** @type {TemplateNode | null} */
439-
var next = node === end ? null : /** @type {TemplateNode} */ (get_next_sibling(node));
440-
441-
fragment.append(node);
442-
node = next;
443-
}
444-
}
445-
446434
export function get_boundary() {
447435
return /** @type {Boundary} */ (/** @type {Effect} */ (active_effect).b);
448436
}

0 commit comments

Comments
 (0)