Skip to content

Commit b8f3398

Browse files
committed
improve diffing mechanism, fixing #123
1 parent 7e281d2 commit b8f3398

File tree

8 files changed

+76
-47
lines changed

8 files changed

+76
-47
lines changed

browser/diffDOM.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

browser/diffDOM.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/index.html

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ <h1>diffDOM demo</h1>
3030
<p>
3131
The output to the right corresponds to the HTML code entered to the
3232
left. Synchronization always takes place after a change in the input
33-
field. if the HTML in the text area is invalidm synchronization does
33+
field. if the HTML in the text area is invalid synchronization does
3434
not take place until it is valid again. Notice how elements that
3535
have not changed are not touched. For example: try to start the
3636
video and then change something not related to the video iframe. The
@@ -41,21 +41,17 @@ <h1>diffDOM demo</h1>
4141
<tr>
4242
<td>
4343
<h2>Input</h2>
44-
<textarea class="success">
45-
Hey, this is a <i>test</i>.<br><iframe src="http://www.youtube.com/embed/0m4-ZEm9sbM" allowfullscreen="" width="420" height="315" frameborder="0"></iframe></textarea
46-
>
44+
<textarea class="success">Hey, this is a <i>test</i>.<br><iframe src="https://www.youtube.com/embed/0m4-ZEm9sbM" allowfullscreen="" width="420" height="315" frameborder="0"></iframe></textarea>
4745
</td>
4846
<td>
4947
<h2>Output</h2>
50-
<div>
51-
Hey, this is a <i>test</i>.<br /><iframe
52-
src="http://www.youtube.com/embed/0m4-ZEm9sbM"
48+
<div>Hey, this is a <i>test</i>.<br /><iframe
49+
src="https://www.youtube.com/embed/0m4-ZEm9sbM"
5350
allowfullscreen=""
5451
width="420"
5552
height="315"
5653
frameborder="0"
57-
></iframe>
58-
</div>
54+
></iframe></div>
5955
</td>
6056
</tr>
6157
</table>

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
"test": "tests"
1010
},
1111
"scripts": {
12-
"ts-migrate": "ts-migrate",
1312
"test": "jest",
1413
"lint": "eslint src/",
1514
"build": "rollup -c",

src/diffDOM/virtual/diff.ts

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -563,15 +563,15 @@ export class DiffFinder {
563563
const gapInformation = getGapInformation(t1, t2, subtrees)
564564
const gaps1 = gapInformation.gaps1
565565
const gaps2 = gapInformation.gaps2
566+
const t1ChildNodes = t1.childNodes.slice()
567+
const t2ChildNodes = t2.childNodes.slice()
566568
let shortest = Math.min(gaps1.length, gaps2.length)
567569
let destinationDifferent
568570
let toGroup
569571
let group
570572
let node
571573
let similarNode
572-
let testI
573574
const diffs = []
574-
575575
for (
576576
let index2 = 0, index1 = 0;
577577
index2 < shortest;
@@ -582,24 +582,25 @@ export class DiffFinder {
582582
(gaps1[index2] === true || gaps2[index2] === true)
583583
) {
584584
// pass
585-
} else if (gaps1[index2] === true) {
586-
node = t1.childNodes[index1]
585+
} else if (gaps1[index1] === true) {
586+
node = t1ChildNodes[index1]
587587
if (node.nodeName === "#text") {
588-
if (t2.childNodes[index2].nodeName === "#text") {
588+
if (t2ChildNodes[index2].nodeName === "#text") {
589589
if (
590590
(node as textDiffNodeType).data !==
591-
(t2.childNodes[index2] as textDiffNodeType).data
591+
(t2ChildNodes[index2] as textDiffNodeType).data
592592
) {
593-
testI = index1
593+
// Check whether a text node with the same value follows later on.
594+
let testI = index1
594595
while (
595-
t1.childNodes.length > testI + 1 &&
596-
t1.childNodes[testI + 1].nodeName === "#text"
596+
t1ChildNodes.length > testI + 1 &&
597+
t1ChildNodes[testI + 1].nodeName === "#text"
597598
) {
598599
testI += 1
599600
if (
600-
(t2.childNodes[index2] as textDiffNodeType)
601+
(t2ChildNodes[index2] as textDiffNodeType)
601602
.data ===
602-
(t1.childNodes[testI] as textDiffNodeType)
603+
(t1ChildNodes[testI] as textDiffNodeType)
603604
.data
604605
) {
605606
similarNode = true
@@ -616,7 +617,7 @@ export class DiffFinder {
616617
)
617618
.setValue(
618619
this.options._const.route,
619-
route.concat(index2)
620+
route.concat(index1)
620621
)
621622
.setValue(
622623
this.options._const.oldValue,
@@ -625,13 +626,14 @@ export class DiffFinder {
625626
.setValue(
626627
this.options._const.newValue,
627628
(
628-
t2.childNodes[
629+
t2ChildNodes[
629630
index2
630631
] as textDiffNodeType
631632
).data
632633
)
634+
// t1ChildNodes at position index1 is not up-to-date, but that does not matter as
635+
// index1 will increase +1
633636
)
634-
return diffs
635637
}
636638
}
637639
} else {
@@ -643,14 +645,40 @@ export class DiffFinder {
643645
)
644646
.setValue(
645647
this.options._const.route,
646-
route.concat(index2)
648+
route.concat(index1)
647649
)
648650
.setValue(this.options._const.value, node.data)
649651
)
650-
gaps1.splice(index2, 1)
652+
gaps1.splice(index1, 1)
653+
t1ChildNodes.splice(index1, 1)
651654
shortest = Math.min(gaps1.length, gaps2.length)
655+
index1 -= 1
652656
index2 -= 1
653657
}
658+
} else if (gaps2[index2] === true) {
659+
// both gaps1[index1] and gaps2[index2] are true.
660+
// We replace one element with another.
661+
diffs.push(
662+
new Diff()
663+
.setValue(
664+
this.options._const.action,
665+
this.options._const.replaceElement
666+
)
667+
.setValue(
668+
this.options._const.oldValue,
669+
cleanNode(node)
670+
)
671+
.setValue(
672+
this.options._const.newValue,
673+
cleanNode(t2ChildNodes[index2])
674+
)
675+
.setValue(
676+
this.options._const.route,
677+
route.concat(index1)
678+
)
679+
)
680+
// t1ChildNodes at position index1 is not up-to-date, but that does not matter as
681+
// index1 will increase +1
654682
} else {
655683
diffs.push(
656684
new Diff()
@@ -660,19 +688,21 @@ export class DiffFinder {
660688
)
661689
.setValue(
662690
this.options._const.route,
663-
route.concat(index2)
691+
route.concat(index1)
664692
)
665693
.setValue(
666694
this.options._const.element,
667695
cleanNode(node)
668696
)
669697
)
670-
gaps1.splice(index2, 1)
698+
gaps1.splice(index1, 1)
699+
t1ChildNodes.splice(index1, 1)
671700
shortest = Math.min(gaps1.length, gaps2.length)
701+
index1 -= 1
672702
index2 -= 1
673703
}
674704
} else if (gaps2[index2] === true) {
675-
node = t2.childNodes[index2]
705+
node = t2ChildNodes[index2]
676706
if (node.nodeName === "#text") {
677707
diffs.push(
678708
new Diff()
@@ -682,13 +712,17 @@ export class DiffFinder {
682712
)
683713
.setValue(
684714
this.options._const.route,
685-
route.concat(index2)
715+
route.concat(index1)
686716
)
687717
.setValue(this.options._const.value, node.data)
688718
)
689-
gaps1.splice(index2, 0, true)
719+
gaps1.splice(index1, 0, true)
720+
t1ChildNodes.splice(index1, 0, {
721+
nodeName: "#text",
722+
data: node.data,
723+
})
690724
shortest = Math.min(gaps1.length, gaps2.length)
691-
index1 -= 1
725+
//index1 += 1
692726
} else {
693727
diffs.push(
694728
new Diff()
@@ -698,35 +732,36 @@ export class DiffFinder {
698732
)
699733
.setValue(
700734
this.options._const.route,
701-
route.concat(index2)
735+
route.concat(index1)
702736
)
703737
.setValue(
704738
this.options._const.element,
705739
cleanNode(node)
706740
)
707741
)
708-
gaps1.splice(index2, 0, true)
742+
gaps1.splice(index1, 0, true)
743+
t1ChildNodes.splice(index1, 0, cleanNode(node))
709744
shortest = Math.min(gaps1.length, gaps2.length)
710-
index1 -= 1
745+
//index1 += 1
711746
}
712-
} else if (gaps1[index2] !== gaps2[index2]) {
747+
} else if (gaps1[index1] !== gaps2[index2]) {
713748
if (diffs.length > 0) {
714749
return diffs
715750
}
716751
// group relocation
717-
group = subtrees[gaps1[index2] as number]
752+
group = subtrees[gaps1[index1] as number]
718753
toGroup = Math.min(
719754
group.newValue,
720-
t1.childNodes.length - group.length
755+
t1ChildNodes.length - group.length
721756
)
722757
if (toGroup !== group.oldValue) {
723758
// Check whether destination nodes are different than originating ones.
724759
destinationDifferent = false
725760
for (let j = 0; j < group.length; j += 1) {
726761
if (
727762
!roughlyEqual(
728-
t1.childNodes[toGroup + j],
729-
t1.childNodes[group.oldValue + j],
763+
t1ChildNodes[toGroup + j],
764+
t1ChildNodes[group.oldValue + j],
730765
{},
731766
false,
732767
true

src/diffDOM/virtual/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export const cleanNode = (diffNode: diffNodeType) => {
8888
}
8989
diffNode = diffNode as elementDiffNodeType
9090
if (Object.prototype.hasOwnProperty.call(diffNode, "attributes")) {
91-
elementNode.attributes = diffNode.attributes
91+
elementNode.attributes = { ...diffNode.attributes }
9292
}
9393
if (Object.prototype.hasOwnProperty.call(diffNode, "checked")) {
9494
elementNode.checked = diffNode.checked

tests/basic.test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -583,11 +583,12 @@ describe("basic", () => {
583583
diffcap: 1000,
584584
}),
585585
divs = document.querySelectorAll("div")
586-
586+
let totalDiffs = 0
587587
for (let i = 0; i < divs.length; i += 2) {
588588
const diffs = dd.diff(divs[i], divs[i + 1])
589589
expect(diffs).not.toHaveLength(0)
590590
expect(diffs.length).toBeLessThanOrEqual(caps[i / 2])
591+
totalDiffs += diffs.length
591592
const t1 = divs[i].cloneNode(true)
592593
dd.apply(t1, diffs)
593594
expect(
@@ -599,5 +600,6 @@ describe("basic", () => {
599600
t1.isEqualNode(divs[i]) || t1.innerHTML === divs[i].innerHTML
600601
).toBe(true)
601602
}
603+
expect(totalDiffs).toBeLessThanOrEqual(408)
602604
})
603605
})

tests/string.test.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ const strings = [
3131
"<div><img><img><i></i></div>",
3232
"<div><i></i><img><img></div>",
3333

34-
'<div><span><b></b><img></span><img><span class="a"></span></div>',
35-
'<div><i></i><p id="a"></p><p class="a"></p><img><span class="a"></span></div>',
36-
3734
"<div><img><i></i><img></div>",
3835
"<div><i></i><img><img></div>",
3936

0 commit comments

Comments
 (0)