From a4c60ccdbb4b68469444af216bdf5f565ddc2916 Mon Sep 17 00:00:00 2001 From: Sander Machado Date: Thu, 20 Aug 2026 14:48:02 +0200 Subject: [PATCH] fix: append_styles resolving to `document.head` in WC (#18614) Fixes #18288 Check the surrounding branch effect's start node to retrieve the correct root node instead of just the anchor, since the latter could come from each.js/branch.js and be a text node that is never going to get connected --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Simon Holthausen --- .changeset/free-signs-ask.md | 5 ++++ .../svelte/src/internal/client/dom/css.js | 7 +++++- .../deferred-nested-styles/Child.svelte | 7 ++++++ .../deferred-nested-styles/_config.js | 24 +++++++++++++++++++ .../deferred-nested-styles/main.svelte | 18 ++++++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 .changeset/free-signs-ask.md create mode 100644 packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/Child.svelte create mode 100644 packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/_config.js create mode 100644 packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/main.svelte diff --git a/.changeset/free-signs-ask.md b/.changeset/free-signs-ask.md new file mode 100644 index 0000000000..7e6c925526 --- /dev/null +++ b/.changeset/free-signs-ask.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: reliably resolve append_style to its correct root diff --git a/packages/svelte/src/internal/client/dom/css.js b/packages/svelte/src/internal/client/dom/css.js index 74bf2d49f9..23f4eff4f7 100644 --- a/packages/svelte/src/internal/client/dom/css.js +++ b/packages/svelte/src/internal/client/dom/css.js @@ -2,14 +2,19 @@ import { DEV } from 'esm-env'; import { register_style } from '../dev/css.js'; import { effect } from '../reactivity/effects.js'; import { create_element } from './operations.js'; +import { active_effect } from '../runtime.js'; /** * @param {Node} anchor * @param {{ hash: string, code: string }} css */ export function append_styles(anchor, css) { - // Use `queue_micro_task` to ensure `anchor` is in the DOM, otherwise getRootNode() will yield wrong results + // Use an effect to ensure `anchor` is in the DOM, otherwise getRootNode() will yield wrong results effect(() => { + // Bit of a hack: branches.js/each.js use offscreen fragments with temporary text nodes that will + // never be connected to the real dom. Therfore walk up to the branch that has created the component + // whose styles we want to append, and check its node instead. It will be connected by the time we get here. + anchor = active_effect?.parent?.nodes?.start ?? anchor; var root = anchor.getRootNode(); var target = /** @type {ShadowRoot} */ (root).host diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/Child.svelte b/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/Child.svelte new file mode 100644 index 0000000000..86064ab3b1 --- /dev/null +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/Child.svelte @@ -0,0 +1,7 @@ +

child

+ + diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/_config.js b/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/_config.js new file mode 100644 index 0000000000..739921ebf5 --- /dev/null +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/_config.js @@ -0,0 +1,24 @@ +import { assert_ok, test } from '../../assert'; + +const tick = () => Promise.resolve(); + +export default test({ + async test({ assert, target }) { + target.innerHTML = ''; + + // wait for the initial mount, the `onMount` reveal and the deferred re-render + await tick(); + await tick(); + await tick(); + await tick(); + + /** @type {any} */ + const el = target.querySelector('my-app'); + const p = el.shadowRoot.querySelector('p'); + assert_ok(p); + + // The child's scoped styles must be injected into the shadow root, not `document.head` + assert_ok(el.shadowRoot.querySelector('style')); + assert.equal(getComputedStyle(p).color, 'rgb(255, 0, 0)'); + } +}); diff --git a/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/main.svelte b/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/main.svelte new file mode 100644 index 0000000000..2bd0456fc1 --- /dev/null +++ b/packages/svelte/tests/runtime-browser/custom-elements-samples/deferred-nested-styles/main.svelte @@ -0,0 +1,18 @@ + + + + +{#each items as item (item)}{/each}