Skip to content

Commit 84e0790

Browse files
committed
merge main
2 parents 6cd7ef9 + 2d2772a commit 84e0790

File tree

13 files changed

+184
-223
lines changed

13 files changed

+184
-223
lines changed

.changeset/early-tips-prove.md

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+
fix: invoke parent boundary of deriveds that throw

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

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22

33
import { BOUNDARY_EFFECT, EFFECT_PRESERVED, EFFECT_TRANSPARENT } from '#client/constants';
44
import { component_context, set_component_context } from '../../context.js';
5+
import { invoke_error_boundary } from '../../error-handling.js';
56
import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js';
67
import {
78
active_effect,
89
active_reaction,
9-
handle_error,
1010
set_active_effect,
11-
set_active_reaction,
12-
reset_is_throwing_error
11+
set_active_reaction
1312
} from '../../runtime.js';
1413
import {
1514
hydrate_next,
@@ -129,16 +128,18 @@ export class Boundary {
129128
}
130129
});
131130
} else {
132-
this.#main_effect = branch(() => children(this.#anchor));
131+
try {
132+
this.#main_effect = branch(() => children(this.#anchor));
133+
} catch (error) {
134+
this.error(error);
135+
}
133136

134137
if (this.#pending_count > 0) {
135138
this.#show_pending_snippet();
136139
} else {
137140
this.ran = true;
138141
}
139142
}
140-
141-
reset_is_throwing_error();
142143
}, flags);
143144

144145
if (hydrating) {
@@ -239,12 +240,7 @@ export class Boundary {
239240

240241
this.#main_effect = this.#run(() => {
241242
this.#is_creating_fallback = false;
242-
243-
try {
244-
return branch(() => this.#children(this.#anchor));
245-
} finally {
246-
reset_is_throwing_error();
247-
}
243+
return branch(() => this.#children(this.#anchor));
248244
});
249245

250246
if (this.#pending_count > 0) {
@@ -260,7 +256,14 @@ export class Boundary {
260256
throw error;
261257
}
262258

263-
onerror?.(error, reset);
259+
var previous_reaction = active_reaction;
260+
261+
try {
262+
set_active_reaction(null);
263+
onerror?.(error, reset);
264+
} finally {
265+
set_active_reaction(previous_reaction);
266+
}
264267

265268
if (this.#main_effect) {
266269
destroy_effect(this.#main_effect);
@@ -297,10 +300,9 @@ export class Boundary {
297300
);
298301
});
299302
} catch (error) {
300-
handle_error(error, this.#effect, null, this.#effect.ctx);
303+
invoke_error_boundary(error, /** @type {Effect} */ (this.#effect.parent));
301304
return null;
302305
} finally {
303-
reset_is_throwing_error();
304306
this.#is_creating_fallback = false;
305307
}
306308
});
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/** @import { Effect } from '#client' */
2+
/** @import { Boundary } from './dom/blocks/boundary.js' */
3+
import { DEV } from 'esm-env';
4+
import { FILENAME } from '../../constants.js';
5+
import { is_firefox } from './dom/operations.js';
6+
import { BOUNDARY_EFFECT, EFFECT_RAN } from './constants.js';
7+
import { define_property } from '../shared/utils.js';
8+
import { active_effect } from './runtime.js';
9+
10+
/**
11+
* @param {unknown} error
12+
*/
13+
export function handle_error(error) {
14+
var effect = /** @type {Effect} */ (active_effect);
15+
16+
if (DEV && error instanceof Error) {
17+
// adjust_error(error, effect);
18+
}
19+
20+
if ((effect.f & EFFECT_RAN) === 0) {
21+
// if the error occurred while creating this subtree, we let it
22+
// bubble up until it hits a boundary that can handle it
23+
if ((effect.f & BOUNDARY_EFFECT) === 0) {
24+
throw error;
25+
}
26+
27+
// @ts-expect-error
28+
effect.fn(error);
29+
} else {
30+
// otherwise we bubble up the effect tree ourselves
31+
invoke_error_boundary(error, effect);
32+
}
33+
}
34+
35+
/**
36+
* @param {unknown} error
37+
* @param {Effect | null} effect
38+
*/
39+
export function invoke_error_boundary(error, effect) {
40+
while (effect !== null) {
41+
if ((effect.f & BOUNDARY_EFFECT) !== 0) {
42+
try {
43+
/** @type {Boundary} */ (effect.b).error(error);
44+
return;
45+
} catch {}
46+
}
47+
48+
effect = effect.parent;
49+
}
50+
51+
throw error;
52+
}
53+
54+
/** @type {WeakSet<Error>} */
55+
const adjusted_errors = new WeakSet();
56+
57+
/**
58+
* Add useful information to the error message/stack in development
59+
* @param {Error} error
60+
* @param {Effect} effect
61+
*/
62+
function adjust_error(error, effect) {
63+
if (adjusted_errors.has(error)) return;
64+
adjusted_errors.add(error);
65+
66+
var indent = is_firefox ? ' ' : '\t';
67+
var component_stack = `\n${indent}in ${effect.fn?.name || '<unknown>'}`;
68+
var context = effect.ctx;
69+
70+
while (context !== null) {
71+
component_stack += `\n${indent}in ${context.function?.[FILENAME].split('/').pop()}`;
72+
context = context.p;
73+
}
74+
75+
define_property(error, 'message', {
76+
value: error.message + `\n${component_stack}\n`
77+
});
78+
79+
if (error.stack) {
80+
// Filter out internal modules
81+
define_property(error, 'stack', {
82+
value: error.stack
83+
.split('\n')
84+
.filter((line) => !line.includes('svelte/src/internal'))
85+
.join('\n')
86+
});
87+
}
88+
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {
2020
update_reaction,
2121
increment_write_version,
2222
set_active_effect,
23-
handle_error,
2423
push_reaction_value,
2524
is_destroying_effect
2625
} from '../runtime.js';
@@ -35,6 +34,7 @@ import { get_pending_boundary } from '../dom/blocks/boundary.js';
3534
import { component_context } from '../context.js';
3635
import { UNINITIALIZED } from '../../../constants.js';
3736
import { current_batch } from './batch.js';
37+
import { handle_error } from '../error-handling.js';
3838

3939
/** @type {Effect | null} */
4040
export let from_async_derived = null;
@@ -157,8 +157,16 @@ export function async_derived(fn, location) {
157157
if (ran) batch.restore();
158158

159159
if (error) {
160+
// TODO instead of handling error here, mark the signal as bad and let it happen when it is read
160161
if (error !== STALE_REACTION) {
161-
handle_error(error, parent, null, parent.ctx);
162+
var previous_effect = active_effect;
163+
164+
try {
165+
set_active_effect(parent);
166+
handle_error(error);
167+
} finally {
168+
set_active_effect(previous_effect);
169+
}
162170
}
163171
} else {
164172
internal_set(signal, value);

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -361,13 +361,6 @@ export function template_effect(fn, sync = [], async = [], d = derived) {
361361
*/
362362
function create_template_effect(fn, deriveds) {
363363
var effect = () => fn(...deriveds.map(get));
364-
365-
if (DEV) {
366-
define_property(effect, 'name', {
367-
value: '{expression}'
368-
});
369-
}
370-
371364
create_effect(RENDER_EFFECT, effect, true);
372365
}
373366

@@ -455,7 +448,11 @@ export function destroy_block_effect_children(signal) {
455448
export function destroy_effect(effect, remove_dom = true) {
456449
var removed = false;
457450

458-
if ((remove_dom || (effect.f & HEAD_EFFECT) !== 0) && effect.nodes_start !== null) {
451+
if (
452+
(remove_dom || (effect.f & HEAD_EFFECT) !== 0) &&
453+
effect.nodes_start !== null &&
454+
effect.nodes_end !== null
455+
) {
459456
remove_effect_dom(effect.nodes_start, /** @type {TemplateNode} */ (effect.nodes_end));
460457
removed = true;
461458
}

0 commit comments

Comments
 (0)