Skip to content

Commit 0045414

Browse files
committed
WIP class boundaries
1 parent 49cba86 commit 0045414

File tree

1 file changed

+166
-88
lines changed
  • packages/svelte/src/internal/client/dom/blocks

1 file changed

+166
-88
lines changed
Lines changed: 166 additions & 88 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 '#client/constants';
3+
import { BOUNDARY_EFFECT, EFFECT_PRESERVED, EFFECT_TRANSPARENT } from '#client/constants';
44
import { component_context, set_component_context } from '../../context.js';
55
import { invoke_error_boundary } from '../../error-handling.js';
66
import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js';
@@ -18,119 +18,197 @@ import {
1818
remove_nodes,
1919
set_hydrate_node
2020
} from '../hydration.js';
21+
import { get_next_sibling } from '../operations.js';
2122
import { queue_micro_task } from '../task.js';
2223

2324
/**
24-
* @param {Effect} boundary
25-
* @param {() => void} fn
25+
* @typedef {{
26+
* onerror?: (error: unknown, reset: () => void) => void;
27+
* failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void;
28+
* }} BoundaryProps
2629
*/
27-
function with_boundary(boundary, fn) {
28-
var previous_effect = active_effect;
29-
var previous_reaction = active_reaction;
30-
var previous_ctx = component_context;
31-
32-
set_active_effect(boundary);
33-
set_active_reaction(boundary);
34-
set_component_context(boundary.ctx);
35-
36-
try {
37-
fn();
38-
} finally {
39-
set_active_effect(previous_effect);
40-
set_active_reaction(previous_reaction);
41-
set_component_context(previous_ctx);
42-
}
43-
}
30+
31+
var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED | BOUNDARY_EFFECT;
4432

4533
/**
4634
* @param {TemplateNode} node
47-
* @param {{
48-
* onerror?: (error: unknown, reset: () => void) => void,
49-
* failed?: (anchor: Node, error: () => unknown, reset: () => () => void) => void
50-
* }} props
51-
* @param {((anchor: Node) => void)} boundary_fn
35+
* @param {BoundaryProps} props
36+
* @param {((anchor: Node) => void)} children
5237
* @returns {void}
5338
*/
54-
export function boundary(node, props, boundary_fn) {
55-
var anchor = node;
39+
export function boundary(node, props, children) {
40+
new Boundary(node, props, children);
41+
}
42+
43+
export class Boundary {
44+
/** @type {Boundary | null} */
45+
parent;
46+
47+
/** @type {TemplateNode} */
48+
#anchor;
49+
50+
/** @type {TemplateNode} */
51+
#hydrate_open;
52+
53+
/** @type {BoundaryProps} */
54+
#props;
55+
56+
/** @type {((anchor: Node) => void)} */
57+
#children;
5658

5759
/** @type {Effect} */
58-
var boundary_effect;
59-
60-
block(() => {
61-
var boundary = /** @type {Effect} */ (active_effect);
62-
var hydrate_open = hydrate_node;
63-
var is_creating_fallback = false;
64-
65-
// We re-use the effect's fn property to avoid allocation of an additional field
66-
boundary.fn = (/** @type {unknown}} */ error) => {
67-
var onerror = props.onerror;
68-
let failed = props.failed;
69-
70-
// If we have nothing to capture the error, or if we hit an error while
71-
// rendering the fallback, re-throw for another boundary to handle
72-
if ((!onerror && !failed) || is_creating_fallback) {
73-
throw error;
74-
}
60+
#effect;
7561

76-
var reset = () => {
77-
pause_effect(boundary_effect);
62+
/** @type {Effect | null} */
63+
#main_effect = null;
7864

79-
with_boundary(boundary, () => {
80-
is_creating_fallback = false;
81-
boundary_effect = branch(() => boundary_fn(anchor));
82-
});
83-
};
65+
/** @type {Effect | null} */
66+
#failed_effect = null;
8467

85-
var previous_reaction = active_reaction;
68+
#is_creating_fallback = false;
8669

87-
try {
88-
set_active_reaction(null);
89-
onerror?.(error, reset);
90-
} finally {
91-
set_active_reaction(previous_reaction);
70+
/**
71+
* @param {TemplateNode} node
72+
* @param {BoundaryProps} props
73+
* @param {((anchor: Node) => void)} children
74+
*/
75+
constructor(node, props, children) {
76+
this.#anchor = node;
77+
this.#props = props;
78+
this.#children = children;
79+
80+
this.#hydrate_open = hydrate_node;
81+
82+
this.parent = /** @type {Effect} */ (active_effect).b;
83+
84+
this.#effect = block(() => {
85+
/** @type {Effect} */ (active_effect).b = this;
86+
87+
if (hydrating) {
88+
hydrate_next();
9289
}
9390

94-
if (boundary_effect) {
95-
destroy_effect(boundary_effect);
96-
} else if (hydrating) {
97-
set_hydrate_node(hydrate_open);
98-
next();
99-
set_hydrate_node(remove_nodes());
91+
try {
92+
this.#main_effect = branch(() => children(this.#anchor));
93+
} catch (error) {
94+
this.error(error);
10095
}
96+
}, flags);
10197

102-
if (failed) {
103-
// Render the `failed` snippet in a microtask
104-
queue_micro_task(() => {
105-
with_boundary(boundary, () => {
106-
is_creating_fallback = true;
107-
108-
try {
109-
boundary_effect = branch(() => {
110-
failed(
111-
anchor,
112-
() => error,
113-
() => reset
114-
);
115-
});
116-
} catch (error) {
117-
invoke_error_boundary(error, /** @type {Effect} */ (boundary.parent));
118-
}
119-
120-
is_creating_fallback = false;
121-
});
98+
if (hydrating) {
99+
this.#anchor = hydrate_node;
100+
}
101+
}
102+
103+
/**
104+
* @param {() => Effect | null} fn
105+
*/
106+
#run(fn) {
107+
var previous_effect = active_effect;
108+
var previous_reaction = active_reaction;
109+
var previous_ctx = component_context;
110+
111+
set_active_effect(this.#effect);
112+
set_active_reaction(this.#effect);
113+
set_component_context(this.#effect.ctx);
114+
115+
try {
116+
return fn();
117+
} finally {
118+
set_active_effect(previous_effect);
119+
set_active_reaction(previous_reaction);
120+
set_component_context(previous_ctx);
121+
}
122+
}
123+
124+
/** @param {unknown} error */
125+
error(error) {
126+
var onerror = this.#props.onerror;
127+
let failed = this.#props.failed;
128+
129+
const reset = () => {
130+
if (this.#failed_effect !== null) {
131+
pause_effect(this.#failed_effect, () => {
132+
this.#failed_effect = null;
122133
});
123134
}
135+
136+
this.#main_effect = this.#run(() => {
137+
this.#is_creating_fallback = false;
138+
return branch(() => this.#children(this.#anchor));
139+
});
124140
};
125141

142+
// If we have nothing to capture the error, or if we hit an error while
143+
// rendering the fallback, re-throw for another boundary to handle
144+
if (this.#is_creating_fallback || (!onerror && !failed)) {
145+
throw error;
146+
}
147+
148+
var previous_reaction = active_reaction;
149+
150+
try {
151+
set_active_reaction(null);
152+
onerror?.(error, reset);
153+
} finally {
154+
set_active_reaction(previous_reaction);
155+
}
156+
157+
if (this.#main_effect) {
158+
destroy_effect(this.#main_effect);
159+
this.#main_effect = null;
160+
}
161+
162+
if (this.#failed_effect) {
163+
destroy_effect(this.#failed_effect);
164+
this.#failed_effect = null;
165+
}
166+
126167
if (hydrating) {
127-
hydrate_next();
168+
set_hydrate_node(this.#hydrate_open);
169+
next();
170+
set_hydrate_node(remove_nodes());
171+
}
172+
173+
if (failed) {
174+
queue_micro_task(() => {
175+
this.#failed_effect = this.#run(() => {
176+
this.#is_creating_fallback = true;
177+
178+
try {
179+
return branch(() => {
180+
failed(
181+
this.#anchor,
182+
() => error,
183+
() => reset
184+
);
185+
});
186+
} catch (error) {
187+
invoke_error_boundary(error, /** @type {Effect} */ (this.#effect.parent));
188+
return null;
189+
} finally {
190+
this.#is_creating_fallback = false;
191+
}
192+
});
193+
});
128194
}
195+
}
196+
}
197+
198+
/**
199+
*
200+
* @param {Effect} effect
201+
* @param {DocumentFragment} fragment
202+
*/
203+
function move_effect(effect, fragment) {
204+
var node = effect.nodes_start;
205+
var end = effect.nodes_end;
129206

130-
boundary_effect = branch(() => boundary_fn(anchor));
131-
}, EFFECT_TRANSPARENT | BOUNDARY_EFFECT);
207+
while (node !== null) {
208+
/** @type {TemplateNode | null} */
209+
var next = node === end ? null : /** @type {TemplateNode} */ (get_next_sibling(node));
132210

133-
if (hydrating) {
134-
anchor = hydrate_node;
211+
fragment.append(node);
212+
node = next;
135213
}
136214
}

0 commit comments

Comments
 (0)