From 1773cb5b593cb650b109f38113b597cf9191714c Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:46:36 +0100 Subject: [PATCH] fix: ensure HMR wrapper forwards correct start/end nodes to active effect (#17985) Fixes #17982 (two issues reported there) Adding the start/end statically at the end once does not work because it's going to be stale when there's a HMR reload of the wrapped component. For reasons not completely clear to me it also fails in another case. So instead of wrapping the HMR with comments we just forward the nodes of the inner effect to the outer active effect, pretending the wrapper isn't there from a "remove dom nodes"-perspective. --- .changeset/better-pugs-play.md | 5 ++++ .../svelte/src/internal/client/dev/hmr.js | 29 ++++--------------- 2 files changed, 11 insertions(+), 23 deletions(-) create mode 100644 .changeset/better-pugs-play.md diff --git a/.changeset/better-pugs-play.md b/.changeset/better-pugs-play.md new file mode 100644 index 0000000000..d1b80a30ce --- /dev/null +++ b/.changeset/better-pugs-play.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: ensure HMR wrapper forwards correct start/end nodes to active effect diff --git a/packages/svelte/src/internal/client/dev/hmr.js b/packages/svelte/src/internal/client/dev/hmr.js index 4687708c90..13ee35b20d 100644 --- a/packages/svelte/src/internal/client/dev/hmr.js +++ b/packages/svelte/src/internal/client/dev/hmr.js @@ -5,9 +5,7 @@ import { hydrate_node, hydrating } from '../dom/hydration.js'; import { block, branch, destroy_effect } from '../reactivity/effects.js'; import { set, source } from '../reactivity/sources.js'; import { set_should_intro } from '../render.js'; -import { get } from '../runtime.js'; -import { assign_nodes } from '../dom/template.js'; -import { create_comment } from '../dom/operations.js'; +import { active_effect, get } from '../runtime.js'; /** * @template {(anchor: Comment, props: any) => any} Component @@ -29,19 +27,6 @@ export function hmr(fn) { let ran = false; - // Surround the wrapped effects with comments and assign the nodes - // on the wrapping effects so the parent can properly do DOM operations. - let start = create_comment(); - let end = create_comment(); - - // During hydration, inserting the start comment before the anchor could - // corrupt the DOM tree that the hydration walker is navigating (e.g. when - // a component is inside a CSS props wrapper gh-issue#17972). We defer the insertion until - // after the component has hydrated. - if (!hydrating) { - anchor.before(start); - } - block(() => { if (component === (component = get(current))) { return; @@ -68,21 +53,19 @@ export function hmr(fn) { if (ran) set_should_intro(true); }); + + // Forward the nodes from the inner effect to the outer active effect which would + // get them if the HMR wrapper wasn't there. Do this inside the block not outside + // so that HMR updates to the component will also update the nodes on the active effect. + /** @type {Effect} */ (active_effect).nodes = effect.nodes; }, EFFECT_TRANSPARENT); ran = true; if (hydrating) { - // Insert start comment now that hydration is done, so it doesn't - // corrupt the hydration walk - anchor.before(start); anchor = hydrate_node; } - anchor.before(end); - - assign_nodes(start, end); - return instance; }