Skip to content

Commit 10e3e3d

Browse files
benmccannJonatan Svennberg
and
Jonatan Svennberg
authored
Improve SSR hydration performance (#6204)
* Improve SSR hydration performance - Fixes #4308 by avoiding de- and reattaching nodes during hydration - Turns existing append, insert and detach methods into "upserts" The new "hydration mode" was added in order to maintain the detach by default behavior during hydration. By tracking which nodes are claimed during hydration unclaimed nodes can then removed from the DOM at the end of hydration without touching the remaining nodes. Co-authored-by: Jonatan Svennberg <[email protected]>
1 parent f322e3f commit 10e3e3d

File tree

3 files changed

+47
-8
lines changed

3 files changed

+47
-8
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
* Avoid recreating DOM elements during hydration ([#6204](https://github.com/sveltejs/svelte/pull/6204))
56
* Add missing function overload for `derived` to allow explicitly setting an initial value for non-async derived stores ([#6172](https://github.com/sveltejs/svelte/pull/6172))
67
* Pass full markup source to script/style preprocessors ([#6169](https://github.com/sveltejs/svelte/pull/6169))
78

src/runtime/internal/Component.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { add_render_callback, flush, schedule_update, dirty_components } from './scheduler';
22
import { current_component, set_current_component } from './lifecycle';
33
import { blank_object, is_empty, is_function, run, run_all, noop } from './utils';
4-
import { children, detach } from './dom';
4+
import { children, detach, start_hydrating, end_hydrating } from './dom';
55
import { transition_in } from './transitions';
66

77
interface Fragment {
@@ -150,6 +150,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
150150

151151
if (options.target) {
152152
if (options.hydrate) {
153+
start_hydrating();
153154
const nodes = children(options.target);
154155
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
155156
$$.fragment && $$.fragment!.l(nodes);
@@ -161,6 +162,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
161162

162163
if (options.intro) transition_in(component.$$.fragment);
163164
mount_component(component, options.target, options.anchor, options.customElement);
165+
end_hydrating();
164166
flush();
165167
}
166168

src/runtime/internal/dom.ts

+43-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,47 @@
11
import { has_prop } from './utils';
22

3+
// Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM
4+
// at the end of hydration without touching the remaining nodes.
5+
let is_hydrating = false;
6+
const nodes_to_detach = new Set<Node>();
7+
8+
export function start_hydrating() {
9+
is_hydrating = true;
10+
}
11+
export function end_hydrating() {
12+
is_hydrating = false;
13+
14+
for (const node of nodes_to_detach) {
15+
node.parentNode.removeChild(node);
16+
}
17+
18+
nodes_to_detach.clear();
19+
}
20+
321
export function append(target: Node, node: Node) {
4-
target.appendChild(node);
22+
if (is_hydrating) {
23+
nodes_to_detach.delete(node);
24+
}
25+
if (node.parentNode !== target) {
26+
target.appendChild(node);
27+
}
528
}
629

730
export function insert(target: Node, node: Node, anchor?: Node) {
8-
target.insertBefore(node, anchor || null);
31+
if (is_hydrating) {
32+
nodes_to_detach.delete(node);
33+
}
34+
if (node.parentNode !== target || (anchor && node.nextSibling !== anchor)) {
35+
target.insertBefore(node, anchor || null);
36+
}
937
}
1038

1139
export function detach(node: Node) {
12-
node.parentNode.removeChild(node);
40+
if (is_hydrating) {
41+
nodes_to_detach.add(node);
42+
} else if (node.parentNode) {
43+
node.parentNode.removeChild(node);
44+
}
1345
}
1446

1547
export function destroy_each(iterations, detaching) {
@@ -154,8 +186,9 @@ export function children(element) {
154186
}
155187

156188
export function claim_element(nodes, name, attributes, svg) {
157-
for (let i = 0; i < nodes.length; i += 1) {
158-
const node = nodes[i];
189+
while (nodes.length > 0) {
190+
const node = nodes.shift();
191+
159192
if (node.nodeName === name) {
160193
let j = 0;
161194
const remove = [];
@@ -168,7 +201,10 @@ export function claim_element(nodes, name, attributes, svg) {
168201
for (let k = 0; k < remove.length; k++) {
169202
node.removeAttribute(remove[k]);
170203
}
171-
return nodes.splice(i, 1)[0];
204+
205+
return node;
206+
} else {
207+
detach(node);
172208
}
173209
}
174210

@@ -180,7 +216,7 @@ export function claim_text(nodes, data) {
180216
const node = nodes[i];
181217
if (node.nodeType === 3) {
182218
node.data = '' + data;
183-
return nodes.splice(i, 1)[0];
219+
return nodes.shift();
184220
}
185221
}
186222

0 commit comments

Comments
 (0)