Skip to content

Commit c44b8ce

Browse files
committed
Merge branch 'branch-manager' into run-batch-until-complete
2 parents 90e3148 + 7c0c71b commit c44b8ce

File tree

15 files changed

+410
-468
lines changed

15 files changed

+410
-468
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
@@ -384,7 +384,9 @@ export function client_component(analysis, options) {
384384
.../** @type {ESTree.Statement[]} */ (template.body)
385385
]);
386386

387-
component_block.body.push(b.stmt(b.call(`$.async_body`, b.arrow([], body, true))));
387+
component_block.body.push(
388+
b.stmt(b.call(`$.async_body`, b.id('$$anchor'), b.arrow([b.id('$$anchor')], body, true)))
389+
);
388390
} else {
389391
component_block.body.push(
390392
...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
@@ -177,7 +177,11 @@ export function Fragment(node, context) {
177177
}
178178

179179
if (has_await) {
180-
return b.block([b.stmt(b.call('$.async_body', b.arrow([], b.block(body), true)))]);
180+
return b.block([
181+
b.stmt(
182+
b.call('$.async_body', b.id('$$anchor'), b.arrow([b.id('$$anchor')], b.block(body), true))
183+
)
184+
]);
181185
} else {
182186
return b.block(body);
183187
}
Lines changed: 70 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,102 @@ 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-
}
10840

109-
if (state !== THEN && then_effect) {
110-
pause_effect(then_effect, () => (then_effect = null));
111-
}
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);
11244

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-
}
45+
var branches = new BranchManager(node);
12246

123-
set_component_context(null);
124-
set_active_reaction(null);
125-
set_active_effect(null);
126-
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-
}
133-
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+
// we want to restore everything _except_ this
76+
set_hydrating(false);
77+
}
15278

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

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);
84+
// without this, the DOM does not update until two ticks after the promise
85+
// resolves, which is unexpected behaviour (and somewhat irksome to test)
86+
if (!is_flushing_sync) flushSync();
87+
}
88+
};
89+
90+
input.then(
91+
(v) => {
92+
resolve(() => {
93+
internal_set(value, v);
94+
branches.ensure(THEN, then_fn && ((target) => then_fn(target, value)));
95+
});
16296
},
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-
}
97+
(e) => {
98+
resolve(() => {
99+
internal_set(error, e);
100+
branches.ensure(THEN, catch_fn && ((target) => catch_fn(target, error)));
101+
102+
if (!catch_fn) {
103+
// Rethrow the error if no catch block exists
104+
throw error.v;
105+
}
106+
});
173107
}
174108
);
175109

176110
if (hydrating) {
177-
if (pending_fn) {
178-
pending_effect = branch(() => pending_fn(anchor));
179-
}
111+
branches.ensure(PENDING, pending_fn);
180112
} else {
181113
// Wait a microtask before checking if we should show the pending state as
182-
// the promise might have resolved by the next microtask.
114+
// the promise might have resolved by then
183115
queue_micro_task(() => {
184-
if (!resolved) update(PENDING, true);
116+
if (!resolved) {
117+
resolve(() => {
118+
branches.ensure(PENDING, pending_fn);
119+
});
120+
}
185121
});
186122
}
187123
} else {
188-
internal_set(input_source, input);
189-
update(THEN, false);
124+
internal_set(value, input);
125+
branches.ensure(THEN, then_fn && ((target) => then_fn(target, value)));
190126
}
191127

192128
if (mismatch) {
193129
// continue in hydration mode
194130
set_hydrating(true);
195131
}
196132

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

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,
@@ -418,24 +424,6 @@ export class Boundary {
418424
}
419425
}
420426

421-
/**
422-
*
423-
* @param {Effect} effect
424-
* @param {DocumentFragment} fragment
425-
*/
426-
function move_effect(effect, fragment) {
427-
var node = effect.nodes_start;
428-
var end = effect.nodes_end;
429-
430-
while (node !== null) {
431-
/** @type {TemplateNode | null} */
432-
var next = node === end ? null : /** @type {TemplateNode} */ (get_next_sibling(node));
433-
434-
fragment.append(node);
435-
node = next;
436-
}
437-
}
438-
439427
export function get_boundary() {
440428
return /** @type {Boundary} */ (/** @type {Effect} */ (active_effect).b);
441429
}

0 commit comments

Comments
 (0)