Implement new hydration optimization

During hydration, greedily pick nodes that exist in the original HTML that should not be detached.
Detach the rest.
pull/6395/head
Altan Birler 5 years ago
parent 17c5402e31
commit 8e1183bfc3

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

@ -1,11 +1,38 @@
import { has_prop } from './utils'; import { has_prop } from './utils';
export function append(target: Node, node: Node) { // Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM
target.appendChild(node); // at the end of hydration without touching the remaining nodes.
let is_hydrating = false;
export function start_hydrating() {
is_hydrating = true;
}
export function end_hydrating() {
is_hydrating = false;
}
export function append(target: Node & {actual_end_child?: Node | null}, node: Node) {
if (is_hydrating) {
// If we are just starting with this target, we will insert before the firstChild (which may be null)
if (target.actual_end_child === undefined) {
target.actual_end_child = target.firstChild;
}
if (node.parentNode !== target) {
target.insertBefore(node, target.actual_end_child);
} else {
target.actual_end_child = node.nextSibling;
}
} else if (node.parentNode !== target) {
target.appendChild(node);
}
} }
export function insert(target: Node, node: Node, anchor?: Node) { export function insert(target: Node, node: Node, anchor?: Node) {
target.insertBefore(node, anchor || null); if (is_hydrating && !anchor) {
append(target, node);
} else if (node.parentNode !== target || (anchor && node.nextSibling !== anchor)) {
target.insertBefore(node, anchor || null);
}
} }
export function detach(node: Node) { export function detach(node: Node) {
@ -149,42 +176,80 @@ export function time_ranges_to_array(ranges) {
return array; return array;
} }
export function children(element) { export function children(element: HTMLElement) {
return Array.from(element.childNodes); return Array.from(element.childNodes);
} }
export function claim_element(nodes, name, attributes, svg) { type ChildNodeArray = ChildNode[] & {
for (let i = 0; i < nodes.length; i += 1) { /**
* All nodes at or after this index are available for preservation (not getting detached)
*/
lastKeepIndex?: number;
};
function claim_node<R extends ChildNode>(nodes: ChildNodeArray, predicate: (node: ChildNode) => node is R, processNode: (node: ChildNode) => void, createNode: () => R) {
if (nodes.lastKeepIndex === undefined) {
nodes.lastKeepIndex = 0;
}
// We first try to find a node we can actually keep without detaching
// This node should be after the previous node that we chose to keep without detaching
for (let i = nodes.lastKeepIndex; i < nodes.length; i++) {
const node = nodes[i]; const node = nodes[i];
if (node.nodeName === name) {
let j = 0; if (predicate(node)) {
const remove = []; processNode(node);
while (j < node.attributes.length) {
const attribute = node.attributes[j++]; nodes.splice(i, 1);
if (!attributes[attribute.name]) { nodes.lastKeepIndex = i;
remove.push(attribute.name); return node;
}
}
for (let k = 0; k < remove.length; k++) {
node.removeAttribute(remove[k]);
}
return nodes.splice(i, 1)[0];
} }
} }
return svg ? svg_element(name) : element(name);
}
export function claim_text(nodes, data) { // Otherwise, we try to find a node that we should detach
for (let i = 0; i < nodes.length; i += 1) { for (let i = 0; i < nodes.lastKeepIndex; i++) {
const node = nodes[i]; const node = nodes[i];
if (node.nodeType === 3) {
node.data = '' + data; if (predicate(node)) {
return nodes.splice(i, 1)[0]; processNode(node);
nodes.splice(i, 1);
nodes.lastKeepIndex -= 1;
detach(node);
return node;
} }
} }
return text(data); // If we can't find any matching node, we create a new one
return createNode();
}
export function claim_element(nodes: ChildNodeArray, name: string, attributes: {[key: string]: boolean}, svg) {
return claim_node<Element | SVGElement>(
nodes,
(node: ChildNode): node is Element | SVGElement => node.nodeName === name,
(node: Element) => {
const remove = [];
for (let j = 0; j < node.attributes.length; j++) {
const attribute = node.attributes[j];
if (!attributes[attribute.name]) {
remove.push(attribute.name);
}
}
remove.forEach(v => node.removeAttribute(v));
},
() => svg ? svg_element(name as keyof SVGElementTagNameMap) : element(name as keyof HTMLElementTagNameMap)
);
}
export function claim_text(nodes: ChildNodeArray, data) {
return claim_node<Text>(
nodes,
(node: ChildNode): node is Text => node.nodeType === 3,
(node: Text) => node.data = '' + data,
() => text(data)
);
} }
export function claim_space(nodes) { export function claim_space(nodes) {

Loading…
Cancel
Save