Skip to content

Commit 5210ae2

Browse files
SomaticITdummdidummRich-Harris
authored
fix: allow component to be mounted in iframe (#13225)
* fix: allow component to be mounted in iframe * prettier * prettier * fix: allow HMR cleanup styles in iframe and custom elements * add test * avoid creating unnecessary microtasks * simplify * rename stuff * typo * lint * changeset * changeset --------- Co-authored-by: Simon H <[email protected]> Co-authored-by: Rich Harris <[email protected]> Co-authored-by: Rich Harris <[email protected]>
1 parent a680e6f commit 5210ae2

File tree

10 files changed

+120
-14
lines changed

10 files changed

+120
-14
lines changed

.changeset/silly-fishes-deny.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: allow custom element styles to be updated in HMR mode

.changeset/wild-mice-look.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: inject styles correctly when mounting inside an iframe

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

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -494,18 +494,7 @@ export function client_component(analysis, options) {
494494

495495
if (analysis.css.hash) {
496496
// remove existing `<style>` element, in case CSS changed
497-
accept_fn_body.unshift(
498-
b.stmt(
499-
b.call(
500-
b.member(
501-
b.call('document.querySelector', b.literal('#' + analysis.css.hash)),
502-
'remove',
503-
false,
504-
true
505-
)
506-
)
507-
)
508-
);
497+
accept_fn_body.unshift(b.stmt(b.call('$.cleanup_styles', b.literal(analysis.css.hash))));
509498
}
510499

511500
const hmr = b.block([
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/** @type {Map<String, Set<HTMLStyleElement>>} */
2+
var all_styles = new Map();
3+
4+
/**
5+
* @param {String} hash
6+
* @param {HTMLStyleElement} style
7+
*/
8+
export function register_style(hash, style) {
9+
var styles = all_styles.get(hash);
10+
11+
if (!styles) {
12+
styles = new Set();
13+
all_styles.set(hash, styles);
14+
}
15+
16+
styles.add(style);
17+
}
18+
19+
/**
20+
* @param {String} hash
21+
*/
22+
export function cleanup_styles(hash) {
23+
var styles = all_styles.get(hash);
24+
if (!styles) return;
25+
26+
for (const style of styles) {
27+
style.remove();
28+
}
29+
30+
all_styles.delete(hash);
31+
}

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

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

4-
var seen = new Set();
5+
var roots = new WeakMap();
56

67
/**
78
* @param {Node} anchor
89
* @param {{ hash: string, code: string }} css
910
* @param {boolean} [is_custom_element]
1011
*/
11-
export function append_styles(anchor, css, is_custom_element = false) {
12+
export function append_styles(anchor, css, is_custom_element) {
1213
// in dev, always check the DOM, so that styles can be replaced with HMR
1314
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+
1420
if (seen.has(css)) return;
1521
seen.add(css);
1622
}
@@ -29,6 +35,10 @@ export function append_styles(anchor, css, is_custom_element = false) {
2935
style.textContent = css.code;
3036

3137
target.appendChild(style);
38+
39+
if (DEV) {
40+
register_style(css.hash, style);
41+
}
3242
}
3343
});
3444
}

packages/svelte/src/internal/client/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { FILENAME, HMR } from '../../constants.js';
2+
export { cleanup_styles } from './dev/css.js';
23
export { add_locations } from './dev/elements.js';
34
export { hmr } from './dev/hmr.js';
45
export {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script>
2+
import { mount } from 'svelte';
3+
import Child from './Child.svelte';
4+
5+
let { count } = $props();
6+
7+
let iframe;
8+
9+
$effect(() => {
10+
mount(Child, {
11+
target: iframe.contentWindow.document.body,
12+
props: {
13+
count
14+
}
15+
});
16+
});
17+
</script>
18+
19+
<iframe title="container" bind:this={iframe}></iframe>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<svelte:options css="injected" />
2+
3+
<script>
4+
let { count } = $props();
5+
</script>
6+
7+
<h1>count: {count}</h1>
8+
9+
<style>
10+
h1 {
11+
color: red;
12+
}
13+
</style>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../assert';
3+
4+
export default test({
5+
async test({ target, assert }) {
6+
const button = target.querySelector('button');
7+
const h1 = () =>
8+
/** @type {HTMLHeadingElement} */ (
9+
/** @type {Window} */ (
10+
target.querySelector('iframe')?.contentWindow
11+
).document.querySelector('h1')
12+
);
13+
14+
assert.equal(h1().textContent, 'count: 0');
15+
assert.equal(getComputedStyle(h1()).color, 'rgb(255, 0, 0)');
16+
17+
flushSync(() => button?.click());
18+
19+
assert.equal(h1().textContent, 'count: 1');
20+
assert.equal(getComputedStyle(h1()).color, 'rgb(255, 0, 0)');
21+
}
22+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
import App from './App.svelte';
3+
4+
let count = $state(0);
5+
</script>
6+
7+
<button onclick={() => (count += 1)}>remount</button>
8+
9+
{#key count}
10+
<App {count} />
11+
{/key}

0 commit comments

Comments
 (0)