From c0cdc5a6a32b1c5a1ff48a555ee153a0511c1454 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 5 Jun 2025 18:38:13 -0400 Subject: [PATCH] move error handling code into separate module --- .../internal/client/dom/blocks/boundary.js | 5 +- .../src/internal/client/error-handling.js | 138 ++++++++++++++++++ .../svelte/src/internal/client/runtime.js | 135 +---------------- 3 files changed, 142 insertions(+), 136 deletions(-) create mode 100644 packages/svelte/src/internal/client/error-handling.js diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 2e94a86806..0eb00bf630 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -2,14 +2,13 @@ import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '#client/constants'; import { component_context, set_component_context } from '../../context.js'; +import { handle_error, reset_is_throwing_error } from '../../error-handling.js'; import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js'; import { active_effect, active_reaction, - handle_error, set_active_effect, - set_active_reaction, - reset_is_throwing_error + set_active_reaction } from '../../runtime.js'; import { hydrate_next, diff --git a/packages/svelte/src/internal/client/error-handling.js b/packages/svelte/src/internal/client/error-handling.js new file mode 100644 index 0000000000..3b59481b0e --- /dev/null +++ b/packages/svelte/src/internal/client/error-handling.js @@ -0,0 +1,138 @@ +/** @import { ComponentContext, Effect } from '#client' */ +import { DEV } from 'esm-env'; +import { FILENAME } from '../../constants.js'; +import { is_firefox } from './dom/operations.js'; +import { BOUNDARY_EFFECT, DESTROYED } from './constants.js'; +import { define_property } from '../shared/utils.js'; + +// Used for DEV time error handling +/** @param {WeakSet} value */ +const handled_errors = new WeakSet(); + +let is_throwing_error = false; + +/** + * @param {unknown} error + * @param {Effect} effect + * @param {Effect | null} [previous_effect] + */ +export function handle_error(error, effect, previous_effect = null) { + var component_context = effect.ctx; + + if (is_throwing_error) { + if (previous_effect === null) { + is_throwing_error = false; + } + + if (should_rethrow_error(effect)) { + throw error; + } + + return; + } + + if (previous_effect !== null) { + is_throwing_error = true; + } + + if (DEV && component_context !== null && error instanceof Error && !handled_errors.has(error)) { + handled_errors.add(error); + + const component_stack = []; + + const effect_name = effect.fn?.name; + + if (effect_name) { + component_stack.push(effect_name); + } + + /** @type {ComponentContext | null} */ + let current_context = component_context; + + while (current_context !== null) { + /** @type {string} */ + var filename = current_context.function?.[FILENAME]; + + if (filename) { + const file = filename.split('/').pop(); + component_stack.push(file); + } + + current_context = current_context.p; + } + + const indent = is_firefox ? ' ' : '\t'; + define_property(error, 'message', { + value: + error.message + `\n${component_stack.map((name) => `\n${indent}in ${name}`).join('')}\n` + }); + define_property(error, 'component_stack', { + value: component_stack + }); + + const stack = error.stack; + + // Filter out internal files from callstack + if (stack) { + const lines = stack.split('\n'); + const new_lines = []; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (line.includes('svelte/src/internal')) { + continue; + } + new_lines.push(line); + } + define_property(error, 'stack', { + value: new_lines.join('\n') + }); + } + } + + propagate_error(error, effect); + + if (should_rethrow_error(effect)) { + throw error; + } +} + +/** + * @param {unknown} error + * @param {Effect} effect + */ +function propagate_error(error, effect) { + /** @type {Effect | null} */ + var current = effect; + + while (current !== null) { + if ((current.f & BOUNDARY_EFFECT) !== 0) { + try { + // @ts-expect-error + current.fn(error); + return; + } catch { + // Remove boundary flag from effect + current.f ^= BOUNDARY_EFFECT; + } + } + + current = current.parent; + } + + is_throwing_error = false; + throw error; +} + +/** + * @param {Effect} effect + */ +function should_rethrow_error(effect) { + return ( + (effect.f & DESTROYED) === 0 && + (effect.parent === null || (effect.parent.f & BOUNDARY_EFFECT) === 0) + ); +} + +export function reset_is_throwing_error() { + is_throwing_error = false; +} diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 2324c72593..2ac3125a0f 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -29,7 +29,7 @@ import { flush_tasks } from './dom/task.js'; import { internal_set, old_values } from './reactivity/sources.js'; import { destroy_derived_effects, update_derived } from './reactivity/deriveds.js'; import * as e from './errors.js'; -import { FILENAME } from '../../constants.js'; + import { tracing_mode_flag } from '../flags/index.js'; import { tracing_expressions, get_stack } from './dev/tracing.js'; import { @@ -39,12 +39,7 @@ import { set_component_context, set_dev_current_component_function } from './context.js'; -import { is_firefox } from './dom/operations.js'; - -// Used for DEV time error handling -/** @param {WeakSet} value */ -const handled_errors = new WeakSet(); -let is_throwing_error = false; +import { handle_error } from './error-handling.js'; let is_flushing = false; @@ -227,132 +222,6 @@ export function check_dirtiness(reaction) { return false; } -/** - * @param {unknown} error - * @param {Effect} effect - */ -function propagate_error(error, effect) { - /** @type {Effect | null} */ - var current = effect; - - while (current !== null) { - if ((current.f & BOUNDARY_EFFECT) !== 0) { - try { - // @ts-expect-error - current.fn(error); - return; - } catch { - // Remove boundary flag from effect - current.f ^= BOUNDARY_EFFECT; - } - } - - current = current.parent; - } - - is_throwing_error = false; - throw error; -} - -/** - * @param {Effect} effect - */ -function should_rethrow_error(effect) { - return ( - (effect.f & DESTROYED) === 0 && - (effect.parent === null || (effect.parent.f & BOUNDARY_EFFECT) === 0) - ); -} - -export function reset_is_throwing_error() { - is_throwing_error = false; -} - -/** - * @param {unknown} error - * @param {Effect} effect - * @param {Effect | null} [previous_effect] - */ -export function handle_error(error, effect, previous_effect = null) { - var component_context = effect.ctx; - - if (is_throwing_error) { - if (previous_effect === null) { - is_throwing_error = false; - } - - if (should_rethrow_error(effect)) { - throw error; - } - - return; - } - - if (previous_effect !== null) { - is_throwing_error = true; - } - - if (DEV && component_context !== null && error instanceof Error && !handled_errors.has(error)) { - handled_errors.add(error); - - const component_stack = []; - - const effect_name = effect.fn?.name; - - if (effect_name) { - component_stack.push(effect_name); - } - - /** @type {ComponentContext | null} */ - let current_context = component_context; - - while (current_context !== null) { - /** @type {string} */ - var filename = current_context.function?.[FILENAME]; - - if (filename) { - const file = filename.split('/').pop(); - component_stack.push(file); - } - - current_context = current_context.p; - } - - const indent = is_firefox ? ' ' : '\t'; - define_property(error, 'message', { - value: - error.message + `\n${component_stack.map((name) => `\n${indent}in ${name}`).join('')}\n` - }); - define_property(error, 'component_stack', { - value: component_stack - }); - - const stack = error.stack; - - // Filter out internal files from callstack - if (stack) { - const lines = stack.split('\n'); - const new_lines = []; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (line.includes('svelte/src/internal')) { - continue; - } - new_lines.push(line); - } - define_property(error, 'stack', { - value: new_lines.join('\n') - }); - } - } - - propagate_error(error, effect); - - if (should_rethrow_error(effect)) { - throw error; - } -} - /** * @param {Value} signal * @param {Effect} effect