Skip to content

Commit 3c53da4

Browse files
committed
fix: remove memory leak from retaining old DOM elements
1 parent 8fef412 commit 3c53da4

File tree

4 files changed

+91
-22
lines changed

4 files changed

+91
-22
lines changed

.changeset/rich-plums-thank.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: remove memory leak from retaining old DOM elements

packages/svelte/src/internal/client/dom/blocks/html.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { derived } from '../../reactivity/deriveds.js';
22
import { render_effect } from '../../reactivity/effects.js';
3-
import { get } from '../../runtime.js';
3+
import { current_effect, get } from '../../runtime.js';
44
import { hydrate_nodes, hydrating } from '../hydration.js';
55
import { create_fragment_from_html, remove } from '../reconciler.js';
6+
import { push_template_node } from '../template.js';
67

78
/**
89
* @param {Element | Text | Comment} anchor
@@ -11,10 +12,12 @@ import { create_fragment_from_html, remove } from '../reconciler.js';
1112
* @returns {void}
1213
*/
1314
export function html(anchor, get_value, svg) {
15+
var effect = anchor.parentNode !== current_effect?.dom ? current_effect : null;
1416
let value = derived(get_value);
1517

1618
render_effect(() => {
17-
var dom = html_to_dom(anchor, get(value), svg);
19+
var dom = html_to_dom(anchor, effect, get(value), svg);
20+
effect = null;
1821

1922
if (dom) {
2023
return () => remove(dom);
@@ -27,11 +30,12 @@ export function html(anchor, get_value, svg) {
2730
* inserts it before the target anchor and returns the new nodes.
2831
* @template V
2932
* @param {Element | Text | Comment} target
33+
* @param {import('#client').Effect | null} effect
3034
* @param {V} value
3135
* @param {boolean} svg
3236
* @returns {Element | Comment | (Element | Comment | Text)[]}
3337
*/
34-
function html_to_dom(target, value, svg) {
38+
function html_to_dom(target, effect, value, svg) {
3539
if (hydrating) return hydrate_nodes;
3640

3741
var html = value + '';
@@ -49,6 +53,9 @@ function html_to_dom(target, value, svg) {
4953
if (node.childNodes.length === 1) {
5054
var child = /** @type {Text | Element | Comment} */ (node.firstChild);
5155
target.before(child);
56+
if (effect !== null) {
57+
push_template_node(effect, child);
58+
}
5259
return child;
5360
}
5461

@@ -62,5 +69,9 @@ function html_to_dom(target, value, svg) {
6269
target.before(node);
6370
}
6471

72+
if (effect !== null) {
73+
push_template_node(effect, nodes);
74+
}
75+
6576
return nodes;
6677
}

packages/svelte/src/internal/client/dom/blocks/svelte-element.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { is_array } from '../../utils.js';
1313
import { set_should_intro } from '../../render.js';
1414
import { current_each_item, set_current_each_item } from './each.js';
1515
import { current_effect } from '../../runtime.js';
16+
import { push_template_node } from '../template.js';
1617

1718
/**
1819
* @param {import('#client').Effect} effect
@@ -131,6 +132,8 @@ export function element(anchor, get_tag, is_svg, render_fn) {
131132
if (prev_element) {
132133
swap_block_dom(parent_effect, prev_element, element);
133134
prev_element.remove();
135+
} else {
136+
push_template_node(parent_effect, element);
134137
}
135138
});
136139
}

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

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,36 @@ import { create_fragment_from_html } from './reconciler.js';
44
import { current_effect } from '../runtime.js';
55
import { TEMPLATE_FRAGMENT, TEMPLATE_USE_IMPORT_NODE } from '../../../constants.js';
66
import { effect } from '../reactivity/effects.js';
7+
import { is_array } from '../utils.js';
8+
9+
/**
10+
* @param {import("#client").Effect} effect
11+
* @param {import("#client").TemplateNode | import("#client").TemplateNode[]} dom
12+
*/
13+
export function push_template_node(effect, dom) {
14+
var current_dom = effect.dom;
15+
if (current_dom === null) {
16+
effect.dom = dom;
17+
} else {
18+
if (!is_array(current_dom)) {
19+
current_dom = effect.dom = [current_dom];
20+
}
21+
var anchor;
22+
// If we're working with an anchor, then remove it and put it at the end.
23+
if (current_dom[0].nodeType === 8) {
24+
anchor = current_dom.pop();
25+
}
26+
if (is_array(dom)) {
27+
current_dom.push(...dom);
28+
} else {
29+
current_dom.push(dom);
30+
}
31+
if (anchor !== undefined) {
32+
current_dom.push(anchor);
33+
}
34+
}
35+
return dom;
36+
}
737

838
/**
939
* @param {string} content
@@ -19,16 +49,31 @@ export function template(content, flags) {
1949
var node;
2050

2151
return () => {
52+
var effect = /** @type {import('#client').Effect} */ (current_effect);
2253
if (hydrating) {
23-
return is_fragment ? hydrate_nodes : /** @type {Node} */ (hydrate_nodes[0]);
54+
var hydration_content = push_template_node(
55+
effect,
56+
is_fragment ? hydrate_nodes : hydrate_nodes[0]
57+
);
58+
return /** @type {Node} */ (hydration_content);
2459
}
2560

2661
if (!node) {
2762
node = create_fragment_from_html(content);
2863
if (!is_fragment) node = /** @type {Node} */ (node.firstChild);
2964
}
65+
var clone = use_import_node ? document.importNode(node, true) : clone_node(node, true);
66+
67+
if (is_fragment) {
68+
push_template_node(
69+
effect,
70+
/** @type {import('#client').TemplateNode[]} */ ([...clone.childNodes])
71+
);
72+
} else {
73+
push_template_node(effect, /** @type {import('#client').TemplateNode} */ (clone));
74+
}
3075

31-
return use_import_node ? document.importNode(node, true) : clone_node(node, true);
76+
return clone;
3277
};
3378
}
3479

@@ -70,8 +115,13 @@ export function svg_template(content, flags) {
70115
var node;
71116

72117
return () => {
118+
var effect = /** @type {import('#client').Effect} */ (current_effect);
73119
if (hydrating) {
74-
return is_fragment ? hydrate_nodes : /** @type {Node} */ (hydrate_nodes[0]);
120+
var hydration_content = push_template_node(
121+
effect,
122+
is_fragment ? hydrate_nodes : hydrate_nodes[0]
123+
);
124+
return /** @type {Node} */ (hydration_content);
75125
}
76126

77127
if (!node) {
@@ -87,7 +137,18 @@ export function svg_template(content, flags) {
87137
}
88138
}
89139

90-
return clone_node(node, true);
140+
var clone = clone_node(node, true);
141+
142+
if (is_fragment) {
143+
push_template_node(
144+
effect,
145+
/** @type {import('#client').TemplateNode[]} */ ([...clone.childNodes])
146+
);
147+
} else {
148+
push_template_node(effect, /** @type {import('#client').TemplateNode} */ (clone));
149+
}
150+
151+
return clone;
91152
};
92153
}
93154

@@ -152,7 +213,8 @@ function run_scripts(node) {
152213
*/
153214
/*#__NO_SIDE_EFFECTS__*/
154215
export function text(anchor) {
155-
if (!hydrating) return empty();
216+
var effect = /** @type {import('#client').Effect} */ (current_effect);
217+
if (!hydrating) return push_template_node(effect, empty());
156218

157219
var node = hydrate_nodes[0];
158220

@@ -162,7 +224,7 @@ export function text(anchor) {
162224
anchor.before((node = empty()));
163225
}
164226

165-
return node;
227+
return push_template_node(effect, node);
166228
}
167229

168230
export const comment = template('<!>', TEMPLATE_FRAGMENT);
@@ -174,19 +236,7 @@ export const comment = template('<!>', TEMPLATE_FRAGMENT);
174236
* @param {import('#client').Dom} dom
175237
*/
176238
export function append(anchor, dom) {
177-
var current = dom;
178-
179239
if (!hydrating) {
180-
var node = /** @type {Node} */ (dom);
181-
182-
if (node.nodeType === 11) {
183-
// if hydrating, `dom` is already an array of nodes, but if not then
184-
// we need to create an array to store it on the current effect
185-
current = /** @type {import('#client').Dom} */ ([...node.childNodes]);
186-
}
187-
188-
anchor.before(node);
240+
anchor.before(/** @type {Node} */ (dom));
189241
}
190-
191-
/** @type {import('#client').Effect} */ (current_effect).dom = current;
192242
}

0 commit comments

Comments
 (0)