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.

223 lines
4.8 KiB

5 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. parentNode,
  66. currentNodes,
  67. currentStart,
  68. currentEnd
  69. );
  70. return futureNodes;
  71. }
  72. const currentChanges = currentEnd - currentStart;
  73. const futureChanges = futureEnd - futureStart;
  74. let i = -1;
  75. // 2 simple indels: the shortest sequence is a subsequence of the longest
  76. if (currentChanges < futureChanges) {
  77. i = indexOf(
  78. futureNodes,
  79. futureStart,
  80. futureEnd,
  81. currentNodes,
  82. currentStart,
  83. currentEnd,
  84. compare
  85. );
  86. // inner diff
  87. if (-1 < i) {
  88. append(
  89. get,
  90. parentNode,
  91. futureNodes,
  92. futureStart,
  93. i,
  94. get(currentNodes[currentStart], 0)
  95. );
  96. append(
  97. get,
  98. parentNode,
  99. futureNodes,
  100. i + currentChanges,
  101. futureEnd,
  102. next(get, currentNodes, currentEnd, currentLength, before)
  103. );
  104. return futureNodes;
  105. }
  106. }
  107. /* istanbul ignore else */
  108. else if (futureChanges < currentChanges) {
  109. i = indexOf(
  110. currentNodes,
  111. currentStart,
  112. currentEnd,
  113. futureNodes,
  114. futureStart,
  115. futureEnd,
  116. compare
  117. );
  118. // outer diff
  119. if (-1 < i) {
  120. remove(
  121. get,
  122. parentNode,
  123. currentNodes,
  124. currentStart,
  125. i
  126. );
  127. remove(
  128. get,
  129. parentNode,
  130. currentNodes,
  131. i + futureChanges,
  132. currentEnd
  133. );
  134. return futureNodes;
  135. }
  136. }
  137. // common case with one replacement for many nodes
  138. // or many nodes replaced for a single one
  139. /* istanbul ignore else */
  140. if ((currentChanges < 2 || futureChanges < 2)) {
  141. append(
  142. get,
  143. parentNode,
  144. futureNodes,
  145. futureStart,
  146. futureEnd,
  147. get(currentNodes[currentStart], 0)
  148. );
  149. remove(
  150. get,
  151. parentNode,
  152. currentNodes,
  153. currentStart,
  154. currentEnd
  155. );
  156. return futureNodes;
  157. }
  158. // the half match diff part has been skipped in petit-dom
  159. // https://github.com/yelouafi/petit-dom/blob/bd6f5c919b5ae5297be01612c524c40be45f14a7/src/vdom.js#L391-L397
  160. // accordingly, I think it's safe to skip in here too
  161. // if one day it'll come out like the speediest thing ever to do
  162. // then I might add it in here too
  163. // Extra: before going too fancy, what about reversed lists ?
  164. // This should bail out pretty quickly if that's not the case.
  165. if (
  166. currentChanges === futureChanges &&
  167. isReversed(
  168. futureNodes,
  169. futureEnd,
  170. currentNodes,
  171. currentStart,
  172. currentEnd,
  173. compare
  174. )
  175. ) {
  176. append(
  177. get,
  178. parentNode,
  179. futureNodes,
  180. futureStart,
  181. futureEnd,
  182. next(get, currentNodes, currentEnd, currentLength, before)
  183. );
  184. return futureNodes;
  185. }
  186. // last resort through a smart diff
  187. smartDiff(
  188. get,
  189. parentNode,
  190. futureNodes,
  191. futureStart,
  192. futureEnd,
  193. futureChanges,
  194. currentNodes,
  195. currentStart,
  196. currentEnd,
  197. currentChanges,
  198. currentLength,
  199. compare,
  200. before
  201. );
  202. return futureNodes;
  203. };
  204. export default domdiff;