Skip to content

Commit d338e80

Browse files
authored
fix: simplify and robustify appending styles (#13303)
#13225 did not fully fix the described issue: As soon as you have a child component, that child component's anchor will not be in the right ownerDocument yet, and so the logic falls down. To fix that, we would need to move the ownerDocument check into the microtask - and that map check was mainly done to avoid just that. So instead I opted to simplify the code and just remove the map checks. According to my benchmarking (https://jsbench.me/3hm17l0bxl/1) this has no impact on performance assuming that you'll have cache hits roughly half the time - and even if you'd have much more cache hits, the performance loss is microscopic. Given that the default mode is to not inject styles, this will not be common anyway, so I favor removing that map.
1 parent f852a68 commit d338e80

File tree

6 files changed

+29
-24
lines changed

6 files changed

+29
-24
lines changed

.changeset/silent-elephants-film.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: simplify and robustify appending styles

packages/svelte/src/compiler/phases/3-transform/client/transform-client.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,9 +386,7 @@ export function client_component(analysis, options) {
386386
state.hoisted.push(b.const('$$css', b.object([b.init('hash', hash), b.init('code', code)])));
387387

388388
component_block.body.unshift(
389-
b.stmt(
390-
b.call('$.append_styles', b.id('$$anchor'), b.id('$$css'), options.customElement && b.true)
391-
)
389+
b.stmt(b.call('$.append_styles', b.id('$$anchor'), b.id('$$css')))
392390
);
393391
}
394392

packages/svelte/src/internal/client/dom/css.js

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,11 @@ import { DEV } from 'esm-env';
22
import { queue_micro_task } from './task.js';
33
import { register_style } from '../dev/css.js';
44

5-
var roots = new WeakMap();
6-
75
/**
86
* @param {Node} anchor
97
* @param {{ hash: string, code: string }} css
10-
* @param {boolean} [is_custom_element]
118
*/
12-
export function append_styles(anchor, css, is_custom_element) {
13-
// in dev, always check the DOM, so that styles can be replaced with HMR
14-
if (!DEV && !is_custom_element) {
15-
var doc = /** @type {Document} */ (anchor.ownerDocument);
16-
17-
if (!roots.has(doc)) roots.set(doc, new Set());
18-
const seen = roots.get(doc);
19-
20-
if (seen.has(css)) return;
21-
seen.add(css);
22-
}
23-
9+
export function append_styles(anchor, css) {
2410
// Use `queue_micro_task` to ensure `anchor` is in the DOM, otherwise getRootNode() will yield wrong results
2511
queue_micro_task(() => {
2612
var root = anchor.getRootNode();
@@ -29,6 +15,8 @@ export function append_styles(anchor, css, is_custom_element) {
2915
? /** @type {ShadowRoot} */ (root)
3016
: /** @type {Document} */ (root).head ?? /** @type {Document} */ (root.ownerDocument).head;
3117

18+
// Always querying the DOM is roughly the same perf as additionally checking for presence in a map first assuming
19+
// that you'll get cache hits half of the time, so we just always query the dom for simplicity and code savings.
3220
if (!target.querySelector('#' + css.hash)) {
3321
const style = document.createElement('style');
3422
style.id = css.hash;

packages/svelte/tests/runtime-browser/samples/mount-in-iframe/Child.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
<svelte:options css="injected" />
22

33
<script>
4+
import GrandChild from "./GrandChild.svelte";
5+
46
let { count } = $props();
57
</script>
68

79
<h1>count: {count}</h1>
10+
<GrandChild {count} />
811

912
<style>
1013
h1 {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<svelte:options css="injected" />
2+
3+
<h1>inner</h1>
4+
5+
<style>
6+
h1 {
7+
color: blue;
8+
}
9+
</style>

packages/svelte/tests/runtime-browser/samples/mount-in-iframe/_config.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@ export default test({
55
async test({ target, assert }) {
66
const button = target.querySelector('button');
77
const h1 = () =>
8-
/** @type {HTMLHeadingElement} */ (
8+
/** @type {NodeListOf<HTMLHeadingElement>} */ (
99
/** @type {Window} */ (
1010
target.querySelector('iframe')?.contentWindow
11-
).document.querySelector('h1')
11+
).document.querySelectorAll('h1')
1212
);
1313

14-
assert.equal(h1().textContent, 'count: 0');
15-
assert.equal(getComputedStyle(h1()).color, 'rgb(255, 0, 0)');
14+
assert.equal(h1()[0].textContent, 'count: 0');
15+
assert.equal(getComputedStyle(h1()[0]).color, 'rgb(255, 0, 0)');
16+
assert.equal(getComputedStyle(h1()[1]).color, 'rgb(0, 0, 255)');
1617

1718
flushSync(() => button?.click());
1819

19-
assert.equal(h1().textContent, 'count: 1');
20-
assert.equal(getComputedStyle(h1()).color, 'rgb(255, 0, 0)');
20+
assert.equal(h1()[0].textContent, 'count: 1');
21+
assert.equal(getComputedStyle(h1()[0]).color, 'rgb(255, 0, 0)');
22+
assert.equal(getComputedStyle(h1()[1]).color, 'rgb(0, 0, 255)');
2123
}
2224
});

0 commit comments

Comments
 (0)