Skip to content

Commit 0b3fd4e

Browse files
authored
fix: properly remove root anchor node on unmount (#13381)
1 parent 0f6cc2b commit 0b3fd4e

File tree

5 files changed

+62
-5
lines changed

5 files changed

+62
-5
lines changed

.changeset/tasty-mice-admire.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: properly remove root anchor node on unmount

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,7 @@ export function set_text(text, value) {
8181
* @returns {Exports}
8282
*/
8383
export function mount(component, options) {
84-
const anchor = options.anchor ?? options.target.appendChild(create_text());
85-
return _mount(component, { ...options, anchor });
84+
return _mount(component, options);
8685
}
8786

8887
/**
@@ -176,7 +175,7 @@ const document_listeners = new Map();
176175
* @param {ComponentType<SvelteComponent<any>> | Component<any>} Component
177176
* @param {{
178177
* target: Document | Element | ShadowRoot;
179-
* anchor: Node;
178+
* anchor?: Node;
180179
* props?: any;
181180
* events?: any;
182181
* context?: Map<any, any>;
@@ -225,6 +224,8 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
225224
var component = undefined;
226225

227226
var unmount = effect_root(() => {
227+
var anchor_node = anchor ?? target.appendChild(create_text());
228+
228229
branch(() => {
229230
if (context) {
230231
push({});
@@ -238,12 +239,12 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
238239
}
239240

240241
if (hydrating) {
241-
assign_nodes(/** @type {TemplateNode} */ (anchor), null);
242+
assign_nodes(/** @type {TemplateNode} */ (anchor_node), null);
242243
}
243244

244245
should_intro = intro;
245246
// @ts-expect-error the public typings are not what the actual function looks like
246-
component = Component(anchor, props) || {};
247+
component = Component(anchor_node, props) || {};
247248
should_intro = true;
248249

249250
if (hydrating) {
@@ -271,6 +272,9 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
271272

272273
root_event_handles.delete(event_handle);
273274
mounted_components.delete(component);
275+
if (anchor_node !== anchor) {
276+
anchor_node.parentNode?.removeChild(anchor_node);
277+
}
274278
};
275279
});
276280

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
let { text = 'hello' } = $props();
3+
</script>
4+
5+
<p>{text}</p>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
test({ assert, target }) {
6+
const btn = target.querySelector('button');
7+
btn?.click();
8+
flushSync();
9+
btn?.click();
10+
flushSync();
11+
btn?.click();
12+
flushSync();
13+
btn?.click();
14+
flushSync();
15+
btn?.click();
16+
flushSync();
17+
18+
const div = target.querySelector('div');
19+
20+
assert.htmlEqual(target.innerHTML, '<button>generate</button><div></div>');
21+
assert.equal(div?.childNodes.length, 0);
22+
}
23+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<script>
2+
import { mount, unmount } from "svelte";
3+
import Component from "./Component.svelte";
4+
5+
let target;
6+
7+
function generate() {
8+
for (let i = 0; i < 1000; i++) {
9+
let myInnocentState = $state({text: "hello"})
10+
const toUnmount = mount(Component, {
11+
target: target,
12+
props: myInnocentState,
13+
});
14+
unmount(toUnmount);
15+
}
16+
}
17+
</script>
18+
19+
<button onclick={generate}>generate</button>
20+
<div bind:this={target}></div>

0 commit comments

Comments
 (0)