diff --git a/cjs/index.js b/cjs/index.js index 9db2caa..b3e525f 100644 --- a/cjs/index.js +++ b/cjs/index.js @@ -17,6 +17,13 @@ * PERFORMANCE OF THIS SOFTWARE. */ +const moveBefore = (parentNode, isConnected, node, before) => { + if (isConnected === node.isConnected) + parentNode.moveBefore(node, before); + else + parentNode.insertBefore(node, before); +}; + /** * @param {Node} parentNode The container where children live * @param {Node[]} a The list of current/live children @@ -27,6 +34,7 @@ * @returns {Node[]} The same list of future children. */ module.exports = (parentNode, a, b, get, before) => { + const { isConnected } = parentNode; const bLength = b.length; let aEnd = a.length; let bEnd = bLength; @@ -46,7 +54,7 @@ module.exports = (parentNode, a, b, get, before) => { get(b[bEnd], 0)) : before; while (bStart < bEnd) - parentNode.insertBefore(get(b[bStart++], 1), node); + moveBefore(parentNode, isConnected, get(b[bStart++], 1), node); } // remove head or tail: fast path else if (bEnd === bStart) { @@ -81,11 +89,9 @@ module.exports = (parentNode, a, b, get, before) => { // [1, 2, 3, 4, 5] // [1, 2, 3, 5, 6, 4] const node = get(a[--aEnd], -1).nextSibling; - parentNode.insertBefore( - get(b[bStart++], 1), - get(a[aStart++], -1).nextSibling - ); - parentNode.insertBefore(get(b[--bEnd], 1), node); + moveBefore(parentNode, isConnected, get(b[bStart++], 1), get(a[aStart++], -1).nextSibling); + moveBefore(parentNode, isConnected, get(b[--bEnd], 1), node); + // mark the future index as identical (yeah, it's dirty, but cheap 👍) // The main reason to do this, is that when a[aEnd] will be reached, // the loop will likely be on the fast path, as identical to b[bEnd]. @@ -131,15 +137,19 @@ module.exports = (parentNode, a, b, get, before) => { if (sequence > (index - bStart)) { const node = get(a[aStart], 0); while (bStart < index) - parentNode.insertBefore(get(b[bStart++], 1), node); + moveBefore(parentNode, isConnected, get(b[bStart++], 1), node); } // if the effort wasn't good enough, fallback to a replace, // moving both source and target indexes forward, hoping that some // similar node will be found later on, to go back to the fast path else { - parentNode.replaceChild( + // TODO: this was a replaceChild but it's not clear if fragments + // work this way ... -1 seems also not appropriate + moveBefore( + parentNode, + isConnected, get(b[bStart++], 1), - get(a[aStart++], -1) + get(a[aStart++], 0) ); } } diff --git a/esm/index.js b/esm/index.js index a9cf2ff..ab71e47 100644 --- a/esm/index.js +++ b/esm/index.js @@ -16,6 +16,13 @@ * PERFORMANCE OF THIS SOFTWARE. */ +const moveBefore = (parentNode, isConnected, node, before) => { + if (isConnected === node.isConnected) + parentNode.moveBefore(node, before); + else + parentNode.insertBefore(node, before); +}; + /** * @param {Node} parentNode The container where children live * @param {Node[]} a The list of current/live children @@ -26,6 +33,7 @@ * @returns {Node[]} The same list of future children. */ export default (parentNode, a, b, get, before) => { + const { isConnected } = parentNode; const bLength = b.length; let aEnd = a.length; let bEnd = bLength; @@ -45,7 +53,7 @@ export default (parentNode, a, b, get, before) => { get(b[bEnd], 0)) : before; while (bStart < bEnd) - parentNode.insertBefore(get(b[bStart++], 1), node); + moveBefore(parentNode, isConnected, get(b[bStart++], 1), node); } // remove head or tail: fast path else if (bEnd === bStart) { @@ -79,12 +87,10 @@ export default (parentNode, a, b, get, before) => { // or asymmetric too // [1, 2, 3, 4, 5] // [1, 2, 3, 5, 6, 4] - const node = get(a[--aEnd], -1).nextSibling; - parentNode.insertBefore( - get(b[bStart++], 1), - get(a[aStart++], -1).nextSibling - ); - parentNode.insertBefore(get(b[--bEnd], 1), node); + const node = get(a[--aEnd], -0).nextSibling; + moveBefore(parentNode, isConnected, get(b[bStart++], 1), get(a[aStart++], -0).nextSibling); + moveBefore(parentNode, isConnected, get(b[--bEnd], 1), node); + // mark the future index as identical (yeah, it's dirty, but cheap 👍) // The main reason to do this, is that when a[aEnd] will be reached, // the loop will likely be on the fast path, as identical to b[bEnd]. @@ -130,15 +136,19 @@ export default (parentNode, a, b, get, before) => { if (sequence > (index - bStart)) { const node = get(a[aStart], 0); while (bStart < index) - parentNode.insertBefore(get(b[bStart++], 1), node); + moveBefore(parentNode, isConnected, get(b[bStart++], 1), node); } // if the effort wasn't good enough, fallback to a replace, // moving both source and target indexes forward, hoping that some // similar node will be found later on, to go back to the fast path else { - parentNode.replaceChild( + // TODO: this was a replaceChild but it's not clear if fragments + // work this way ... -1 seems also not appropriate + moveBefore( + parentNode, + isConnected, get(b[bStart++], 1), - get(a[aStart++], -1) + get(a[aStart++], 0) ); } } diff --git a/index.js b/index.js index bbcf0b5..29e760a 100644 --- a/index.js +++ b/index.js @@ -19,6 +19,10 @@ var udomdiff = (function (exports) { * PERFORMANCE OF THIS SOFTWARE. */ + var moveBefore = function moveBefore(parentNode, isConnected, node, before) { + if (isConnected === node.isConnected) parentNode.moveBefore(node, before);else parentNode.insertBefore(node, before); + }; + /** * @param {Node} parentNode The container where children live * @param {Node[]} a The list of current/live children @@ -29,6 +33,7 @@ var udomdiff = (function (exports) { * @returns {Node[]} The same list of future children. */ var index = (function (parentNode, a, b, get, before) { + var isConnected = parentNode.isConnected; var bLength = b.length; var aEnd = a.length; var bEnd = bLength; @@ -43,7 +48,7 @@ var udomdiff = (function (exports) { // the node to `insertBefore`, if the index is more than 0 // must be retrieved, otherwise it's gonna be the first item. var node = bEnd < bLength ? bStart ? get(b[bStart - 1], -0).nextSibling : get(b[bEnd], 0) : before; - while (bStart < bEnd) parentNode.insertBefore(get(b[bStart++], 1), node); + while (bStart < bEnd) moveBefore(parentNode, isConnected, get(b[bStart++], 1), node); } // remove head or tail: fast path else if (bEnd === bStart) { @@ -74,8 +79,9 @@ var udomdiff = (function (exports) { // [1, 2, 3, 4, 5] // [1, 2, 3, 5, 6, 4] var _node = get(a[--aEnd], -1).nextSibling; - parentNode.insertBefore(get(b[bStart++], 1), get(a[aStart++], -1).nextSibling); - parentNode.insertBefore(get(b[--bEnd], 1), _node); + moveBefore(parentNode, isConnected, get(b[bStart++], 1), get(a[aStart++], -1).nextSibling); + moveBefore(parentNode, isConnected, get(b[--bEnd], 1), _node); + // mark the future index as identical (yeah, it's dirty, but cheap 👍) // The main reason to do this, is that when a[aEnd] will be reached, // the loop will likely be on the fast path, as identical to b[bEnd]. @@ -118,13 +124,15 @@ var udomdiff = (function (exports) { // will be processed at zero cost if (sequence > index - bStart) { var _node2 = get(a[aStart], 0); - while (bStart < index) parentNode.insertBefore(get(b[bStart++], 1), _node2); + while (bStart < index) moveBefore(parentNode, isConnected, get(b[bStart++], 1), _node2); } // if the effort wasn't good enough, fallback to a replace, // moving both source and target indexes forward, hoping that some // similar node will be found later on, to go back to the fast path else { - parentNode.replaceChild(get(b[bStart++], 1), get(a[aStart++], -1)); + // TODO: this was a replaceChild but it's not clear if fragments + // work this way ... -1 seems also not appropriate + moveBefore(parentNode, isConnected, get(b[bStart++], 1), get(a[aStart++], 0)); } } // otherwise move the source forward, 'cause there's nothing to do diff --git a/min.js b/min.js index f130b86..d3cb400 100644 --- a/min.js +++ b/min.js @@ -1 +1 @@ -var udomdiff=(e=>(e.default=function(e,r,i,f,l){for(var n=i.length,t=r.length,o=n,s=0,a=0,v=null;s{function x(e,r,i,f){r===i.isConnected?e.moveBefore(i,f):e.insertBefore(i,f)}return e.default=function(e,r,i,f,n){for(var l=e.isConnected,o=i.length,t=r.length,s=o,a=0,v=0,d=null;a{const f=i.length;let n=t.length,s=f,o=0,c=0,u=null;for(;or-c){const f=l(t[o],0);for(;c{t===n.isConnected?e.moveBefore(n,i):e.insertBefore(n,i)};return e.default=(e,n,i,l,o)=>{const{isConnected:s}=e,f=i.length;let r=n.length,c=f,d=0,u=0,g=null;for(;do-u){const f=l(n[d],0);for(;u { }; class Siblings { + get isConnected() { return true } get nextSibling() { const {parentNode} = this; if (parentNode) { @@ -87,6 +88,9 @@ class Dommy extends Siblings { newNode.parentNode = this; return newNode; } + moveBefore(newNode, oldNode) { + this.insertBefore(newNode, oldNode); + } insertBefore(newNode, oldNode) { if (newNode !== oldNode) { remove(newNode); diff --git a/test/index.html b/test/index.html index 4e4376d..b44cd38 100644 --- a/test/index.html +++ b/test/index.html @@ -6,6 +6,7 @@ diff --git a/test/index.js b/test/index.js index 86f298a..772e776 100644 --- a/test/index.js +++ b/test/index.js @@ -1,6 +1,6 @@ global.document = { createElement: function (tagName) { - return {tagName: tagName, value: '\n'}; + return {tagName: tagName, value: '\n', isConnected: true}; }, createTextNode: function (value) { return Object.defineProperty( @@ -14,6 +14,7 @@ global.document = { }, importNode: function () {}, body: { + isConnected: true, get lastElementChild() { return this.childNodes[this.childNodes.length - 1]; }, @@ -26,6 +27,9 @@ global.document = { node.parentNode = this; }, childNodes: [], + moveBefore: function (before, after) { + this.insertBefore(before, after); + }, insertBefore: function (before, after) { if (before !== after) { this.removeChild(before);