From 83cf3df024a47dc318da40eb878261c9dec94def Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 18 Nov 2025 11:19:51 +0100 Subject: [PATCH 1/3] perf: don't use tracing overeager during dev #17176 is a case where many sources are created and then written to (due to Svelte 4 prop mechanics), and our tracing kicked in eagerly. That combined with the excessive depth of the related stack traces slowed things down tremendously. The fix is simple: Don't record stack traces until we've seen this source get updated for a couple of times. Additionally we now delete the `updates` map after a flush. Previously it was just an ever-growing stack trace map. --- .changeset/salty-hounds-worry.md | 5 ++++ .../svelte/src/compiler/phases/types.d.ts | 1 + .../src/internal/client/reactivity/batch.js | 14 ++++++++++ .../src/internal/client/reactivity/sources.js | 26 ++++++++++++------- packages/svelte/src/internal/flags/index.js | 3 +++ 5 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 .changeset/salty-hounds-worry.md diff --git a/.changeset/salty-hounds-worry.md b/.changeset/salty-hounds-worry.md new file mode 100644 index 000000000000..82a887450c4c --- /dev/null +++ b/.changeset/salty-hounds-worry.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +perf: don't use tracing overeager during dev diff --git a/packages/svelte/src/compiler/phases/types.d.ts b/packages/svelte/src/compiler/phases/types.d.ts index 71f77b1a1165..5397ea45f922 100644 --- a/packages/svelte/src/compiler/phases/types.d.ts +++ b/packages/svelte/src/compiler/phases/types.d.ts @@ -53,6 +53,7 @@ export interface Analysis { /** @deprecated use `runes` from `state.js` instead */ runes: boolean; immutable: boolean; + /** True if `$inspect.trace` is used */ tracing: boolean; comments: AST.JSComment[]; diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index c7e01fbcba1b..e7229c28b208 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -608,6 +608,8 @@ function flush_effects() { var was_updating_effect = is_updating_effect; is_flushing = true; + var source_stacks = DEV ? new Set() : null; + try { var flush_count = 0; set_is_updating_effect(true); @@ -643,12 +645,24 @@ function flush_effects() { batch.process(queued_root_effects); old_values.clear(); + + if (DEV) { + for (const source of batch.current.keys()) { + /** @type {Set} */ (source_stacks).add(source); + } + } } } finally { is_flushing = false; set_is_updating_effect(was_updating_effect); last_scheduled_effect = null; + + if (DEV) { + for (const source of /** @type {Set} */ (source_stacks)) { + source.updated = null; + } + } } } diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 052ca97dc071..9f3ba0a05326 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -188,18 +188,26 @@ export function internal_set(source, value) { if (DEV) { if (tracing_mode_flag || active_effect !== null) { - const error = get_stack('updated at'); + source.updated ??= new Map(); - if (error !== null) { - source.updated ??= new Map(); - let entry = source.updated.get(error.stack); + // For performance reasons, when not using $inspect.trace, we only start collecting stack traces + // after the same source has been updated more than 5 times in the same flush cycle. + const count = source.updated.get('')?.count ?? 0; + source.updated.set('', { error: /** @type {any} */ (null), count: count + 1 }); - if (!entry) { - entry = { error, count: 0 }; - source.updated.set(error.stack, entry); - } + if (tracing_mode_flag || count > 5) { + const error = get_stack('updated at'); + + if (error !== null) { + let entry = source.updated.get(error.stack); - entry.count++; + if (!entry) { + entry = { error, count: 0 }; + source.updated.set(error.stack, entry); + } + + entry.count++; + } } } diff --git a/packages/svelte/src/internal/flags/index.js b/packages/svelte/src/internal/flags/index.js index ce7bba604bff..5d4054975f62 100644 --- a/packages/svelte/src/internal/flags/index.js +++ b/packages/svelte/src/internal/flags/index.js @@ -1,5 +1,8 @@ +/** True if experimental.async=true */ export let async_mode_flag = false; +/** True if we're not certain that we only have Svelte 5 code in the compilation */ export let legacy_mode_flag = false; +/** True if $inspect.trace is used */ export let tracing_mode_flag = false; export function enable_async_mode_flag() { From 8ca7269af1ac4db77551e8d3f3f6039d552d5c02 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 18 Nov 2025 11:28:03 +0100 Subject: [PATCH 2/3] fix --- packages/svelte/src/internal/client/dev/tracing.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/internal/client/dev/tracing.js b/packages/svelte/src/internal/client/dev/tracing.js index 4688637f5d87..183da734473b 100644 --- a/packages/svelte/src/internal/client/dev/tracing.js +++ b/packages/svelte/src/internal/client/dev/tracing.js @@ -57,8 +57,10 @@ function log_entry(signal, entry) { if (dirty && signal.updated) { for (const updated of signal.updated.values()) { - // eslint-disable-next-line no-console - console.log(updated.error); + if (updated.error) { + // eslint-disable-next-line no-console + console.log(updated.error); + } } } From efb49d2c5201d33a7bbc09830d69ce115efb194d Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Tue, 18 Nov 2025 11:44:59 +0100 Subject: [PATCH 3/3] fix --- packages/svelte/src/internal/client/reactivity/batch.js | 6 ++++-- packages/svelte/src/internal/client/reactivity/sources.js | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index e7229c28b208..22526df7c1f2 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -635,8 +635,10 @@ function flush_effects() { } for (const update of updates.values()) { - // eslint-disable-next-line no-console - console.error(update.error); + if (update.error) { + // eslint-disable-next-line no-console + console.error(update.error); + } } } diff --git a/packages/svelte/src/internal/client/reactivity/sources.js b/packages/svelte/src/internal/client/reactivity/sources.js index 9f3ba0a05326..822fb218229a 100644 --- a/packages/svelte/src/internal/client/reactivity/sources.js +++ b/packages/svelte/src/internal/client/reactivity/sources.js @@ -192,8 +192,8 @@ export function internal_set(source, value) { // For performance reasons, when not using $inspect.trace, we only start collecting stack traces // after the same source has been updated more than 5 times in the same flush cycle. - const count = source.updated.get('')?.count ?? 0; - source.updated.set('', { error: /** @type {any} */ (null), count: count + 1 }); + const count = (source.updated.get('')?.count ?? 0) + 1; + source.updated.set('', { error: /** @type {any} */ (null), count }); if (tracing_mode_flag || count > 5) { const error = get_stack('updated at');