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 <simon.holthausen@vercel.com>
pull/18537/merge
Sander Machado 4 days ago committed by GitHub
parent ee1249b43c
commit a4c60ccdbb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: reliably resolve append_style to its correct root

@ -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

@ -0,0 +1,24 @@
import { assert_ok, test } from '../../assert';
const tick = () => Promise.resolve();
export default test({
async test({ assert, target }) {
target.innerHTML = '<my-app></my-app>';
// 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)');
}
});

@ -0,0 +1,18 @@
<svelte:options customElement="my-app" />
<script>
import { onMount } from 'svelte';
import Child from './Child.svelte';
let items = $state([]);
// Add the item _after_ the initial mount, so that the each block renders the
// new item into an offscreen anchor that is discarded once it's committed to
// the DOM. This reproduces styles being injected into `document.head` instead
// of the shadow root (https://github.com/sveltejs/svelte/issues/18288)
onMount(() => {
items = [1];
});
</script>
{#each items as item (item)}<Child />{/each}
Loading…
Cancel
Save