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.
pull/6204/head
Jonatan Svennberg 6 years ago committed by Ben McCann
parent f322e3fba4
commit 7d1e6dadbf

@ -1,7 +1,7 @@
import { add_render_callback, flush, schedule_update, dirty_components } from './scheduler';
import { current_component, set_current_component } from './lifecycle';
import { blank_object, is_empty, is_function, run, run_all, noop } from './utils';
import { children, detach } from './dom';
import { children, detach, start_hydrating, end_hydrating } from './dom';
import { transition_in } from './transitions';
interface Fragment {
@ -150,6 +150,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
if (options.target) {
if (options.hydrate) {
start_hydrating();
const nodes = children(options.target);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
$$.fragment && $$.fragment!.l(nodes);
@ -161,6 +162,7 @@ export function init(component, options, instance, create_fragment, not_equal, p
if (options.intro) transition_in(component.$$.fragment);
mount_component(component, options.target, options.anchor, options.customElement);
end_hydrating();
flush();
}

@ -1,15 +1,47 @@
import { has_prop } from './utils';
// Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM
// at the end of hydration without touching the remaining nodes.
let is_hydrating = false;
const nodes_to_detach = new Set<Node>();
export function start_hydrating() {
is_hydrating = true;
}
export function end_hydrating() {
is_hydrating = false;
for (const node of nodes_to_detach) {
node.parentNode.removeChild(node);
}
nodes_to_detach.clear();
}
export function append(target: Node, node: Node) {
if (is_hydrating) {
nodes_to_detach.delete(node);
}
if (node.parentNode !== target) {
target.appendChild(node);
}
}
export function insert(target: Node, node: Node, anchor?: Node) {
if (is_hydrating) {
nodes_to_detach.delete(node);
}
if (node.parentNode !== target || (anchor && node.nextSibling !== anchor)) {
target.insertBefore(node, anchor || null);
}
}
export function detach(node: Node) {
if (is_hydrating) {
nodes_to_detach.add(node);
} else if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
export function destroy_each(iterations, detaching) {
@ -154,8 +186,9 @@ export function children(element) {
}
export function claim_element(nodes, name, attributes, svg) {
for (let i = 0; i < nodes.length; i += 1) {
const node = nodes[i];
while (nodes.length > 0) {
const node = nodes.shift();
if (node.nodeName === name) {
let j = 0;
const remove = [];
@ -168,7 +201,10 @@ export function claim_element(nodes, name, attributes, svg) {
for (let k = 0; k < remove.length; k++) {
node.removeAttribute(remove[k]);
}
return nodes.splice(i, 1)[0];
return node;
} else {
detach(node);
}
}
@ -180,7 +216,7 @@ export function claim_text(nodes, data) {
const node = nodes[i];
if (node.nodeType === 3) {
node.data = '' + data;
return nodes.splice(i, 1)[0];
return nodes.shift();
}
}

Loading…
Cancel
Save