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.

218 lines
4.8 KiB

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