You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

219 lines
4.7 KiB

4 years ago
  1. /*! (c) 2018 Andrea Giammarchi (ISC) */
  2. import {
  3. eqeq, identity, indexOf, isReversed, next,
  4. append, remove,
  5. smartDiff
  6. } from './utils.js';
  7. const domdiff = (
  8. parentNode, // where changes happen
  9. currentNodes, // Array of current items/nodes
  10. futureNodes, // Array of future items/nodes
  11. options // optional object with one of the following properties
  12. // before: domNode
  13. // compare(generic, generic) => true if same generic
  14. // node(generic) => Node
  15. ) => {
  16. if (!options)
  17. options = {};
  18. const compare = options.compare || eqeq;
  19. const get = options.node || identity;
  20. const before = options.before == null ? null : get(options.before, 0);
  21. const currentLength = currentNodes.length;
  22. let currentEnd = currentLength;
  23. let currentStart = 0;
  24. let futureEnd = futureNodes.length;
  25. let futureStart = 0;
  26. // common prefix
  27. while (
  28. currentStart < currentEnd &&
  29. futureStart < futureEnd &&
  30. compare(currentNodes[currentStart], futureNodes[futureStart])
  31. ) {
  32. currentStart++;
  33. futureStart++;
  34. }
  35. // common suffix
  36. while (
  37. currentStart < currentEnd &&
  38. futureStart < futureEnd &&
  39. compare(currentNodes[currentEnd - 1], futureNodes[futureEnd - 1])
  40. ) {
  41. currentEnd--;
  42. futureEnd--;
  43. }
  44. const currentSame = currentStart === currentEnd;
  45. const futureSame = futureStart === futureEnd;
  46. // same list
  47. if (currentSame && futureSame)
  48. return futureNodes;
  49. // only stuff to add
  50. if (currentSame && futureStart < futureEnd) {
  51. append(
  52. get,
  53. parentNode,
  54. futureNodes,
  55. futureStart,
  56. futureEnd,
  57. next(get, currentNodes, currentStart, currentLength, before)
  58. );
  59. return futureNodes;
  60. }
  61. // only stuff to remove
  62. if (futureSame && currentStart < currentEnd) {
  63. remove(
  64. get,
  65. currentNodes,
  66. currentStart,
  67. currentEnd
  68. );
  69. return futureNodes;
  70. }
  71. const currentChanges = currentEnd - currentStart;
  72. const futureChanges = futureEnd - futureStart;
  73. let i = -1;
  74. // 2 simple indels: the shortest sequence is a subsequence of the longest
  75. if (currentChanges < futureChanges) {
  76. i = indexOf(
  77. futureNodes,
  78. futureStart,
  79. futureEnd,
  80. currentNodes,
  81. currentStart,
  82. currentEnd,
  83. compare
  84. );
  85. // inner diff
  86. if (-1 < i) {
  87. append(
  88. get,
  89. parentNode,
  90. futureNodes,
  91. futureStart,
  92. i,
  93. get(currentNodes[currentStart], 0)
  94. );
  95. append(
  96. get,
  97. parentNode,
  98. futureNodes,
  99. i + currentChanges,
  100. futureEnd,
  101. next(get, currentNodes, currentEnd, currentLength, before)
  102. );
  103. return futureNodes;
  104. }
  105. }
  106. /* istanbul ignore else */
  107. else if (futureChanges < currentChanges) {
  108. i = indexOf(
  109. currentNodes,
  110. currentStart,
  111. currentEnd,
  112. futureNodes,
  113. futureStart,
  114. futureEnd,
  115. compare
  116. );
  117. // outer diff
  118. if (-1 < i) {
  119. remove(
  120. get,
  121. currentNodes,
  122. currentStart,
  123. i
  124. );
  125. remove(
  126. get,
  127. currentNodes,
  128. i + futureChanges,
  129. currentEnd
  130. );
  131. return futureNodes;
  132. }
  133. }
  134. // common case with one replacement for many nodes
  135. // or many nodes replaced for a single one
  136. /* istanbul ignore else */
  137. if ((currentChanges < 2 || futureChanges < 2)) {
  138. append(
  139. get,
  140. parentNode,
  141. futureNodes,
  142. futureStart,
  143. futureEnd,
  144. get(currentNodes[currentStart], 0)
  145. );
  146. remove(
  147. get,
  148. currentNodes,
  149. currentStart,
  150. currentEnd
  151. );
  152. return futureNodes;
  153. }
  154. // the half match diff part has been skipped in petit-dom
  155. // https://github.com/yelouafi/petit-dom/blob/bd6f5c919b5ae5297be01612c524c40be45f14a7/src/vdom.js#L391-L397
  156. // accordingly, I think it's safe to skip in here too
  157. // if one day it'll come out like the speediest thing ever to do
  158. // then I might add it in here too
  159. // Extra: before going too fancy, what about reversed lists ?
  160. // This should bail out pretty quickly if that's not the case.
  161. if (
  162. currentChanges === futureChanges &&
  163. isReversed(
  164. futureNodes,
  165. futureEnd,
  166. currentNodes,
  167. currentStart,
  168. currentEnd,
  169. compare
  170. )
  171. ) {
  172. append(
  173. get,
  174. parentNode,
  175. futureNodes,
  176. futureStart,
  177. futureEnd,
  178. next(get, currentNodes, currentEnd, currentLength, before)
  179. );
  180. return futureNodes;
  181. }
  182. // last resort through a smart diff
  183. smartDiff(
  184. get,
  185. parentNode,
  186. futureNodes,
  187. futureStart,
  188. futureEnd,
  189. futureChanges,
  190. currentNodes,
  191. currentStart,
  192. currentEnd,
  193. currentChanges,
  194. currentLength,
  195. compare,
  196. before
  197. );
  198. return futureNodes;
  199. };
  200. export default domdiff;