From 42b91569ba473506113aa18460a1b3cbb9718f0c Mon Sep 17 00:00:00 2001 From: Altan Birler Date: Fri, 25 Jun 2021 11:38:23 +0200 Subject: [PATCH 01/12] Improve src equality checking during hydration Normalize src values before checking equality --- src/compiler/compile/render_dom/wrappers/Element/Attribute.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index b9dbd0952d03..0f7c2b4c59cd 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts @@ -132,7 +132,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper { `); } else if (this.is_src) { block.chunks.hydrate.push( - b`if (${element.var}.src !== ${init}) ${method}(${element.var}, "${name}", ${this.last});` + b`if (${element.var}.src !== (new URL(${init}, location)).href) ${method}(${element.var}, "${name}", ${this.last});` ); updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`; } else if (property_name) { @@ -193,7 +193,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper { if (should_cache) { condition = this.is_src - ? x`${condition} && (${element.var}.src !== (${last} = ${value}))` + ? x`${condition} && (${element.var}.src !== (new URL((${last} = ${value}), location)).href)` : x`${condition} && (${last} !== (${last} = ${value}))`; } From 34f9ffe2d8a732839262d59ad1ebfc358f6ff09d Mon Sep 17 00:00:00 2001 From: Altan Birler Date: Fri, 25 Jun 2021 14:21:22 +0200 Subject: [PATCH 02/12] Skip reordering of unclaimed nodes in hydration --- src/runtime/internal/dom.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index a055bee4460a..eb2175a0d492 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -14,7 +14,7 @@ export function end_hydrating() { type NodeEx = Node & { claim_order?: number, hydrate_init? : true, - actual_end_child?: Node, + actual_end_child?: NodeEx, childNodes: NodeListOf, }; @@ -38,9 +38,9 @@ function init_hydrate(target: NodeEx) { type NodeEx2 = NodeEx & {claim_order: number}; // We know that all children have claim_order values since the unclaimed have been detached - const children = target.childNodes as NodeListOf; - - /* + const children = Array.from(target.childNodes as NodeListOf).filter(x => x.claim_order !== undefined) as NodeEx2[]; + + /* * Reorder claimed children optimally. * We can reorder claimed children optimally by finding the longest subsequence of * nodes that are already claimed in order and only moving the rest. The longest @@ -119,8 +119,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))) { 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) { - target.insertBefore(node, target.actual_end_child); + if (node.claim_order !== undefined || node.parentNode !== target) { + // We only insert if the ordering of this node should be modified or the parent node is not target + target.insertBefore(node, target.actual_end_child); + } } else { target.actual_end_child = node.nextSibling; } From d8aceba369af65e0a433961c0c9a9426fbd6bda6 Mon Sep 17 00:00:00 2001 From: Altan Birler Date: Fri, 25 Jun 2021 18:11:47 +0200 Subject: [PATCH 03/12] Fix failing tests Set `claim_order` in claim_html_tag --- src/runtime/internal/dom.ts | 19 +++++++++++++++---- .../_after_head.html | 2 +- .../samples/hydrated-void-element/expected.js | 4 ++-- .../samples/src-attribute-check/expected.js | 10 +++++----- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index eb2175a0d492..ac34f4c87061 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -313,12 +313,16 @@ export function children(element: Element) { return Array.from(element.childNodes); } -function claim_node(nodes: ChildNodeArray, predicate: (node: ChildNodeEx) => node is R, processNode: (node: ChildNodeEx) => void, createNode: () => R, dontUpdateLastIndex: boolean = false) { - // Try to find nodes in an order such that we lengthen the longest increasing subsequence +function init_claim_info(nodes: ChildNodeArray) { if (nodes.claim_info === undefined) { nodes.claim_info = {last_index: 0, total_claimed: 0}; } +} +function claim_node(nodes: ChildNodeArray, predicate: (node: ChildNodeEx) => node is R, processNode: (node: ChildNodeEx) => void, 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 = (() => { // We first try to find an element after the previous one for (let i = nodes.claim_info.last_index; i < nodes.length; i++) { @@ -415,10 +419,17 @@ export function claim_html_tag(nodes) { if (start_index === end_index) { return new HtmlTag(); } + + init_claim_info(nodes); const html_tag_nodes = nodes.splice(start_index, end_index + 1); detach(html_tag_nodes[0]); 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) { @@ -544,7 +555,7 @@ export function custom_event(type: string, detail?: T, bubbles: boolean = } 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 { diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html b/test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html index 9016a44869ec..be7a01ba4ff7 100644 --- a/test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html +++ b/test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html @@ -1,4 +1,4 @@ +Some Title -Some Title diff --git a/test/js/samples/hydrated-void-element/expected.js b/test/js/samples/hydrated-void-element/expected.js index e53d16d9250e..d30bc46cb068 100644 --- a/test/js/samples/hydrated-void-element/expected.js +++ b/test/js/samples/hydrated-void-element/expected.js @@ -35,7 +35,7 @@ function create_fragment(ctx) { this.h(); }, h() { - if (img.src !== (img_src_value = "donuts.jpg")) attr(img, "src", img_src_value); + if (img.src !== new URL(img_src_value = "donuts.jpg", location).href) attr(img, "src", img_src_value); attr(img, "alt", "donuts"); }, m(target, anchor) { @@ -61,4 +61,4 @@ class Component extends SvelteComponent { } } -export default Component; \ No newline at end of file +export default Component; diff --git a/test/js/samples/src-attribute-check/expected.js b/test/js/samples/src-attribute-check/expected.js index 93638edfb43b..8a8a2d6fca86 100644 --- a/test/js/samples/src-attribute-check/expected.js +++ b/test/js/samples/src-attribute-check/expected.js @@ -35,9 +35,9 @@ function create_fragment(ctx) { }, h() { attr(img0, "alt", "potato"); - if (img0.src !== (img0_src_value = /*url*/ ctx[0])) attr(img0, "src", img0_src_value); + if (img0.src !== new URL(img0_src_value = /*url*/ ctx[0], location).href) attr(img0, "src", img0_src_value); attr(img1, "alt", "potato"); - if (img1.src !== (img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"))) attr(img1, "src", img1_src_value); + if (img1.src !== new URL(img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"), location).href) attr(img1, "src", img1_src_value); }, m(target, anchor) { insert(target, img0, anchor); @@ -45,11 +45,11 @@ function create_fragment(ctx) { insert(target, img1, anchor); }, p(ctx, [dirty]) { - if (dirty & /*url*/ 1 && img0.src !== (img0_src_value = /*url*/ ctx[0])) { + if (dirty & /*url*/ 1 && img0.src !== new URL(img0_src_value = /*url*/ ctx[0], location).href) { attr(img0, "src", img0_src_value); } - if (dirty & /*slug*/ 2 && img1.src !== (img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"))) { + if (dirty & /*slug*/ 2 && img1.src !== new URL(img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"), location).href) { attr(img1, "src", img1_src_value); } }, @@ -82,4 +82,4 @@ class Component extends SvelteComponent { } } -export default Component; \ No newline at end of file +export default Component; From be00e035ba540f00fd5ef2ce49e6a366c566cd78 Mon Sep 17 00:00:00 2001 From: Altan Birler Date: Fri, 25 Jun 2021 18:29:55 +0200 Subject: [PATCH 04/12] Normale src urls Normalize both sides of the equality to be sure --- .../compile/render_dom/wrappers/Element/Attribute.ts | 4 ++-- test/js/samples/hydrated-void-element/expected.js | 2 +- test/js/samples/src-attribute-check/expected.js | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index 0f7c2b4c59cd..c8a84a4e0b39 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts @@ -132,7 +132,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper { `); } else if (this.is_src) { block.chunks.hydrate.push( - b`if (${element.var}.src !== (new URL(${init}, location)).href) ${method}(${element.var}, "${name}", ${this.last});` + b`if (new URL(${element.var}.src, location).href !== (new URL(${init}, location)).href) ${method}(${element.var}, "${name}", ${this.last});` ); updater = b`${method}(${element.var}, "${name}", ${should_cache ? this.last : value});`; } else if (property_name) { @@ -193,7 +193,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper { if (should_cache) { condition = this.is_src - ? x`${condition} && (${element.var}.src !== (new URL((${last} = ${value}), location)).href)` + ? x`${condition} && (new URL(${element.var}.src, location).href !== (new URL((${last} = ${value}), location)).href)` : x`${condition} && (${last} !== (${last} = ${value}))`; } diff --git a/test/js/samples/hydrated-void-element/expected.js b/test/js/samples/hydrated-void-element/expected.js index d30bc46cb068..cd837769cb24 100644 --- a/test/js/samples/hydrated-void-element/expected.js +++ b/test/js/samples/hydrated-void-element/expected.js @@ -35,7 +35,7 @@ function create_fragment(ctx) { this.h(); }, h() { - if (img.src !== new URL(img_src_value = "donuts.jpg", location).href) attr(img, "src", img_src_value); + if (new URL(img.src, location).href !== new URL(img_src_value = "donuts.jpg", location).href) attr(img, "src", img_src_value); attr(img, "alt", "donuts"); }, m(target, anchor) { diff --git a/test/js/samples/src-attribute-check/expected.js b/test/js/samples/src-attribute-check/expected.js index 8a8a2d6fca86..f8174e948a26 100644 --- a/test/js/samples/src-attribute-check/expected.js +++ b/test/js/samples/src-attribute-check/expected.js @@ -35,9 +35,9 @@ function create_fragment(ctx) { }, h() { attr(img0, "alt", "potato"); - if (img0.src !== new URL(img0_src_value = /*url*/ ctx[0], location).href) attr(img0, "src", img0_src_value); + if (new URL(img0.src, location).href !== new URL(img0_src_value = /*url*/ ctx[0], location).href) attr(img0, "src", img0_src_value); attr(img1, "alt", "potato"); - if (img1.src !== new URL(img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"), location).href) attr(img1, "src", img1_src_value); + if (new URL(img1.src, location).href !== new URL(img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"), location).href) attr(img1, "src", img1_src_value); }, m(target, anchor) { insert(target, img0, anchor); @@ -45,11 +45,11 @@ function create_fragment(ctx) { insert(target, img1, anchor); }, p(ctx, [dirty]) { - if (dirty & /*url*/ 1 && img0.src !== new URL(img0_src_value = /*url*/ ctx[0], location).href) { + if (dirty & /*url*/ 1 && new URL(img0.src, location).href !== new URL(img0_src_value = /*url*/ ctx[0], location).href) { attr(img0, "src", img0_src_value); } - if (dirty & /*slug*/ 2 && img1.src !== new URL(img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"), location).href) { + if (dirty & /*slug*/ 2 && new URL(img1.src, location).href !== new URL(img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"), location).href) { attr(img1, "src", img1_src_value); } }, From dae7599d2e128747099f795654852e897627dc2e Mon Sep 17 00:00:00 2001 From: Altan Birler Date: Fri, 25 Jun 2021 20:43:46 +0200 Subject: [PATCH 05/12] Improve comments --- src/runtime/internal/dom.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index ac34f4c87061..e289d7e0a44d 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -126,8 +126,8 @@ export function append(target: NodeEx, node: NodeEx) { } 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) { - // We only insert if the ordering of this node should be modified or the parent node is not target target.insertBefore(node, target.actual_end_child); } } else { From 1f2460dd6e081f6a5e94252f4599a5db4182364a Mon Sep 17 00:00:00 2001 From: Altan Birler Date: Sun, 27 Jun 2021 06:14:33 +0200 Subject: [PATCH 06/12] Implement proper src equality checking Use imports --- .../compile/render_dom/wrappers/Element/Attribute.ts | 4 ++-- src/runtime/internal/utils.ts | 4 ++++ test/js/samples/hydrated-void-element/expected.js | 5 +++-- test/js/samples/src-attribute-check/expected.js | 11 ++++++----- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index c8a84a4e0b39..179635eb5789 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts @@ -132,7 +132,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper { `); } else if (this.is_src) { block.chunks.hydrate.push( - b`if (new URL(${element.var}.src, location).href !== (new URL(${init}, location)).href) ${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});`; } else if (property_name) { @@ -193,7 +193,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper { if (should_cache) { condition = this.is_src - ? x`${condition} && (new URL(${element.var}.src, location).href !== (new URL((${last} = ${value}), location)).href)` + ? x`${condition} && (!@src_url_equal(${element.var}.src, (${last} = ${value})))` : x`${condition} && (${last} !== (${last} = ${value}))`; } diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 9f3da8589a9c..84bf75b0c0a3 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -40,6 +40,10 @@ export function safe_not_equal(a, b) { return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } +export function src_url_equal(a, b) { + return new URL(a, window.location.origin).href === (new URL(b, window.location.origin)).href; +} + export function not_equal(a, b) { return a != a ? b == b : a !== b; } diff --git a/test/js/samples/hydrated-void-element/expected.js b/test/js/samples/hydrated-void-element/expected.js index cd837769cb24..2df469e68903 100644 --- a/test/js/samples/hydrated-void-element/expected.js +++ b/test/js/samples/hydrated-void-element/expected.js @@ -11,7 +11,8 @@ import { insert, noop, safe_not_equal, - space + space, + src_url_equal } from "svelte/internal"; function create_fragment(ctx) { @@ -35,7 +36,7 @@ function create_fragment(ctx) { this.h(); }, h() { - if (new URL(img.src, location).href !== new URL(img_src_value = "donuts.jpg", location).href) 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"); }, m(target, anchor) { diff --git a/test/js/samples/src-attribute-check/expected.js b/test/js/samples/src-attribute-check/expected.js index f8174e948a26..a09b5c589fb4 100644 --- a/test/js/samples/src-attribute-check/expected.js +++ b/test/js/samples/src-attribute-check/expected.js @@ -10,7 +10,8 @@ import { insert, noop, safe_not_equal, - space + space, + src_url_equal } from "svelte/internal"; function create_fragment(ctx) { @@ -35,9 +36,9 @@ function create_fragment(ctx) { }, h() { attr(img0, "alt", "potato"); - if (new URL(img0.src, location).href !== new URL(img0_src_value = /*url*/ ctx[0], location).href) 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"); - if (new URL(img1.src, location).href !== new URL(img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"), location).href) 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) { insert(target, img0, anchor); @@ -45,11 +46,11 @@ function create_fragment(ctx) { insert(target, img1, anchor); }, p(ctx, [dirty]) { - if (dirty & /*url*/ 1 && new URL(img0.src, location).href !== new URL(img0_src_value = /*url*/ ctx[0], location).href) { + if (dirty & /*url*/ 1 && !src_url_equal(img0.src, img0_src_value = /*url*/ ctx[0])) { attr(img0, "src", img0_src_value); } - if (dirty & /*slug*/ 2 && new URL(img1.src, location).href !== new URL(img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"), location).href) { + if (dirty & /*slug*/ 2 && !src_url_equal(img1.src, img1_src_value = "" + (/*slug*/ ctx[1] + ".jpg"))) { attr(img1, "src", img1_src_value); } }, From 8e69246d47317a4200c1b341f9b94b4128c6b338 Mon Sep 17 00:00:00 2001 From: Altan Birler Date: Sun, 27 Jun 2021 06:46:11 +0200 Subject: [PATCH 07/12] Optimize `init_hydrate` Do not create array from children if not needed --- src/runtime/internal/dom.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index e289d7e0a44d..c325689c3a38 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -36,9 +36,21 @@ function init_hydrate(target: NodeEx) { target.hydrate_init = true; type NodeEx2 = NodeEx & {claim_order: number}; - - // We know that all children have claim_order values since the unclaimed have been detached - const children = Array.from(target.childNodes as NodeListOf).filter(x => x.claim_order !== undefined) as NodeEx2[]; + + // We know that all children have claim_order values since the unclaimed have been detached if target is not head + let children: ArrayLike = target.childNodes as NodeListOf; + + // If target is head, there may be children without claim_order + if (target.nodeName.toLowerCase() == "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. From a16bb391db52c9ce367111439e8d9482e3a65f2d Mon Sep 17 00:00:00 2001 From: Altan Birler Date: Sun, 27 Jun 2021 07:13:41 +0200 Subject: [PATCH 08/12] Implement fast path for `init_hydrate` binary search --- src/runtime/internal/dom.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index c325689c3a38..d1b52647915c 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -82,8 +82,9 @@ function init_hydrate(target: NodeEx) { // 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 - 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; const newLen = seqLen + 1; From 2fbc2f8741306e9d1b08039bb10fa27fec9f35df Mon Sep 17 00:00:00 2001 From: Altan Birler Date: Sun, 27 Jun 2021 07:51:03 +0200 Subject: [PATCH 09/12] Improve claim_text Split an existing text node if we match the prefix --- src/runtime/internal/dom.ts | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index d1b52647915c..556178870bf0 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -41,12 +41,12 @@ function init_hydrate(target: NodeEx) { let children: ArrayLike = target.childNodes as NodeListOf; // If target is head, there may be children without claim_order - if (target.nodeName.toLowerCase() == "head") { + if (target.nodeName.toLowerCase() === 'head') { const myChildren = []; for (let i = 0; i < children.length; i++) { const node = children[i]; if (node.claim_order !== undefined) { - myChildren.push(node) + myChildren.push(node); } } children = myChildren; @@ -332,7 +332,7 @@ function init_claim_info(nodes: ChildNodeArray) { } } -function claim_node(nodes: ChildNodeArray, predicate: (node: ChildNodeEx) => node is R, processNode: (node: ChildNodeEx) => void, createNode: () => R, dontUpdateLastIndex: boolean = false) { +function claim_node(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); @@ -342,9 +342,13 @@ function claim_node(nodes: ChildNodeArray, predicate: (no const node = nodes[i]; if (predicate(node)) { - processNode(node); + const replacement = processNode(node); - nodes.splice(i, 1); + if (replacement === undefined) { + nodes.splice(i, 1); + } else { + nodes[i] = replacement; + } if (!dontUpdateLastIndex) { nodes.claim_info.last_index = i; } @@ -359,12 +363,16 @@ function claim_node(nodes: ChildNodeArray, predicate: (no const node = nodes[i]; if (predicate(node)) { - processNode(node); + const replacement = processNode(node); - nodes.splice(i, 1); + if (replacement === undefined) { + nodes.splice(i, 1); + } else { + nodes[i] = replacement; + } if (!dontUpdateLastIndex) { nodes.claim_info.last_index = i; - } else { + } else if (replacement === undefined) { // Since we spliced before the last_index, we decrease it nodes.claim_info.last_index--; } @@ -394,6 +402,7 @@ export function claim_element(nodes: ChildNodeArray, name: string, attributes: { } } remove.forEach(v => node.removeAttribute(v)); + return undefined; }, () => svg ? svg_element(name as keyof SVGElementTagNameMap) : element(name as keyof HTMLElementTagNameMap) ); @@ -404,7 +413,14 @@ export function claim_text(nodes: ChildNodeArray, data) { nodes, (node: ChildNode): node is Text => node.nodeType === 3, (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), true // Text nodes should not update last index since it is likely not worth it to eliminate an increasing subsequence of actual elements From 2647bfbbee3bd21bd5e7b67f0d1ef2081abf3115 Mon Sep 17 00:00:00 2001 From: Altan Birler Date: Thu, 8 Jul 2021 22:03:25 +0200 Subject: [PATCH 10/12] Use anchor instead of URL for relative to absolute path convertion Use an anchor element's href attribute to convert relative paths to absolute paths --- src/runtime/internal/utils.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 84bf75b0c0a3..0de03f4b125b 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -40,8 +40,16 @@ export function safe_not_equal(a, b) { return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -export function src_url_equal(a, b) { - return new URL(a, window.location.origin).href === (new URL(b, window.location.origin)).href; +const relative_to_absolute = (function() { + const anchor = document.createElement('a'); + return function(url) { + anchor.href = url; + return anchor.href; + }; +})(); + +export function src_url_equal(element_src, url) { + return element_src === relative_to_absolute(url); } export function not_equal(a, b) { From ef025d95428738a5973a377ef7a8739bab3f9a70 Mon Sep 17 00:00:00 2001 From: Altan Birler Date: Thu, 8 Jul 2021 22:25:21 +0200 Subject: [PATCH 11/12] Fix tree-shakability --- src/runtime/internal/utils.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 0de03f4b125b..4d07ffbac828 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -40,13 +40,13 @@ export function safe_not_equal(a, b) { return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -const relative_to_absolute = (function() { - const anchor = document.createElement('a'); - return function(url) { - anchor.href = url; - return anchor.href; - }; -})(); +const relative_to_absolute : ((url: string) => string) & {anchor?: HTMLAnchorElement} = (url: string) => { + if (relative_to_absolute.anchor === undefined) { + relative_to_absolute.anchor = document.createElement('a'); + } + relative_to_absolute.anchor.href = url; + return relative_to_absolute.anchor.href; +}; export function src_url_equal(element_src, url) { return element_src === relative_to_absolute(url); From a43fb64a1e11cf1d04688d5e3d14dcb7d0ed9a91 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Wed, 21 Jul 2021 12:11:01 -0400 Subject: [PATCH 12/12] tidy --- src/runtime/internal/dom.ts | 24 ++++++++++++------------ src/runtime/internal/utils.ts | 14 ++++++-------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index 556178870bf0..c1909dafe213 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -36,12 +36,12 @@ function init_hydrate(target: NodeEx) { target.hydrate_init = true; type NodeEx2 = NodeEx & {claim_order: number}; - - // We know that all children have claim_order values since the unclaimed have been detached if target is not head + + // We know that all children have claim_order values since the unclaimed have been detached if target is not let children: ArrayLike = target.childNodes as NodeListOf; - - // If target is head, there may be children without claim_order - if (target.nodeName.toLowerCase() === 'head') { + + // If target is , 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]; @@ -51,8 +51,8 @@ function init_hydrate(target: NodeEx) { } children = myChildren; } - - /* + + /* * Reorder claimed children optimally. * We can reorder claimed children optimally by finding the longest subsequence of * nodes that are already claimed in order and only moving the rest. The longest @@ -84,7 +84,7 @@ function init_hydrate(target: NodeEx) { // upper_bound returns first greater value, so we subtract one // 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; const newLen = seqLen + 1; @@ -132,12 +132,12 @@ export function append(target: NodeEx, node: NodeEx) { if ((target.actual_end_child === undefined) || ((target.actual_end_child !== null) && (target.actual_end_child.parentElement !== target))) { 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) { // 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) { @@ -335,7 +335,7 @@ function init_claim_info(nodes: ChildNodeArray) { function claim_node(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 = (() => { // We first try to find an element after the previous one for (let i = nodes.claim_info.last_index; i < nodes.length; i++) { @@ -448,7 +448,7 @@ export function claim_html_tag(nodes) { if (start_index === end_index) { return new HtmlTag(); } - + init_claim_info(nodes); const html_tag_nodes = nodes.splice(start_index, end_index + 1); detach(html_tag_nodes[0]); diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index 4d07ffbac828..9a372617239c 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -40,16 +40,14 @@ export function safe_not_equal(a, b) { return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -const relative_to_absolute : ((url: string) => string) & {anchor?: HTMLAnchorElement} = (url: string) => { - if (relative_to_absolute.anchor === undefined) { - relative_to_absolute.anchor = document.createElement('a'); - } - relative_to_absolute.anchor.href = url; - return relative_to_absolute.anchor.href; -}; +let src_url_equal_anchor; export function src_url_equal(element_src, url) { - return element_src === relative_to_absolute(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) {