[fix] hydration improvements (#6449)

pull/6560/head
Hasan Altan Birler 4 years ago committed by GitHub
parent 16d562fcf6
commit ecbd96af95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -132,7 +132,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
`); `);
} else if (this.is_src) { } else if (this.is_src) {
block.chunks.hydrate.push( block.chunks.hydrate.push(
b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${this.last});` b`if (!@src_url_equal(${element.var}.src, ${init})) ${method}(${element.var}, "${name}", ${this.last});`
); );
updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`; updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`;
} else if (property_name) { } else if (property_name) {
@ -193,7 +193,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
if (should_cache) { if (should_cache) {
condition = this.is_src condition = this.is_src
? x`${condition} && (${element.var}.src !== (${last} = ${value}))` ? x`${condition} && (!@src_url_equal(${element.var}.src, (${last} = ${value})))`
: x`${condition} && (${last} !== (${last} = ${value}))`; : x`${condition} && (${last} !== (${last} = ${value}))`;
} }

@ -14,7 +14,7 @@ export function end_hydrating() {
type NodeEx = Node & { type NodeEx = Node & {
claim_order?: number, claim_order?: number,
hydrate_init? : true, hydrate_init? : true,
actual_end_child?: Node, actual_end_child?: NodeEx,
childNodes: NodeListOf<NodeEx>, childNodes: NodeListOf<NodeEx>,
}; };
@ -37,8 +37,20 @@ function init_hydrate(target: NodeEx) {
type NodeEx2 = NodeEx & {claim_order: number}; type NodeEx2 = NodeEx & {claim_order: number};
// We know that all children have claim_order values since the unclaimed have been detached // We know that all children have claim_order values since the unclaimed have been detached if target is not <head>
const children = target.childNodes as NodeListOf<NodeEx2>; let children: ArrayLike<NodeEx2> = target.childNodes as NodeListOf<NodeEx2>;
// If target is <head>, there may be children without claim_order
if (target.nodeName === 'HEAD') {
const myChildren = [];
for (let i = 0; i < children.length; i++) {
const node = children[i];
if (node.claim_order !== undefined) {
myChildren.push(node);
}
}
children = myChildren;
}
/* /*
* Reorder claimed children optimally. * Reorder claimed children optimally.
@ -70,7 +82,8 @@ function init_hydrate(target: NodeEx) {
// Find the largest subsequence length such that it ends in a value less than our current value // Find the largest subsequence length such that it ends in a value less than our current value
// upper_bound returns first greater value, so we subtract one // upper_bound returns first greater value, so we subtract one
const seqLen = upper_bound(1, longest + 1, idx => children[m[idx]].claim_order, current) - 1; // with fast path for when we are on the current longest subsequence
const seqLen = ((longest > 0 && children[m[longest]].claim_order <= current) ? longest + 1 : upper_bound(1, longest, idx => children[m[idx]].claim_order, current)) - 1;
p[i] = m[seqLen] + 1; p[i] = m[seqLen] + 1;
@ -119,8 +132,17 @@ export function append(target: NodeEx, node: NodeEx) {
if ((target.actual_end_child === undefined) || ((target.actual_end_child !== null) && (target.actual_end_child.parentElement !== target))) { if ((target.actual_end_child === undefined) || ((target.actual_end_child !== null) && (target.actual_end_child.parentElement !== target))) {
target.actual_end_child = target.firstChild; target.actual_end_child = target.firstChild;
} }
// Skip nodes of undefined ordering
while ((target.actual_end_child !== null) && (target.actual_end_child.claim_order === undefined)) {
target.actual_end_child = target.actual_end_child.nextSibling;
}
if (node !== target.actual_end_child) { if (node !== target.actual_end_child) {
// We only insert if the ordering of this node should be modified or the parent node is not target
if (node.claim_order !== undefined || node.parentNode !== target) {
target.insertBefore(node, target.actual_end_child); target.insertBefore(node, target.actual_end_child);
}
} else { } else {
target.actual_end_child = node.nextSibling; target.actual_end_child = node.nextSibling;
} }
@ -304,11 +326,15 @@ export function children(element: Element) {
return Array.from(element.childNodes); return Array.from(element.childNodes);
} }
function claim_node<R extends ChildNodeEx>(nodes: ChildNodeArray, predicate: (node: ChildNodeEx) => node is R, processNode: (node: ChildNodeEx) => void, createNode: () => R, dontUpdateLastIndex: boolean = false) { function init_claim_info(nodes: ChildNodeArray) {
// Try to find nodes in an order such that we lengthen the longest increasing subsequence
if (nodes.claim_info === undefined) { if (nodes.claim_info === undefined) {
nodes.claim_info = {last_index: 0, total_claimed: 0}; nodes.claim_info = {last_index: 0, total_claimed: 0};
} }
}
function claim_node<R extends ChildNodeEx>(nodes: ChildNodeArray, predicate: (node: ChildNodeEx) => node is R, processNode: (node: ChildNodeEx) => ChildNodeEx | undefined, createNode: () => R, dontUpdateLastIndex: boolean = false) {
// Try to find nodes in an order such that we lengthen the longest increasing subsequence
init_claim_info(nodes);
const resultNode = (() => { const resultNode = (() => {
// We first try to find an element after the previous one // We first try to find an element after the previous one
@ -316,9 +342,13 @@ function claim_node<R extends ChildNodeEx>(nodes: ChildNodeArray, predicate: (no
const node = nodes[i]; const node = nodes[i];
if (predicate(node)) { if (predicate(node)) {
processNode(node); const replacement = processNode(node);
if (replacement === undefined) {
nodes.splice(i, 1); nodes.splice(i, 1);
} else {
nodes[i] = replacement;
}
if (!dontUpdateLastIndex) { if (!dontUpdateLastIndex) {
nodes.claim_info.last_index = i; nodes.claim_info.last_index = i;
} }
@ -333,12 +363,16 @@ function claim_node<R extends ChildNodeEx>(nodes: ChildNodeArray, predicate: (no
const node = nodes[i]; const node = nodes[i];
if (predicate(node)) { if (predicate(node)) {
processNode(node); const replacement = processNode(node);
if (replacement === undefined) {
nodes.splice(i, 1); nodes.splice(i, 1);
} else {
nodes[i] = replacement;
}
if (!dontUpdateLastIndex) { if (!dontUpdateLastIndex) {
nodes.claim_info.last_index = i; nodes.claim_info.last_index = i;
} else { } else if (replacement === undefined) {
// Since we spliced before the last_index, we decrease it // Since we spliced before the last_index, we decrease it
nodes.claim_info.last_index--; nodes.claim_info.last_index--;
} }
@ -368,6 +402,7 @@ export function claim_element(nodes: ChildNodeArray, name: string, attributes: {
} }
} }
remove.forEach(v => node.removeAttribute(v)); remove.forEach(v => node.removeAttribute(v));
return undefined;
}, },
() => svg ? svg_element(name as keyof SVGElementTagNameMap) : element(name as keyof HTMLElementTagNameMap) () => svg ? svg_element(name as keyof SVGElementTagNameMap) : element(name as keyof HTMLElementTagNameMap)
); );
@ -378,7 +413,14 @@ export function claim_text(nodes: ChildNodeArray, data) {
nodes, nodes,
(node: ChildNode): node is Text => node.nodeType === 3, (node: ChildNode): node is Text => node.nodeType === 3,
(node: Text) => { (node: Text) => {
node.data = '' + data; const dataStr = '' + data;
if (node.data.startsWith(dataStr)) {
if (node.data.length !== dataStr.length) {
return node.splitText(dataStr.length);
}
} else {
node.data = dataStr;
}
}, },
() => text(data), () => text(data),
true // Text nodes should not update last index since it is likely not worth it to eliminate an increasing subsequence of actual elements true // Text nodes should not update last index since it is likely not worth it to eliminate an increasing subsequence of actual elements
@ -406,10 +448,17 @@ export function claim_html_tag(nodes) {
if (start_index === end_index) { if (start_index === end_index) {
return new HtmlTag(); return new HtmlTag();
} }
init_claim_info(nodes);
const html_tag_nodes = nodes.splice(start_index, end_index + 1); const html_tag_nodes = nodes.splice(start_index, end_index + 1);
detach(html_tag_nodes[0]); detach(html_tag_nodes[0]);
detach(html_tag_nodes[html_tag_nodes.length - 1]); detach(html_tag_nodes[html_tag_nodes.length - 1]);
return new HtmlTag(html_tag_nodes.slice(1, html_tag_nodes.length - 1)); const claimed_nodes = html_tag_nodes.slice(1, html_tag_nodes.length - 1);
for (const n of claimed_nodes) {
n.claim_order = nodes.claim_info.total_claimed;
nodes.claim_info.total_claimed += 1;
}
return new HtmlTag(claimed_nodes);
} }
export function set_data(text, data) { export function set_data(text, data) {
@ -535,7 +584,7 @@ export function custom_event<T=any>(type: string, detail?: T, bubbles: boolean =
} }
export function query_selector_all(selector: string, parent: HTMLElement = document.body) { export function query_selector_all(selector: string, parent: HTMLElement = document.body) {
return Array.from(parent.querySelectorAll(selector)); return Array.from(parent.querySelectorAll(selector)) as ChildNodeArray;
} }
export class HtmlTag { export class HtmlTag {

@ -40,6 +40,16 @@ export function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
} }
let src_url_equal_anchor;
export function src_url_equal(element_src, url) {
if (!src_url_equal_anchor) {
src_url_equal_anchor = document.createElement('a');
}
src_url_equal_anchor.href = url;
return element_src === src_url_equal_anchor.href;
}
export function not_equal(a, b) { export function not_equal(a, b) {
return a != a ? b == b : a !== b; return a != a ? b == b : a !== b;
} }

@ -1,4 +1,4 @@
<title>Some Title</title>
<link href="/" rel="canonical"> <link href="/" rel="canonical">
<meta content="some description" name="description"> <meta content="some description" name="description">
<meta content="some keywords" name="keywords"> <meta content="some keywords" name="keywords">
<title>Some Title</title>

@ -11,7 +11,8 @@ import {
insert, insert,
noop, noop,
safe_not_equal, safe_not_equal,
space space,
src_url_equal
} from "svelte/internal"; } from "svelte/internal";
function create_fragment(ctx) { function create_fragment(ctx) {
@ -35,7 +36,7 @@ function create_fragment(ctx) {
this.h(); this.h();
}, },
h() { h() {
if (img.src !== (img_src_value = "donuts.jpg")) attr(img, "src", img_src_value); if (!src_url_equal(img.src, img_src_value = "donuts.jpg")) attr(img, "src", img_src_value);
attr(img, "alt", "donuts"); attr(img, "alt", "donuts");
}, },
m(target, anchor) { m(target, anchor) {

@ -10,7 +10,8 @@ import {
insert, insert,
noop, noop,
safe_not_equal, safe_not_equal,
space space,
src_url_equal
} from "svelte/internal"; } from "svelte/internal";
function create_fragment(ctx) { function create_fragment(ctx) {
@ -35,9 +36,9 @@ function create_fragment(ctx) {
}, },
h() { h() {
attr(img0, "alt", "potato"); attr(img0, "alt", "potato");
if (img0.src !== (img0_src_value = /*url*/ ctx[0])) attr(img0, "src", img0_src_value); if (!src_url_equal(img0.src, img0_src_value = /*url*/ ctx[0])) attr(img0, "src", img0_src_value);
attr(img1, "alt", "potato"); attr(img1, "alt", "potato");
if (img1.src !== (img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"))) attr(img1, "src", img1_src_value); if (!src_url_equal(img1.src, img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"))) attr(img1, "src", img1_src_value);
}, },
m(target, anchor) { m(target, anchor) {
insert(target, img0, anchor); insert(target, img0, anchor);
@ -45,11 +46,11 @@ function create_fragment(ctx) {
insert(target, img1, anchor); insert(target, img1, anchor);
}, },
p(ctx, [dirty]) { p(ctx, [dirty]) {
if (dirty & /*url*/ 1 && img0.src !== (img0_src_value = /*url*/ ctx[0])) { if (dirty & /*url*/ 1 && !src_url_equal(img0.src, img0_src_value = /*url*/ ctx[0])) {
attr(img0, "src", img0_src_value); attr(img0, "src", img0_src_value);
} }
if (dirty & /*slug*/ 2 && img1.src !== (img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"))) { if (dirty & /*slug*/ 2 && !src_url_equal(img1.src, img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"))) {
attr(img1, "src", img1_src_value); attr(img1, "src", img1_src_value);
} }
}, },

Loading…
Cancel
Save