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.

222 lines
4.9 KiB

5 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. parentNode,
  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. parentNode,
  122. currentNodes,
  123. currentStart,
  124. i
  125. );
  126. remove(
  127. get,
  128. parentNode,
  129. currentNodes,
  130. i + futureChanges,
  131. currentEnd
  132. );
  133. return futureNodes;
  134. }
  135. }
  136. // common case with one replacement for many nodes
  137. // or many nodes replaced for a single one
  138. /* istanbul ignore else */
  139. if ((currentChanges < 2 || futureChanges < 2)) {
  140. append(
  141. get,
  142. parentNode,
  143. futureNodes,
  144. futureStart,
  145. futureEnd,
  146. get(currentNodes[currentStart], 0)
  147. );
  148. remove(
  149. get,
  150. parentNode,
  151. currentNodes,
  152. currentStart,
  153. currentEnd
  154. );
  155. return futureNodes;
  156. }
  157. // the half match diff part has been skipped in petit-dom
  158. // https://github.com/yelouafi/petit-dom/blob/bd6f5c919b5ae5297be01612c524c40be45f14a7/src/vdom.js#L391-L397
  159. // accordingly, I think it's safe to skip in here too
  160. // if one day it'll come out like the speediest thing ever to do
  161. // then I might add it in here too
  162. // Extra: before going too fancy, what about reversed lists ?
  163. // This should bail out pretty quickly if that's not the case.
  164. if (
  165. currentChanges === futureChanges &&
  166. isReversed(
  167. futureNodes,
  168. futureEnd,
  169. currentNodes,
  170. currentStart,
  171. currentEnd,
  172. compare
  173. )
  174. ) {
  175. append(
  176. get,
  177. parentNode,
  178. futureNodes,
  179. futureStart,
  180. futureEnd,
  181. next(get, currentNodes, currentEnd, currentLength, before)
  182. );
  183. return futureNodes;
  184. }
  185. // last resort through a smart diff
  186. smartDiff(
  187. get,
  188. parentNode,
  189. futureNodes,
  190. futureStart,
  191. futureEnd,
  192. futureChanges,
  193. currentNodes,
  194. currentStart,
  195. currentEnd,
  196. currentChanges,
  197. currentLength,
  198. compare,
  199. before
  200. );
  201. return futureNodes;
  202. };
  203. Object.defineProperty(exports, '__esModule', {value: true}).default = domdiff;