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.

179 lines
4.7 KiB

4 years ago
  1. # domdiff
  2. [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat)](https://github.com/WebReflection/donate) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/domdiff/badge.svg?branch=master)](https://coveralls.io/github/WebReflection/domdiff?branch=master) [![Build Status](https://travis-ci.org/WebReflection/domdiff.svg?branch=master)](https://travis-ci.org/WebReflection/domdiff) [![License: ISC](https://img.shields.io/badge/License-ISC-yellow.svg)](https://opensource.org/licenses/ISC)
  3. A vDOM-less implementation of the [petit-dom](https://github.com/yelouafi/petit-dom) diffing logic, at the core of [hyperHTML](https://github.com/WebReflection/hyperHTML).
  4. ### V2 breaking change
  5. * the good old snabdom diff logic has been 100% replaced
  6. * lists with `null` or `undefined` nodes are not allowed anymore
  7. <sup><sub>... but I guess having null nodes in the equation was quite possibly a bad idea in the first place ...</sub></sup>
  8. #### V2 Diffing Strategies:
  9. * common prefixes
  10. * common suffixes
  11. * skip same lists
  12. * add boundaries
  13. * remove boundaries
  14. * simple sub-sequences insertions and removals
  15. * one to many and many to one replacements
  16. * fast inverted list swap
  17. * O(ND) algo with a limit of 50 attempts
  18. * last fallback with a simplified Hunt Szymanski algorithm
  19. The current goal is to have in about 1K the best DOM diffing library out there.
  20. #### V1 breaking change
  21. The signature has moved from `parent, current[], future[], getNode(), beforeNode` to `parent, current[], future[], {before, compare(), node()}`.
  22. ### Signature
  23. ```js
  24. futureNodes = domdiff(
  25. parentNode, // where changes happen
  26. currentNodes, // Array of current items/nodes
  27. futureNodes, // Array of future items/nodes (returned)
  28. options // optional object with one of the following properties
  29. // before: domNode
  30. // compare(generic, generic) => true if same generic
  31. // node(generic) => Node
  32. );
  33. ```
  34. ### How to import it:
  35. * via **CDN**, as global variable: `https://unpkg.com/domdiff`
  36. * via **ESM**, as external module: `https://unpkg.com/domdiff/esm/index.js`
  37. * via **CJS**: `const EventTarget = require('domdiff').default;` <sup><sub>( or `require('domdiff/cjs').default` )</sub></sup>
  38. * via bundlers/transpilers: `import domdiff from 'domdiff';` <sup><sub>( or `from 'domdiff/esm'` )</sub></sup>
  39. ### Example
  40. ```js
  41. var nodes = {
  42. a: document.createTextNode('a'),
  43. b: document.createTextNode('b'),
  44. c: document.createTextNode('c')
  45. };
  46. var parentNode = document.createElement('p');
  47. var childNodes = [nodes.a, nodes.c];
  48. parentNode.append(...childNodes);
  49. parentNode.textContent;
  50. // "ac"
  51. childNodes = domdiff(
  52. parentNode,
  53. childNodes,
  54. [nodes.a, nodes.b, nodes.c]
  55. );
  56. parentNode.textContent;
  57. // "abc"
  58. ```
  59. ### Compatibility:
  60. Every. JavaScript. Engine.
  61. ### A `{node: (generic, info) => node}` callback for complex data
  62. The optional `{node: (generic, info) => node}` is invoked per each operation on the DOM.
  63. This can be useful to represent node through wrappers, whenever that is needed.
  64. The passed `info` value can be:
  65. * `1` when the item/node is being appended
  66. * `0` when the item/node is being used as insert _before_ reference
  67. * `-0` when the item/node is being used as insert _after_ reference
  68. * `-1` when the item/node is being removed
  69. [Example](https://codepen.io/WebReflection/pen/bYJVPd?editors=0010)
  70. ```js
  71. function node(item, i) {
  72. // case removal or case after
  73. if ((1 / i) < 0) {
  74. // case removal
  75. if (i) {
  76. // if the item has more than a node
  77. // remove all other nodes at once
  78. if (item.length > 1) {
  79. const range = document.createRange();
  80. range.setStartBefore(item[1]);
  81. range.setEndAfter(item[item.length - 1]);
  82. range.deleteContents();
  83. }
  84. // return the first node to be removed
  85. return item[0];
  86. }
  87. // case after
  88. else {
  89. return item[item.length - 1];
  90. }
  91. }
  92. // case insert
  93. else if (i) {
  94. const fragment = document.createDocumentFragment();
  95. fragment.append(...item);
  96. return fragment;
  97. }
  98. // case before
  99. else {
  100. return item[0];
  101. }
  102. }
  103. const and = [document.createTextNode(' & ')];
  104. const Bob = [
  105. document.createTextNode('B'),
  106. document.createTextNode('o'),
  107. document.createTextNode('b')
  108. ];
  109. const Lucy = [
  110. document.createTextNode('L'),
  111. document.createTextNode('u'),
  112. document.createTextNode('c'),
  113. document.createTextNode('y')
  114. ];
  115. // clean the body for demo purpose
  116. document.body.textContent = '';
  117. let content = domdiff(
  118. document.body,
  119. [],
  120. [Bob, and, Lucy],
  121. {node}
  122. );
  123. // ... later on ...
  124. content = domdiff(
  125. document.body,
  126. content,
  127. [Lucy, and, Bob],
  128. {node}
  129. );
  130. // clean up
  131. domdiff(
  132. document.body,
  133. content,
  134. [],
  135. {node}
  136. );
  137. ```