├── .editorconfig
├── .gitignore
├── .jshintrc
├── README.md
├── dist
├── all.js
├── all.min.js
├── vd.js
└── vd.min.js
├── gulpfile.js
├── index.js
├── lib
└── operate.js
├── package.json
├── patch.js
├── q-vd.js
├── render.js
├── test
├── cases
│ └── test.js
├── index.html
├── mocha.opts
└── test.js
├── vd.js
└── webpack.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .monitor
3 | .*.swp
4 | .nodemonignore
5 | releases
6 | *.log
7 | *.err
8 | fleet.json
9 | public/browserify
10 | bin/*.json
11 | .bin
12 | build
13 | compile
14 | .lock-wscript
15 | coverage
16 | node_modules
17 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "asi": true,
3 | "bitwise": false,
4 | "camelcase": true,
5 | "curly": false,
6 | "eqeqeq": true,
7 | "eqnull": true,
8 | "forin": false,
9 | "immed": true,
10 | "indent": 4,
11 | "latedef": "nofunc",
12 | "newcap": false,
13 | "noarg": true,
14 | "nonew": true,
15 | "plusplus": false,
16 | "proto": true,
17 | "quotmark": false,
18 | "regexp": false,
19 | "undef": true,
20 | "unused": true,
21 | "strict": false,
22 | "trailing": true,
23 | "noempty": true,
24 | "maxdepth": 5,
25 | "maxparams": 5,
26 | "globals": {
27 | "console": true,
28 | "Buffer": true,
29 | "setTimeout": true,
30 | "clearTimeout": true,
31 | "setInterval": true,
32 | "clearInterval": true,
33 | "require": false,
34 | "module": false,
35 | "exports": true,
36 | "global": false,
37 | "process": true,
38 | "__dirname": false,
39 | "__filename": false
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | qvd
2 | ====
3 |
4 | > 简化版Virtual DOM,用于Mobile页面渲染。
5 |
6 | ### Example
7 |
8 | ```javascript
9 | var h = require('qvd').h
10 | , diff = require('qvd').diff;
11 |
12 | var a = h('div', { style: { textAlign: 'center' }, [h('text', 'hello')] })
13 | , b = h('div', { style: { borer: '1px' } });
14 |
15 | diff(a, b);
16 | ```
17 |
18 | ### API
19 |
20 | ```javascript
21 | /**
22 | * h(tagName, properties, children)
23 | * 创建一个节点
24 | * @param {String} tagName tag名
25 | * @param {Object} properties 属性对象
26 | * @param {Array} children 子节点数组
27 | */
28 | h('div', { style: { border: '1px' } }, [h('p')]);
29 | ```
30 |
31 | ```javascript
32 | /**
33 | * h('text', text)
34 | * 创建文字节点
35 | * @param {String} text 文字节点内容
36 | */
37 | h('text', 'hello world');
38 | ```
39 |
40 | ```javascript
41 | /**
42 | * render(vd)
43 | * 得到生成的html片段
44 | * @param {VD} vd
45 | * @returns {String}
46 | */
47 | //
48 | render(h('div', { style: { border: '1px' } }, [h('p')]))
49 | ```
50 |
51 | ```javasript
52 | /**
53 | * diff(a, b)
54 | * @param {VD} a 目前状态的a虚拟节点
55 | * @param {VD} b 要变成状态的b虚拟节点
56 | * @returns {Array} patches
57 | */
58 | diff(a, b);
59 | ```
60 |
61 | ```javascript
62 | /**
63 | * patch(patches, container)
64 | * @param {Array} patches diff方法返回的值
65 | * @param {Node} container a 虚拟节点的容器
66 | */
67 | // 节点变换,从a到b
68 | patch(patches, container);
69 | ```
70 |
71 | ### License
72 |
73 | Copyright (c) 2014 Matt-Esch.
74 |
75 | Copyright (c) 2015 Daniel Yang.
76 |
77 | Permission is hereby granted, free of charge, to any person obtaining a copy
78 | of this software and associated documentation files (the "Software"), to deal
79 | in the Software without restriction, including without limitation the rights
80 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
81 | copies of the Software, and to permit persons to whom the Software is
82 | furnished to do so, subject to the following conditions:
83 |
84 | The above copyright notice and this permission notice shall be included in
85 | all copies or substantial portions of the Software.
86 |
87 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
88 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
89 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
90 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
91 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
92 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
93 | THE SOFTWARE.
94 |
--------------------------------------------------------------------------------
/dist/all.js:
--------------------------------------------------------------------------------
1 | define("vd", [], function() { return /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 |
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 |
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId])
10 | /******/ return installedModules[moduleId].exports;
11 |
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ exports: {},
15 | /******/ id: moduleId,
16 | /******/ loaded: false
17 | /******/ };
18 |
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 |
22 | /******/ // Flag the module as loaded
23 | /******/ module.loaded = true;
24 |
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 |
29 |
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 |
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 |
36 | /******/ // __webpack_public_path__
37 | /******/ __webpack_require__.p = "";
38 |
39 | /******/ // Load entry module and return exports
40 | /******/ return __webpack_require__(0);
41 | /******/ })
42 | /************************************************************************/
43 | /******/ ([
44 | /* 0 */
45 | /***/ function(module, exports, __webpack_require__) {
46 |
47 | var vd = __webpack_require__(1)
48 | , render = __webpack_require__(2)
49 | , patch = __webpack_require__(3);
50 |
51 | module.exports = {
52 | diff: vd.diff,
53 | h: vd.h,
54 | render: render,
55 | patch: patch
56 | };
57 |
58 | /***/ },
59 | /* 1 */
60 | /***/ function(module, exports, __webpack_require__) {
61 |
62 | var OPERATE = __webpack_require__(4);
63 |
64 | function isText(node) {
65 | return node.tag === 'text';
66 | }
67 |
68 | function isEle(node) {
69 | return node.tag !== 'text';
70 | }
71 |
72 | function isObject(obj) {
73 | return typeof obj === 'object';
74 | }
75 |
76 | function remove(arr, index, key) {
77 | arr.splice(index, 1);
78 |
79 | return {
80 | from: index,
81 | key: key
82 | };
83 | }
84 |
85 | function walk(a, b, patches, index) {
86 | patches[index] = apply = patches[index] || [];
87 | // no need to walk
88 | if (b == null) {
89 | apply.push({
90 | from: a,
91 | operate: OPERATE.REMOVE
92 | });
93 | // a and b is element
94 | } else if(isEle(a) && isEle(b)) {
95 | if (a.tag === b.tag) {
96 | var diff = diffProps(a.props, b.props);
97 | if (diff) {
98 | apply.push({
99 | diff: diff,
100 | from: a,
101 | operate: OPERATE.PROPS
102 | });
103 | }
104 | diffChildren(a, b, patches, apply, index);
105 | } else {
106 | apply.push({
107 | from: b,
108 | to: a,
109 | operate: OPERATE.REPLACE
110 | });
111 | }
112 | // at least one is text
113 | } else {
114 | if (
115 | isEle(a) || isEle(b)
116 | || a.text !== b.text
117 | ) {
118 | apply.push({
119 | from: b,
120 | to: a,
121 | operate: OPERATE.REPLACE
122 | });
123 | }
124 | }
125 | }
126 |
127 | function diffProps(a, b, apply) {
128 | var key, aVal, bVal, diff;
129 |
130 | for (key in a) {
131 | if (!(key in b)) {
132 | diff = diff || {};
133 | diff[key] = undefined;
134 | continue;
135 | }
136 |
137 | aVal = a[key];
138 | bVal = b[key];
139 |
140 | if (aVal === bVal) {
141 | continue;
142 | } else if (isObject(aVal) && isObject(bVal)) {
143 | var objectDiff = diffProps(aVal, bVal);
144 | if (objectDiff) {
145 | diff = diff || {};
146 | diff[key] = objectDiff;
147 | }
148 | } else {
149 | diff = diff || {};
150 | diff[key] = bVal;
151 | }
152 | }
153 |
154 | for (key in b) {
155 | if (!(key in a)) {
156 | diff = diff || {};
157 | diff[key] = b[key];
158 | }
159 | }
160 |
161 | return diff;
162 | }
163 |
164 | function keyIndex(children) {
165 | var keys = {}
166 | , free = []
167 | , length = children.length
168 | , i = 0
169 | , child;
170 |
171 | for (; i < length; i++) {
172 | child = children[i];
173 |
174 | if (child.key) {
175 | keys[child.key] = i;
176 | } else {
177 | free.push(i);
178 | }
179 | }
180 |
181 | return {
182 | keys: keys, // A hash of key name to index
183 | free: free // An array of unkeyed item indices
184 | };
185 | }
186 |
187 | function reorder(aChildren, bChildren) {
188 | // O(M) time, O(M) memory
189 | var bChildIndex = keyIndex(bChildren)
190 | , bKeys = bChildIndex.keys
191 | , bFree = bChildIndex.free;
192 |
193 | if (bFree.length === bChildren.length) {
194 | return {
195 | children: bChildren,
196 | moves: null
197 | };
198 | }
199 |
200 | // O(N) time, O(N) memory
201 | var aChildIndex = keyIndex(aChildren)
202 | , aKeys = aChildIndex.keys
203 | , aFree = aChildIndex.free;
204 |
205 | if (aFree.length === aChildren.length) {
206 | return {
207 | children: bChildren,
208 | moves: null
209 | };
210 | }
211 |
212 | // O(MAX(N, M)) memory
213 | var newChildren = []
214 | , freeIndex = 0
215 | , freeCount = bFree.length
216 | , deletedItems = 0
217 | , aItem
218 | , itemIndex
219 | , i = 0
220 | , l = aChildren.length;
221 |
222 | // Iterate through a and match a node in b
223 | // O(N) time,
224 | for (; i < l; i++) {
225 | aItem = aChildren[i];
226 |
227 | if (aItem.key) {
228 | if (bKeys.hasOwnProperty(aItem.key)) {
229 | // Match up the old keys
230 | itemIndex = bKeys[aItem.key];
231 | newChildren.push(bChildren[itemIndex]);
232 | } else {
233 | // Remove old keyed items
234 | itemIndex = i - deletedItems++;
235 | newChildren.push(null);
236 | }
237 | } else {
238 | // Match the item in a with the next free item in b
239 | if (freeIndex < freeCount) {
240 | itemIndex = bFree[freeIndex++];
241 | newChildren.push(bChildren[itemIndex]);
242 | } else {
243 | // There are no free items in b to match with
244 | // the free items in a, so the extra free nodes
245 | // are deleted.
246 | itemIndex = i - deletedItems++;
247 | newChildren.push(null);
248 | }
249 | }
250 | }
251 |
252 | var lastFreeIndex = freeIndex >= bFree.length ?
253 | bChildren.length :
254 | bFree[freeIndex]
255 | , j = 0
256 | , newItem;
257 |
258 | l = bChildren.length;
259 |
260 | // Iterate through b and append any new keys
261 | // O(M) time
262 | for (; j < l; j++) {
263 | newItem = bChildren[j];
264 |
265 | if (newItem.key) {
266 | if (!aKeys.hasOwnProperty(newItem.key)) {
267 | // Add any new keyed items
268 | // We are adding new items to the end and then sorting them
269 | // in place. In future we should insert new items in place.
270 | newChildren.push(newItem);
271 | }
272 | } else if (j >= lastFreeIndex) {
273 | // Add any leftover non-keyed items
274 | newChildren.push(newItem);
275 | }
276 | }
277 |
278 | var simulate = newChildren.slice()
279 | , simulateIndex = 0
280 | , removes = []
281 | , inserts = []
282 | , simulateItem
283 | , wantedItem
284 | , k = 0;
285 |
286 | for (; k < l;) {
287 | wantedItem = bChildren[k];
288 | simulateItem = simulate[simulateIndex];
289 |
290 | // remove items
291 | while (simulateItem === null && simulate.length) {
292 | removes.push(remove(simulate, simulateIndex, null));
293 | simulateItem = simulate[simulateIndex];
294 | }
295 |
296 | if (!simulateItem || simulateItem.key !== wantedItem.key) {
297 | // if we need a key in this position...
298 | if (wantedItem.key) {
299 | if (simulateItem && simulateItem.key) {
300 | // if an insert doesn't put this key in place, it needs to move
301 | if (bKeys[simulateItem.key] !== k + 1) {
302 | removes.push(remove(simulate, simulateIndex, simulateItem.key));
303 | simulateItem = simulate[simulateIndex];
304 | // if the remove didn't put the wanted item in place, we need to insert it
305 | if (!simulateItem || simulateItem.key !== wantedItem.key) {
306 | inserts.push({key: wantedItem.key, to: k});
307 | // items are matching, so skip ahead
308 | } else {
309 | simulateIndex++
310 | }
311 | } else {
312 | inserts.push({key: wantedItem.key, to: k})
313 | }
314 | } else {
315 | inserts.push({key: wantedItem.key, to: k})
316 | }
317 | k++
318 | // a key in simulate has no matching wanted key, remove it
319 | } else if (simulateItem && simulateItem.key) {
320 | removes.push(remove(simulate, simulateIndex, simulateItem.key));
321 | }
322 | } else {
323 | simulateIndex++;
324 | k++;
325 | }
326 | }
327 |
328 | // remove all the remaining nodes from simulate
329 | while(simulateIndex < simulate.length) {
330 | simulateItem = simulate[simulateIndex];
331 | removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key));
332 | }
333 |
334 | // If the only moves we have are deletes then we can just
335 | // let the delete patch remove these items.
336 | if (removes.length === deletedItems && !inserts.length) {
337 | return {
338 | children: newChildren,
339 | moves: null
340 | };
341 | }
342 |
343 | return {
344 | children: newChildren,
345 | moves: {
346 | removes: removes,
347 | inserts: inserts
348 | }
349 | };
350 | }
351 |
352 | function diffChildren(a, b, patches, apply, index) {
353 | var aChildren = a.children
354 | , orderedSet = reorder(aChildren, b.children)
355 | , bChildren = orderedSet.children
356 | , aLen = aChildren.length
357 | , bLen = bChildren.length
358 | , len = aLen > bLen ? aLen : bLen
359 | , i = 0
360 | , leftNode
361 | , rightNode;
362 |
363 | for (var i = 0; i < len; i++) {
364 | leftNode = aChildren[i];
365 | rightNode = bChildren[i];
366 | index += 1;
367 |
368 | if (!leftNode) {
369 | if (rightNode) {
370 | apply.push({
371 | from: rightNode,
372 | to: a,
373 | operate: OPERATE.INSERT
374 | });
375 | }
376 | } else {
377 | walk(leftNode, rightNode, patches, index)
378 | }
379 | }
380 |
381 | if (orderedSet.moves) {
382 | apply.push({
383 | operate: OPERATE.ORDER,
384 | from: orderedSet.moves,
385 | to: a
386 | });
387 | }
388 |
389 | return apply;
390 | }
391 |
392 | /**
393 | * h(tagName, props, children)
394 | * h('text', 'hello')
395 | * {
396 | * tag: 'div',
397 | * children: [],
398 | * props: {}
399 | * }
400 | */
401 | function h(tagName, props, children) {
402 | props = props || {};
403 | children = children || [];
404 | var key;
405 | if (tagName === 'text') {
406 | return {
407 | tag: tagName,
408 | text: props
409 | };
410 | } else {
411 | if ('key' in props) {
412 | key = props.key;
413 | delete props['key'];
414 | }
415 | for (var i = 0, l = children.length; i < l; i++) {
416 | // text
417 | typeof children[i] === 'string' &&
418 | (children[i] = h('text', children[i]));
419 | }
420 | return {
421 | tag: tagName,
422 | props: props,
423 | children: children,
424 | key: key
425 | };
426 | }
427 | }
428 |
429 | /**
430 | * diff
431 | * @param {VD} a
432 | * @param {VD} b
433 | */
434 | function diff(a, b) {
435 | var patches = {}, res = [];
436 | res.a = a;
437 | walk(a, b, patches, 0);
438 | for (var i = 0; patches[i]; i++) {
439 | patches[i].length &&
440 | res.push(patches[i])
441 | }
442 | return res;
443 | }
444 |
445 | module.exports = {
446 | h: h,
447 | diff: diff
448 | };
449 |
450 | /***/ },
451 | /* 2 */
452 | /***/ function(module, exports, __webpack_require__) {
453 |
454 | var singleTag = {
455 | area: true,
456 | base: true,
457 | basefont: true,
458 | br: true,
459 | col: true,
460 | command: true,
461 | embed: true,
462 | frame: true,
463 | hr: true,
464 | img: true,
465 | input: true,
466 | isindex: true,
467 | keygen: true,
468 | link: true,
469 | meta: true,
470 | param: true,
471 | source: true,
472 | track: true,
473 | wbr: true,
474 | }
475 | , booleanAttributes = {
476 | allowfullscreen: true,
477 | async: true,
478 | autofocus: true,
479 | autoplay: true,
480 | checked: true,
481 | controls: true,
482 | default: true,
483 | defer: true,
484 | disabled: true,
485 | hidden: true,
486 | ismap: true,
487 | loop: true,
488 | multiple: true,
489 | muted: true,
490 | open: true,
491 | readonly: true,
492 | required: true,
493 | reversed: true,
494 | scoped: true,
495 | seamless: true,
496 | selected: true,
497 | typemustmatch: true
498 | }
499 | , REG_CAP = /[A-Z]/g;
500 |
501 | function isEle(node) {
502 | return node.tag !== 'text';
503 | }
504 |
505 | function formatStyle(style) {
506 | var key, value
507 | , output = 'style="';
508 |
509 | for (key in style) {
510 | value = style[key];
511 |
512 | key = key.replace(REG_CAP, function (cap) {
513 | return '-' + cap.toLowerCase();
514 | });
515 |
516 | output += key + ': ' + value + ';';
517 | }
518 |
519 | return output + '"';
520 | }
521 |
522 | function formatProps(props) {
523 | if (!props) return;
524 |
525 | var output = ''
526 | , key
527 | , value;
528 |
529 | for (key in props) {
530 | value = props[key];
531 |
532 | if (output) {
533 | output += ' ';
534 | }
535 |
536 | if (!value && booleanAttributes[key]) {
537 | output += key;
538 | } else if (key === 'style') {
539 | output += formatStyle(value);
540 | } else {
541 | output += key + '="' + value + '"';
542 | }
543 | }
544 | return output;
545 | }
546 |
547 | function renderTag(vd) {
548 | var tag = '<'+ vd.tag
549 | , props = formatProps(vd.props);
550 |
551 | if (props) {
552 | tag += ' ' + props;
553 | }
554 |
555 | tag += '>';
556 |
557 | if (vd.children) {
558 | tag += render(vd.children);
559 | }
560 |
561 | if (!singleTag[vd.tag]) {
562 | tag += '' + vd.tag + '>';
563 | }
564 |
565 | return tag;
566 | }
567 |
568 | function renderText(vd) {
569 | return vd.text;
570 | }
571 |
572 |
573 | function render(vds) {
574 | !Array.isArray(vds) &&
575 | (vds = [vds]);
576 |
577 | var output = ''
578 | , l = vds.length
579 | , vd;
580 |
581 | for (var i = 0; i < l; i++) {
582 | vd = vds[i]
583 |
584 | if (isEle(vd)) {
585 | output += renderTag(vd);
586 | } else {
587 | output += renderText(vd);
588 | }
589 | }
590 | return output;
591 | }
592 |
593 | module.exports = render;
594 |
595 | /***/ },
596 | /* 3 */
597 | /***/ function(module, exports, __webpack_require__) {
598 |
599 | // TODO in the rough
600 | var OPERATE = __webpack_require__(4)
601 | , DEAL = {};
602 |
603 | function isText(vd) {
604 | return vd.tag === 'text';
605 | }
606 |
607 | function getVDByKey(vds, key, cache) {
608 | cache = cache || {};
609 | if (cache[key]) return cache[key];
610 | for (var i = 0, l = vds.length; i < l; i++) {
611 | if (vds[i].key === key) return (cache[key] = vds[i]);
612 | }
613 | }
614 |
615 | function find(vd, vds) {
616 | var i = vds.indexOf(vd)
617 | , res, tmp;
618 | if (!(~i)) {
619 | vds.every(function (item, j) {
620 | if (!item.children || !item.children.length) return true;
621 | res = find(vd, item.children);
622 | if (res) {
623 | tmp = res;
624 | res = [j];
625 | [].push.apply(res, tmp);
626 | return false;
627 | } else {
628 | return true;
629 | }
630 | });
631 | return res;
632 | }
633 | return [i];
634 | }
635 |
636 | function getRef(vd, root, a) {
637 | var ref = vd.ref;
638 | if (ref) return ref;
639 | if (!root || !a) throw new Error('Should bind the reference node.');
640 | var indexes = find(vd, [a]);
641 | if (!indexes) throw new Error('Couldn\'t find the reference node.');
642 | ref = root.childNodes[0];
643 | for (var i = 1, l = indexes.length; i < l; i++) {
644 | ref = ref.childNodes[indexes[i]];
645 | }
646 | vd.ref = ref;
647 | return ref;
648 | }
649 |
650 | function setProps(to, from, node) {
651 | for (key in from) {
652 | // style
653 | if (key === 'style') {
654 | style = node.style;
655 | value = from[key];
656 | if (!value) {
657 | style.cssText = '';
658 | to.style = {};
659 | } else {
660 | value = from[key];
661 | for (i in value) {
662 | if (value[i] === undefined) {
663 | style[i] = '';
664 | delete to.style[i];
665 | } else {
666 | style[i] = value[i];
667 | to.style[i] = value[i];
668 | }
669 | }
670 | }
671 | // property
672 | } else if (key in node) {
673 | value = from[key];
674 | type = typeof node[key];
675 | if (value === undefined) {
676 | type === 'string' ?
677 | (node[key] = '') :
678 | (node[key] = null);
679 | delete to[key];
680 | } else {
681 | node[key] = to[key] = value;
682 | }
683 | // attribute
684 | } else {
685 | value = from[key];
686 | if (value === undefined) {
687 | node.removeAttribute(key);
688 | delete to[key];
689 | } else {
690 | node.setAttribute(key, value);
691 | to[key] = value;
692 | }
693 | }
694 | }
695 | }
696 |
697 | function create(vds, parent) {
698 | !Array.isArray(vds) && (vds = [vds]);
699 | parent = parent || document.createDocumentFragment();
700 | var node;
701 | vds.forEach(function (vd) {
702 | if (isText(vd)) {
703 | node = document.createTextNode(vd.text);
704 | } else {
705 | node = document.createElement(vd.tag);
706 | }
707 | parent.appendChild(node);
708 | vd.children && vd.children.length &&
709 | create(vd.children, node);
710 |
711 | vd.props &&
712 | setProps({ style: {} }, vd.props, node);
713 | });
714 | return parent;
715 | }
716 |
717 | function splice(vd, a, replacement) {
718 | var indexes, i, l;
719 | indexes = find(vd, [a]);
720 | for (i = 1, l = indexes.length - 1; i < l; i++) {
721 | a = a.children[indexes[i]];
722 | }
723 | replacement ?
724 | a.children.splice(indexes[l], 1, replacement) :
725 | a.children.splice(indexes[l], 1);
726 | }
727 |
728 | DEAL[OPERATE.REMOVE] = function (item, root, a) {
729 | var node = getRef(item.from, root, a);
730 | node.parentNode.removeChild(node);
731 | item.from.ref = null;
732 | splice(item.from, a);
733 | };
734 |
735 | DEAL[OPERATE.INSERT] = function (item, root, a) {
736 | var node = create(item.from)
737 | , parent = getRef(item.to, root, a);
738 | parent.appendChild(node);
739 | item.to.children.push(item.from);
740 | };
741 |
742 | DEAL[OPERATE.REPLACE] = function (item, root, a) {
743 | var node = create(item.from)
744 | , target = getRef(item.to, root, a)
745 | , indexes
746 | , i
747 | , l;
748 | target.parentNode.replaceChild(node, target);
749 | item.to.ref = null;
750 | splice(item.to, a, item.from);
751 | };
752 |
753 | DEAL[OPERATE.ORDER] = function (item, root, a) {
754 | var to = item.to
755 | , from = item.from
756 | , children = to.children
757 | , parent = getRef(to, root, a)
758 | , grandpa = parent.parentNode
759 | , removes = from.removes
760 | , inserts = from.inserts
761 | , cache = {};
762 |
763 | removes.forEach(function (order) {
764 | var vd = getVDByKey(children, order.key, cache)
765 | , node = getRef(vd, grandpa, to);
766 | parent.removeChild(node);
767 | splice(vd, to);
768 | });
769 | inserts.forEach(function (order) {
770 | var vd = getVDByKey(children, order.key, cache)
771 | , node = getRef(vd, grandpa, to)
772 | , before = getRef(children[order.to], grandpa, to);
773 | parent.insertBefore(node, before);
774 | children.splice(order.to, 0, vd);
775 | });
776 | cache = null;
777 | };
778 |
779 | DEAL[OPERATE.PROPS] = function (item, root, a) {
780 | var props = item.from.props
781 | , node = getRef(item.from, root, a)
782 | , diff = item.diff
783 | , key, value, i, l, style, type;
784 | setProps(props, diff, node);
785 | };
786 |
787 | function patch(patches, root) {
788 | var i = 0, l = patches.length
789 | , a = patches.a;
790 | root = root || a.hook;
791 | for (; i < l; i++) {
792 | patches[i].forEach(function (item) {
793 | DEAL[item.operate](item, root, a);
794 | });
795 | }
796 | }
797 |
798 | module.exports = patch;
799 |
800 | /***/ },
801 | /* 4 */
802 | /***/ function(module, exports, __webpack_require__) {
803 |
804 | module.exports = {
805 | REMOVE: 1,
806 | INSERT: 2,
807 | REPLACE: 3,
808 | ORDER: 4,
809 | PROPS: 5
810 | };
811 |
812 | /***/ }
813 | /******/ ])});;
--------------------------------------------------------------------------------
/dist/all.min.js:
--------------------------------------------------------------------------------
1 | define("vd",[],function(){return function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports,o,o.exports,r),o.loaded=!0,o.exports}var t={};return r.m=e,r.c=t,r.p="",r(0)}([function(e,r,t){var n=t(1),o=t(2),l=t(3);e.exports={diff:n.diff,h:n.h,render:o,patch:l}},function(e,r,t){function n(e){return"text"!==e.tag}function o(e){return"object"==typeof e}function l(e,r,t){return e.splice(r,1),{from:r,key:t}}function i(e,r,t,o){if(t[o]=apply=t[o]||[],null==r)apply.push({from:e,operate:h.REMOVE});else if(n(e)&&n(r))if(e.tag===r.tag){var l=u(e.props,r.props);l&&apply.push({diff:l,from:e,operate:h.PROPS}),s(e,r,t,apply,o)}else apply.push({from:r,to:e,operate:h.REPLACE});else(n(e)||n(r)||e.text!==r.text)&&apply.push({from:r,to:e,operate:h.REPLACE})}function u(e,r){var t,n,l,i;for(t in e)if(t in r){if(n=e[t],l=r[t],n!==l)if(o(n)&&o(l)){var f=u(n,l);f&&(i=i||{},i[t]=f)}else i=i||{},i[t]=l}else i=i||{},i[t]=void 0;for(t in r)t in e||(i=i||{},i[t]=r[t]);return i}function f(e){for(var r,t={},n=[],o=e.length,l=0;o>l;l++)r=e[l],r.key?t[r.key]=l:n.push(l);return{keys:t,free:n}}function a(e,r){var t=f(r),n=t.keys,o=t.free;if(o.length===r.length)return{children:r,moves:null};var i=f(e),u=i.keys,a=i.free;if(a.length===e.length)return{children:r,moves:null};for(var s,c,p=[],h=0,y=o.length,d=0,v=0,k=e.length;k>v;v++)s=e[v],s.key?n.hasOwnProperty(s.key)?(c=n[s.key],p.push(r[c])):(c=v-d++,p.push(null)):y>h?(c=o[h++],p.push(r[c])):(c=v-d++,p.push(null));var m,g=h>=o.length?r.length:o[h],E=0;for(k=r.length;k>E;E++)m=r[E],m.key?u.hasOwnProperty(m.key)||p.push(m):E>=g&&p.push(m);for(var x,R,O=p.slice(),P=0,A=[],C=[],b=0;k>b;){for(R=r[b],x=O[P];null===x&&O.length;)A.push(l(O,P,null)),x=O[P];x&&x.key===R.key?(P++,b++):R.key?(x&&x.key&&n[x.key]!==b+1?(A.push(l(O,P,x.key)),x=O[P],x&&x.key===R.key?P++:C.push({key:R.key,to:b})):C.push({key:R.key,to:b}),b++):x&&x.key&&A.push(l(O,P,x.key))}for(;Py?p:y,v=0,v=0;d>v;v++)l=f[v],u=c[v],o+=1,l?i(l,u,t,o):u&&n.push({from:u,to:e,operate:h.INSERT});return s.moves&&n.push({operate:h.ORDER,from:s.moves,to:e}),n}function c(e,r,t){r=r||{},t=t||[];var n;if("text"===e)return{tag:e,text:r};"key"in r&&(n=r.key,delete r.key);for(var o=0,l=t.length;l>o;o++)"string"==typeof t[o]&&(t[o]=c("text",t[o]));return{tag:e,props:r,children:t,key:n}}function p(e,r){var t={},n=[];n.a=e,i(e,r,t,0);for(var o=0;t[o];o++)t[o].length&&n.push(t[o]);return n}var h=t(4);e.exports={h:c,diff:p}},function(e){function r(e){return"text"!==e.tag}function t(e){var r,t,n='style="';for(r in e)t=e[r],r=r.replace(a,function(e){return"-"+e.toLowerCase()}),n+=r+": "+t+";";return n+'"'}function n(e){if(e){var r,n,o="";for(r in e)n=e[r],o&&(o+=" "),o+=!n&&f[r]?r:"style"===r?t(n):r+'="'+n+'"';return o}}function o(e){var r="<"+e.tag,t=n(e.props);return t&&(r+=" "+t),r+=">",e.children&&(r+=i(e.children)),u[e.tag]||(r+=""+e.tag+">"),r}function l(e){return e.text}function i(e){!Array.isArray(e)&&(e=[e]);for(var t,n="",i=e.length,u=0;i>u;u++)t=e[u],n+=r(t)?o(t):l(t);return n}var u={area:!0,base:!0,basefont:!0,br:!0,col:!0,command:!0,embed:!0,frame:!0,hr:!0,img:!0,input:!0,isindex:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0},f={allowfullscreen:!0,async:!0,autofocus:!0,autoplay:!0,checked:!0,controls:!0,"default":!0,defer:!0,disabled:!0,hidden:!0,ismap:!0,loop:!0,multiple:!0,muted:!0,open:!0,readonly:!0,required:!0,reversed:!0,scoped:!0,seamless:!0,selected:!0,typemustmatch:!0},a=/[A-Z]/g;e.exports=i},function(e,r,t){function n(e){return"text"===e.tag}function o(e,r,t){if(t=t||{},t[r])return t[r];for(var n=0,o=e.length;o>n;n++)if(e[n].key===r)return t[r]=e[n]}function l(e,r){var t,n,o=r.indexOf(e);return~o?[o]:(r.every(function(r,o){return r.children&&r.children.length?(t=l(e,r.children),t?(n=t,t=[o],[].push.apply(t,n),!1):!0):!0}),t)}function u(e,r,t){var n=e.ref;if(n)return n;if(!r||!t)throw new Error("Should bind the reference node.");var o=l(e,[t]);if(!o)throw new Error("Couldn't find the reference node.");n=r.childNodes[0];for(var i=1,u=o.length;u>i;i++)n=n.childNodes[o[i]];return e.ref=n,n}function f(e,r,t){for(key in r)if("style"===key)if(style=t.style,value=r[key],value){value=r[key];for(i in value)void 0===value[i]?(style[i]="",delete e.style[i]):(style[i]=value[i],e.style[i]=value[i])}else style.cssText="",e.style={};else key in t?(value=r[key],type=typeof t[key],void 0===value?(t[key]="string"===type?"":null,delete e[key]):t[key]=e[key]=value):(value=r[key],void 0===value?(t.removeAttribute(key),delete e[key]):(t.setAttribute(key,value),e[key]=value))}function a(e,r){!Array.isArray(e)&&(e=[e]),r=r||document.createDocumentFragment();var t;return e.forEach(function(e){t=n(e)?document.createTextNode(e.text):document.createElement(e.tag),r.appendChild(t),e.children&&e.children.length&&a(e.children,t),e.props&&f({style:{}},e.props,t)}),r}function s(e,r,t){var n,o,i;for(n=l(e,[r]),o=1,i=n.length-1;i>o;o++)r=r.children[n[o]];t?r.children.splice(n[i],1,t):r.children.splice(n[i],1)}function c(e,r){var t=0,n=e.length,o=e.a;for(r=r||o.hook;n>t;t++)e[t].forEach(function(e){h[e.operate](e,r,o)})}var p=t(4),h={};h[p.REMOVE]=function(e,r,t){var n=u(e.from,r,t);n.parentNode.removeChild(n),e.from.ref=null,s(e.from,t)},h[p.INSERT]=function(e,r,t){var n=a(e.from),o=u(e.to,r,t);o.appendChild(n),e.to.children.push(e.from)},h[p.REPLACE]=function(e,r,t){var n=a(e.from),o=u(e.to,r,t);o.parentNode.replaceChild(n,o),e.to.ref=null,s(e.to,t,e.from)},h[p.ORDER]=function(e,r,t){var n=e.to,l=e.from,i=n.children,f=u(n,r,t),a=f.parentNode,c=l.removes,p=l.inserts,h={};c.forEach(function(e){var r=o(i,e.key,h),t=u(r,a,n);f.removeChild(t),s(r,n)}),p.forEach(function(e){var r=o(i,e.key,h),t=u(r,a,n),l=u(i[e.to],a,n);f.insertBefore(t,l),i.splice(e.to,0,r)}),h=null},h[p.PROPS]=function(e,r,t){var n=e.from.props,o=u(e.from,r,t),l=e.diff;f(n,l,o)},e.exports=c},function(e){e.exports={REMOVE:1,INSERT:2,REPLACE:3,ORDER:4,PROPS:5}}])});
--------------------------------------------------------------------------------
/dist/vd.js:
--------------------------------------------------------------------------------
1 | define("vd", [], function() { return /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 |
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 |
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId])
10 | /******/ return installedModules[moduleId].exports;
11 |
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ exports: {},
15 | /******/ id: moduleId,
16 | /******/ loaded: false
17 | /******/ };
18 |
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 |
22 | /******/ // Flag the module as loaded
23 | /******/ module.loaded = true;
24 |
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 |
29 |
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 |
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 |
36 | /******/ // __webpack_public_path__
37 | /******/ __webpack_require__.p = "";
38 |
39 | /******/ // Load entry module and return exports
40 | /******/ return __webpack_require__(0);
41 | /******/ })
42 | /************************************************************************/
43 | /******/ ([
44 | /* 0 */
45 | /***/ function(module, exports, __webpack_require__) {
46 |
47 | var OPERATE = __webpack_require__(1);
48 |
49 | function isText(node) {
50 | return node.tag === 'text';
51 | }
52 |
53 | function isEle(node) {
54 | return node.tag !== 'text';
55 | }
56 |
57 | function isObject(obj) {
58 | return typeof obj === 'object';
59 | }
60 |
61 | function remove(arr, index, key) {
62 | arr.splice(index, 1);
63 |
64 | return {
65 | from: index,
66 | key: key
67 | };
68 | }
69 |
70 | function walk(a, b, patches, index) {
71 | patches[index] = apply = patches[index] || [];
72 | // no need to walk
73 | if (b == null) {
74 | apply.push({
75 | from: a,
76 | operate: OPERATE.REMOVE
77 | });
78 | // a and b is element
79 | } else if(isEle(a) && isEle(b)) {
80 | if (a.tag === b.tag) {
81 | var diff = diffProps(a.props, b.props);
82 | if (diff) {
83 | apply.push({
84 | diff: diff,
85 | from: a,
86 | operate: OPERATE.PROPS
87 | });
88 | }
89 | diffChildren(a, b, patches, apply, index);
90 | } else {
91 | apply.push({
92 | from: b,
93 | to: a,
94 | operate: OPERATE.REPLACE
95 | });
96 | }
97 | // at least one is text
98 | } else {
99 | if (
100 | isEle(a) || isEle(b)
101 | || a.text !== b.text
102 | ) {
103 | apply.push({
104 | from: b,
105 | to: a,
106 | operate: OPERATE.REPLACE
107 | });
108 | }
109 | }
110 | }
111 |
112 | function diffProps(a, b, apply) {
113 | var key, aVal, bVal, diff;
114 |
115 | for (key in a) {
116 | if (!(key in b)) {
117 | diff = diff || {};
118 | diff[key] = undefined;
119 | continue;
120 | }
121 |
122 | aVal = a[key];
123 | bVal = b[key];
124 |
125 | if (aVal === bVal) {
126 | continue;
127 | } else if (isObject(aVal) && isObject(bVal)) {
128 | var objectDiff = diffProps(aVal, bVal);
129 | if (objectDiff) {
130 | diff = diff || {};
131 | diff[key] = objectDiff;
132 | }
133 | } else {
134 | diff = diff || {};
135 | diff[key] = bVal;
136 | }
137 | }
138 |
139 | for (key in b) {
140 | if (!(key in a)) {
141 | diff = diff || {};
142 | diff[key] = b[key];
143 | }
144 | }
145 |
146 | return diff;
147 | }
148 |
149 | function keyIndex(children) {
150 | var keys = {}
151 | , free = []
152 | , length = children.length
153 | , i = 0
154 | , child;
155 |
156 | for (; i < length; i++) {
157 | child = children[i];
158 |
159 | if (child.key) {
160 | keys[child.key] = i;
161 | } else {
162 | free.push(i);
163 | }
164 | }
165 |
166 | return {
167 | keys: keys, // A hash of key name to index
168 | free: free // An array of unkeyed item indices
169 | };
170 | }
171 |
172 | function reorder(aChildren, bChildren) {
173 | // O(M) time, O(M) memory
174 | var bChildIndex = keyIndex(bChildren)
175 | , bKeys = bChildIndex.keys
176 | , bFree = bChildIndex.free;
177 |
178 | if (bFree.length === bChildren.length) {
179 | return {
180 | children: bChildren,
181 | moves: null
182 | };
183 | }
184 |
185 | // O(N) time, O(N) memory
186 | var aChildIndex = keyIndex(aChildren)
187 | , aKeys = aChildIndex.keys
188 | , aFree = aChildIndex.free;
189 |
190 | if (aFree.length === aChildren.length) {
191 | return {
192 | children: bChildren,
193 | moves: null
194 | };
195 | }
196 |
197 | // O(MAX(N, M)) memory
198 | var newChildren = []
199 | , freeIndex = 0
200 | , freeCount = bFree.length
201 | , deletedItems = 0
202 | , aItem
203 | , itemIndex
204 | , i = 0
205 | , l = aChildren.length;
206 |
207 | // Iterate through a and match a node in b
208 | // O(N) time,
209 | for (; i < l; i++) {
210 | aItem = aChildren[i];
211 |
212 | if (aItem.key) {
213 | if (bKeys.hasOwnProperty(aItem.key)) {
214 | // Match up the old keys
215 | itemIndex = bKeys[aItem.key];
216 | newChildren.push(bChildren[itemIndex]);
217 | } else {
218 | // Remove old keyed items
219 | itemIndex = i - deletedItems++;
220 | newChildren.push(null);
221 | }
222 | } else {
223 | // Match the item in a with the next free item in b
224 | if (freeIndex < freeCount) {
225 | itemIndex = bFree[freeIndex++];
226 | newChildren.push(bChildren[itemIndex]);
227 | } else {
228 | // There are no free items in b to match with
229 | // the free items in a, so the extra free nodes
230 | // are deleted.
231 | itemIndex = i - deletedItems++;
232 | newChildren.push(null);
233 | }
234 | }
235 | }
236 |
237 | var lastFreeIndex = freeIndex >= bFree.length ?
238 | bChildren.length :
239 | bFree[freeIndex]
240 | , j = 0
241 | , newItem;
242 |
243 | l = bChildren.length;
244 |
245 | // Iterate through b and append any new keys
246 | // O(M) time
247 | for (; j < l; j++) {
248 | newItem = bChildren[j];
249 |
250 | if (newItem.key) {
251 | if (!aKeys.hasOwnProperty(newItem.key)) {
252 | // Add any new keyed items
253 | // We are adding new items to the end and then sorting them
254 | // in place. In future we should insert new items in place.
255 | newChildren.push(newItem);
256 | }
257 | } else if (j >= lastFreeIndex) {
258 | // Add any leftover non-keyed items
259 | newChildren.push(newItem);
260 | }
261 | }
262 |
263 | var simulate = newChildren.slice()
264 | , simulateIndex = 0
265 | , removes = []
266 | , inserts = []
267 | , simulateItem
268 | , wantedItem
269 | , k = 0;
270 |
271 | for (; k < l;) {
272 | wantedItem = bChildren[k];
273 | simulateItem = simulate[simulateIndex];
274 |
275 | // remove items
276 | while (simulateItem === null && simulate.length) {
277 | removes.push(remove(simulate, simulateIndex, null));
278 | simulateItem = simulate[simulateIndex];
279 | }
280 |
281 | if (!simulateItem || simulateItem.key !== wantedItem.key) {
282 | // if we need a key in this position...
283 | if (wantedItem.key) {
284 | if (simulateItem && simulateItem.key) {
285 | // if an insert doesn't put this key in place, it needs to move
286 | if (bKeys[simulateItem.key] !== k + 1) {
287 | removes.push(remove(simulate, simulateIndex, simulateItem.key));
288 | simulateItem = simulate[simulateIndex];
289 | // if the remove didn't put the wanted item in place, we need to insert it
290 | if (!simulateItem || simulateItem.key !== wantedItem.key) {
291 | inserts.push({key: wantedItem.key, to: k});
292 | // items are matching, so skip ahead
293 | } else {
294 | simulateIndex++
295 | }
296 | } else {
297 | inserts.push({key: wantedItem.key, to: k})
298 | }
299 | } else {
300 | inserts.push({key: wantedItem.key, to: k})
301 | }
302 | k++
303 | // a key in simulate has no matching wanted key, remove it
304 | } else if (simulateItem && simulateItem.key) {
305 | removes.push(remove(simulate, simulateIndex, simulateItem.key));
306 | }
307 | } else {
308 | simulateIndex++;
309 | k++;
310 | }
311 | }
312 |
313 | // remove all the remaining nodes from simulate
314 | while(simulateIndex < simulate.length) {
315 | simulateItem = simulate[simulateIndex];
316 | removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key));
317 | }
318 |
319 | // If the only moves we have are deletes then we can just
320 | // let the delete patch remove these items.
321 | if (removes.length === deletedItems && !inserts.length) {
322 | return {
323 | children: newChildren,
324 | moves: null
325 | };
326 | }
327 |
328 | return {
329 | children: newChildren,
330 | moves: {
331 | removes: removes,
332 | inserts: inserts
333 | }
334 | };
335 | }
336 |
337 | function diffChildren(a, b, patches, apply, index) {
338 | var aChildren = a.children
339 | , orderedSet = reorder(aChildren, b.children)
340 | , bChildren = orderedSet.children
341 | , aLen = aChildren.length
342 | , bLen = bChildren.length
343 | , len = aLen > bLen ? aLen : bLen
344 | , i = 0
345 | , leftNode
346 | , rightNode;
347 |
348 | for (var i = 0; i < len; i++) {
349 | leftNode = aChildren[i];
350 | rightNode = bChildren[i];
351 | index += 1;
352 |
353 | if (!leftNode) {
354 | if (rightNode) {
355 | apply.push({
356 | from: rightNode,
357 | to: a,
358 | operate: OPERATE.INSERT
359 | });
360 | }
361 | } else {
362 | walk(leftNode, rightNode, patches, index)
363 | }
364 | }
365 |
366 | if (orderedSet.moves) {
367 | apply.push({
368 | operate: OPERATE.ORDER,
369 | from: orderedSet.moves,
370 | to: a
371 | });
372 | }
373 |
374 | return apply;
375 | }
376 |
377 | /**
378 | * h(tagName, props, children)
379 | * h('text', 'hello')
380 | * {
381 | * tag: 'div',
382 | * children: [],
383 | * props: {}
384 | * }
385 | */
386 | function h(tagName, props, children) {
387 | props = props || {};
388 | children = children || [];
389 | var key;
390 | if (tagName === 'text') {
391 | return {
392 | tag: tagName,
393 | text: props
394 | };
395 | } else {
396 | if ('key' in props) {
397 | key = props.key;
398 | delete props['key'];
399 | }
400 | for (var i = 0, l = children.length; i < l; i++) {
401 | // text
402 | typeof children[i] === 'string' &&
403 | (children[i] = h('text', children[i]));
404 | }
405 | return {
406 | tag: tagName,
407 | props: props,
408 | children: children,
409 | key: key
410 | };
411 | }
412 | }
413 |
414 | /**
415 | * diff
416 | * @param {VD} a
417 | * @param {VD} b
418 | */
419 | function diff(a, b) {
420 | var patches = {}, res = [];
421 | res.a = a;
422 | walk(a, b, patches, 0);
423 | for (var i = 0; patches[i]; i++) {
424 | patches[i].length &&
425 | res.push(patches[i])
426 | }
427 | return res;
428 | }
429 |
430 | module.exports = {
431 | h: h,
432 | diff: diff
433 | };
434 |
435 | /***/ },
436 | /* 1 */
437 | /***/ function(module, exports, __webpack_require__) {
438 |
439 | module.exports = {
440 | REMOVE: 1,
441 | INSERT: 2,
442 | REPLACE: 3,
443 | ORDER: 4,
444 | PROPS: 5
445 | };
446 |
447 | /***/ }
448 | /******/ ])});;
--------------------------------------------------------------------------------
/dist/vd.min.js:
--------------------------------------------------------------------------------
1 | define("vd",[],function(){return function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports,o,o.exports,r),o.loaded=!0,o.exports}var t={};return r.m=e,r.c=t,r.p="",r(0)}([function(e,r,t){function n(e){return"text"!==e.tag}function o(e){return"object"==typeof e}function l(e,r,t){return e.splice(r,1),{from:r,key:t}}function u(e,r,t,o){if(t[o]=apply=t[o]||[],null==r)apply.push({from:e,operate:y.REMOVE});else if(n(e)&&n(r))if(e.tag===r.tag){var l=p(e.props,r.props);l&&apply.push({diff:l,from:e,operate:y.PROPS}),h(e,r,t,apply,o)}else apply.push({from:r,to:e,operate:y.REPLACE});else(n(e)||n(r)||e.text!==r.text)&&apply.push({from:r,to:e,operate:y.REPLACE})}function p(e,r){var t,n,l,u;for(t in e)if(t in r){if(n=e[t],l=r[t],n!==l)if(o(n)&&o(l)){var f=p(n,l);f&&(u=u||{},u[t]=f)}else u=u||{},u[t]=l}else u=u||{},u[t]=void 0;for(t in r)t in e||(u=u||{},u[t]=r[t]);return u}function f(e){for(var r,t={},n=[],o=e.length,l=0;o>l;l++)r=e[l],r.key?t[r.key]=l:n.push(l);return{keys:t,free:n}}function s(e,r){var t=f(r),n=t.keys,o=t.free;if(o.length===r.length)return{children:r,moves:null};var u=f(e),p=u.keys,s=u.free;if(s.length===e.length)return{children:r,moves:null};for(var h,i,a=[],y=0,k=o.length,c=0,v=0,g=e.length;g>v;v++)h=e[v],h.key?n.hasOwnProperty(h.key)?(i=n[h.key],a.push(r[i])):(i=v-c++,a.push(null)):k>y?(i=o[y++],a.push(r[i])):(i=v-c++,a.push(null));var d,m=y>=o.length?r.length:o[y],E=0;for(g=r.length;g>E;E++)d=r[E],d.key?p.hasOwnProperty(d.key)||a.push(d):E>=m&&a.push(d);for(var x,R,P=a.slice(),O=0,S=[],A=[],C=0;g>C;){for(R=r[C],x=P[O];null===x&&P.length;)S.push(l(P,O,null)),x=P[O];x&&x.key===R.key?(O++,C++):R.key?(x&&x.key&&n[x.key]!==C+1?(S.push(l(P,O,x.key)),x=P[O],x&&x.key===R.key?O++:A.push({key:R.key,to:C})):A.push({key:R.key,to:C}),C++):x&&x.key&&S.push(l(P,O,x.key))}for(;Ok?a:k,v=0,v=0;c>v;v++)l=f[v],p=i[v],o+=1,l?u(l,p,t,o):p&&n.push({from:p,to:e,operate:y.INSERT});return h.moves&&n.push({operate:y.ORDER,from:h.moves,to:e}),n}function i(e,r,t){r=r||{},t=t||[];var n;if("text"===e)return{tag:e,text:r};"key"in r&&(n=r.key,delete r.key);for(var o=0,l=t.length;l>o;o++)"string"==typeof t[o]&&(t[o]=i("text",t[o]));return{tag:e,props:r,children:t,key:n}}function a(e,r){var t={},n=[];n.a=e,u(e,r,t,0);for(var o=0;t[o];o++)t[o].length&&n.push(t[o]);return n}var y=t(1);e.exports={h:i,diff:a}},function(e){e.exports={REMOVE:1,INSERT:2,REPLACE:3,ORDER:4,PROPS:5}}])});
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp')
2 | , map = require('map-stream')
3 | , maxmin = require('maxmin')
4 | , uglify = require('gulp-uglify')
5 | , webpack = require('gulp-webpack')
6 | , config = require('./webpack.config');
7 |
8 | function Size(name) {
9 | this._name = name;
10 | this._max = undefined;
11 | this._min = undefined;
12 | }
13 | Size.prototype.max = function () {
14 | var self = this;
15 | return map(function (file, cb) {
16 | self._max = file.contents;
17 | cb(null, file);
18 | });
19 | };
20 | Size.prototype.min = function (rename) {
21 | var self = this;
22 | return map(function (file, cb) {
23 | self._min = file.contents;
24 | rename &&
25 | (file.path = rename(file.path));
26 | cb(null, file);
27 | });
28 | };
29 | Size.prototype.print = function () {
30 | var self = this;
31 | setTimeout(function () {
32 | console.log(self._name, maxmin(self._max, self._min, true));
33 | }, 0);
34 | }
35 |
36 | var size = new Size('vd.js')
37 | , allSize = new Size('all.js');
38 |
39 | gulp.task('vd-build', function (done) {
40 | gulp.src(['vd.js'])
41 | .pipe(webpack(config.vd))
42 | .pipe(size.max())
43 | .pipe(gulp.dest('./dist'))
44 | .on('end', function () {
45 | done();
46 | });
47 | });
48 |
49 | gulp.task('vd-uglify', ['vd-build'], function (done) {
50 | gulp.src(['dist/vd.js'])
51 | .pipe(uglify({
52 | preserveComments: 'some'
53 | }))
54 | .pipe(size.min(function (path) {
55 | return path.replace(/\.js$/, '.min.js');
56 | }))
57 | .pipe(gulp.dest('./dist'))
58 | .on('end', function () {
59 | size.print();
60 | done();
61 | })
62 | });
63 |
64 | gulp.task('all-build', function (done) {
65 | gulp.src(['index.js'])
66 | .pipe(webpack(config.all))
67 | .pipe(allSize.max())
68 | .pipe(gulp.dest('./dist'))
69 | .on('end', function () {
70 | done();
71 | });
72 | });
73 |
74 | gulp.task('all-uglify', ['all-build'], function (done) {
75 | gulp.src(['dist/all.js'])
76 | .pipe(uglify({
77 | preserveComments: 'some'
78 | }))
79 | .pipe(allSize.min(function (path) {
80 | return path.replace(/\.js$/, '.min.js');
81 | }))
82 | .pipe(gulp.dest('./dist'))
83 | .on('end', function () {
84 | allSize.print();
85 | done();
86 | })
87 | });
88 |
89 | gulp.task('default', ['vd-uglify', 'all-uglify']);
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var vd = require('./vd')
2 | , render = require('./render')
3 | , patch = require('./patch');
4 |
5 | module.exports = {
6 | diff: vd.diff,
7 | h: vd.h,
8 | render: render,
9 | patch: patch
10 | };
--------------------------------------------------------------------------------
/lib/operate.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | REMOVE: 1,
3 | INSERT: 2,
4 | REPLACE: 3,
5 | ORDER: 4,
6 | PROPS: 5
7 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "qvd",
3 | "version": "0.0.1",
4 | "description": "A very simple Virtual DOM and diffing aloorithm for mobile fork from virtual-dom.",
5 | "main": "index.js",
6 | "directories": {
7 | "test": "test"
8 | },
9 | "dependencies": {},
10 | "devDependencies": {
11 | "gulp": "^3.8.11",
12 | "gulp-uglify": "^1.1.0",
13 | "gulp-webpack": "^1.3.1",
14 | "map-stream": "^0.0.5",
15 | "maxmin": "^1.1.0",
16 | "mocha": "^2.2.1",
17 | "should": "^5.2.0"
18 | },
19 | "scripts": {
20 | "test": "mocha"
21 | },
22 | "author": "",
23 | "license": "MIT"
24 | }
25 |
--------------------------------------------------------------------------------
/patch.js:
--------------------------------------------------------------------------------
1 | // TODO in the rough
2 | var OPERATE = require('./lib/operate')
3 | , DEAL = {};
4 |
5 | function isText(vd) {
6 | return vd.tag === 'text';
7 | }
8 |
9 | function getVDByKey(vds, key, cache) {
10 | cache = cache || {};
11 | if (cache[key]) return cache[key];
12 | for (var i = 0, l = vds.length; i < l; i++) {
13 | if (vds[i].key === key) return (cache[key] = vds[i]);
14 | }
15 | }
16 |
17 | function find(vd, vds) {
18 | var i = vds.indexOf(vd)
19 | , res, tmp;
20 | if (!(~i)) {
21 | vds.every(function (item, j) {
22 | if (!item.children || !item.children.length) return true;
23 | res = find(vd, item.children);
24 | if (res) {
25 | tmp = res;
26 | res = [j];
27 | [].push.apply(res, tmp);
28 | return false;
29 | } else {
30 | return true;
31 | }
32 | });
33 | return res;
34 | }
35 | return [i];
36 | }
37 |
38 | function getRef(vd, root, a) {
39 | var ref = vd.ref;
40 | if (ref) return ref;
41 | if (!root || !a) throw new Error('Should bind the reference node.');
42 | var indexes = find(vd, [a]);
43 | if (!indexes) throw new Error('Couldn\'t find the reference node.');
44 | ref = root.childNodes[0];
45 | for (var i = 1, l = indexes.length; i < l; i++) {
46 | ref = ref.childNodes[indexes[i]];
47 | }
48 | vd.ref = ref;
49 | return ref;
50 | }
51 |
52 | function setProps(to, from, node) {
53 | for (key in from) {
54 | // style
55 | if (key === 'style') {
56 | style = node.style;
57 | value = from[key];
58 | if (!value) {
59 | style.cssText = '';
60 | to.style = {};
61 | } else {
62 | value = from[key];
63 | for (i in value) {
64 | if (value[i] === undefined) {
65 | style[i] = '';
66 | delete to.style[i];
67 | } else {
68 | style[i] = value[i];
69 | to.style[i] = value[i];
70 | }
71 | }
72 | }
73 | // property
74 | } else if (key in node) {
75 | value = from[key];
76 | type = typeof node[key];
77 | if (value === undefined) {
78 | type === 'string' ?
79 | (node[key] = '') :
80 | (node[key] = null);
81 | delete to[key];
82 | } else {
83 | node[key] = to[key] = value;
84 | }
85 | // attribute
86 | } else {
87 | value = from[key];
88 | if (value === undefined) {
89 | node.removeAttribute(key);
90 | delete to[key];
91 | } else {
92 | node.setAttribute(key, value);
93 | to[key] = value;
94 | }
95 | }
96 | }
97 | }
98 |
99 | function create(vds, parent) {
100 | !Array.isArray(vds) && (vds = [vds]);
101 | parent = parent || document.createDocumentFragment();
102 | var node;
103 | vds.forEach(function (vd) {
104 | if (isText(vd)) {
105 | node = document.createTextNode(vd.text);
106 | } else {
107 | node = document.createElement(vd.tag);
108 | }
109 | parent.appendChild(node);
110 | vd.children && vd.children.length &&
111 | create(vd.children, node);
112 |
113 | vd.props &&
114 | setProps({ style: {} }, vd.props, node);
115 | });
116 | return parent;
117 | }
118 |
119 | function splice(vd, a, replacement) {
120 | var indexes, i, l;
121 | indexes = find(vd, [a]);
122 | for (i = 1, l = indexes.length - 1; i < l; i++) {
123 | a = a.children[indexes[i]];
124 | }
125 | replacement ?
126 | a.children.splice(indexes[l], 1, replacement) :
127 | a.children.splice(indexes[l], 1);
128 | }
129 |
130 | DEAL[OPERATE.REMOVE] = function (item, root, a) {
131 | var node = getRef(item.from, root, a);
132 | node.parentNode.removeChild(node);
133 | item.from.ref = null;
134 | splice(item.from, a);
135 | };
136 |
137 | DEAL[OPERATE.INSERT] = function (item, root, a) {
138 | var node = create(item.from)
139 | , parent = getRef(item.to, root, a);
140 | parent.appendChild(node);
141 | item.to.children.push(item.from);
142 | };
143 |
144 | DEAL[OPERATE.REPLACE] = function (item, root, a) {
145 | var node = create(item.from)
146 | , target = getRef(item.to, root, a)
147 | , indexes
148 | , i
149 | , l;
150 | target.parentNode.replaceChild(node, target);
151 | item.to.ref = null;
152 | splice(item.to, a, item.from);
153 | };
154 |
155 | DEAL[OPERATE.ORDER] = function (item, root, a) {
156 | var to = item.to
157 | , from = item.from
158 | , children = to.children
159 | , parent = getRef(to, root, a)
160 | , grandpa = parent.parentNode
161 | , removes = from.removes
162 | , inserts = from.inserts
163 | , cache = {};
164 |
165 | removes.forEach(function (order) {
166 | var vd = getVDByKey(children, order.key, cache)
167 | , node = getRef(vd, grandpa, to);
168 | parent.removeChild(node);
169 | splice(vd, to);
170 | });
171 | inserts.forEach(function (order) {
172 | var vd = getVDByKey(children, order.key, cache)
173 | , node = getRef(vd, grandpa, to)
174 | , before = getRef(children[order.to], grandpa, to);
175 | parent.insertBefore(node, before);
176 | children.splice(order.to, 0, vd);
177 | });
178 | cache = null;
179 | };
180 |
181 | DEAL[OPERATE.PROPS] = function (item, root, a) {
182 | var props = item.from.props
183 | , node = getRef(item.from, root, a)
184 | , diff = item.diff
185 | , key, value, i, l, style, type;
186 | setProps(props, diff, node);
187 | };
188 |
189 | function patch(patches, root) {
190 | var i = 0, l = patches.length
191 | , a = patches.a;
192 | root = root || a.hook;
193 | for (; i < l; i++) {
194 | patches[i].forEach(function (item) {
195 | DEAL[item.operate](item, root, a);
196 | });
197 | }
198 | }
199 |
200 | module.exports = patch;
--------------------------------------------------------------------------------
/q-vd.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TODO
3 | * q-vd directive for Q.js
4 | * @example
5 | *
6 | * -
7 | *
8 | * {[this.user.firstName, this.user.lastName].join(' ')}
9 | *
10 | *
11 | * Just use jsx-transform transform to:
12 | * h('li', null, [
13 | * h('img', { src: "avatar.png", class: "profile" }),
14 | * h('h3', null, [[this.user.firstName, user.lastName].join(' ')])
15 | * ]);
16 | * q-vd directive will bind data to this, and when data change, it will rerender the dom
17 | */
18 | var Q = require('Q')
19 | , h = require('./vd').h
20 | , diff = require('./vd').diff
21 | , render = require('./render')
22 | , vds = {}
23 | , jsxs = {}
24 | , uid = 0;
25 |
26 | function Jsx(name, jsx) {
27 | if (jsx) {
28 | jsxs[name] = new Function('h', jsx);
29 | } else {
30 | jsx = jsxs[name];
31 | if (!jsx) throw new Error('jsx ' + name + ' not exists.');
32 | return jsx;
33 | }
34 | }
35 |
36 | function first(name, el, data) {
37 | var jsx = Jsx(name)
38 | , vd;
39 | vd = jsx.call(data, h);
40 | el.innerHTML = render(vd);
41 | // TODO need to bind ele & vd
42 | el.vdId = ++uid;
43 | vds[uid] = vd;
44 | }
45 |
46 | function update(data) {
47 | var el = this.el
48 | , name = this.arg
49 | , vdId = el.vdId;
50 | if (!vdId) return first(name, el, data);
51 | var aVd = vds[vdId]
52 | , jsx = Jsx(name)
53 | , bVd
54 | , patches;
55 | bVd = jsx.call(data, h);
56 | patches = diff(aVd, bVd);
57 | // TODO need to deal with patches
58 | vds[vdId] = bVd;
59 | }
60 |
61 | Q.jsx = Jsx;
62 | Q.options.directives.vd = update;
--------------------------------------------------------------------------------
/render.js:
--------------------------------------------------------------------------------
1 | var singleTag = {
2 | area: true,
3 | base: true,
4 | basefont: true,
5 | br: true,
6 | col: true,
7 | command: true,
8 | embed: true,
9 | frame: true,
10 | hr: true,
11 | img: true,
12 | input: true,
13 | isindex: true,
14 | keygen: true,
15 | link: true,
16 | meta: true,
17 | param: true,
18 | source: true,
19 | track: true,
20 | wbr: true,
21 | }
22 | , booleanAttributes = {
23 | allowfullscreen: true,
24 | async: true,
25 | autofocus: true,
26 | autoplay: true,
27 | checked: true,
28 | controls: true,
29 | default: true,
30 | defer: true,
31 | disabled: true,
32 | hidden: true,
33 | ismap: true,
34 | loop: true,
35 | multiple: true,
36 | muted: true,
37 | open: true,
38 | readonly: true,
39 | required: true,
40 | reversed: true,
41 | scoped: true,
42 | seamless: true,
43 | selected: true,
44 | typemustmatch: true
45 | }
46 | , REG_CAP = /[A-Z]/g;
47 |
48 | function isEle(node) {
49 | return node.tag !== 'text';
50 | }
51 |
52 | function formatStyle(style) {
53 | var key, value
54 | , output = 'style="';
55 |
56 | for (key in style) {
57 | value = style[key];
58 |
59 | key = key.replace(REG_CAP, function (cap) {
60 | return '-' + cap.toLowerCase();
61 | });
62 |
63 | output += key + ': ' + value + ';';
64 | }
65 |
66 | return output + '"';
67 | }
68 |
69 | function formatProps(props) {
70 | if (!props) return;
71 |
72 | var output = ''
73 | , key
74 | , value;
75 |
76 | for (key in props) {
77 | value = props[key];
78 |
79 | if (output) {
80 | output += ' ';
81 | }
82 |
83 | if (!value && booleanAttributes[key]) {
84 | output += key;
85 | } else if (key === 'style') {
86 | output += formatStyle(value);
87 | } else {
88 | output += key + '="' + value + '"';
89 | }
90 | }
91 | return output;
92 | }
93 |
94 | function renderTag(vd) {
95 | var tag = '<'+ vd.tag
96 | , props = formatProps(vd.props);
97 |
98 | if (props) {
99 | tag += ' ' + props;
100 | }
101 |
102 | tag += '>';
103 |
104 | if (vd.children) {
105 | tag += render(vd.children);
106 | }
107 |
108 | if (!singleTag[vd.tag]) {
109 | tag += '' + vd.tag + '>';
110 | }
111 |
112 | return tag;
113 | }
114 |
115 | function renderText(vd) {
116 | return vd.text;
117 | }
118 |
119 |
120 | function render(vds) {
121 | !Array.isArray(vds) &&
122 | (vds = [vds]);
123 |
124 | var output = ''
125 | , l = vds.length
126 | , vd;
127 |
128 | for (var i = 0; i < l; i++) {
129 | vd = vds[i]
130 |
131 | if (isEle(vd)) {
132 | output += renderTag(vd);
133 | } else {
134 | output += renderText(vd);
135 | }
136 | }
137 | return output;
138 | }
139 |
140 | module.exports = render;
--------------------------------------------------------------------------------
/test/cases/test.js:
--------------------------------------------------------------------------------
1 | var diff = vd.diff
2 | , h = vd.h
3 | , patch = vd.patch
4 | , render = vd.render
5 | , container = document.getElementById('container')
6 | , a = h('div', { style: { color: '#fff' } });
7 |
8 | describe('render', function () {
9 | it('should able render a template', function () {
10 | var div;
11 | container.innerHTML = render(a);
12 | div = container.childNodes[0];
13 | div.tagName.toLowerCase().should.equal('div');
14 | div.style.color.should.equal('rgb(255, 255, 255)');
15 | });
16 |
17 | it('should able rerender properties', function () {
18 | var b = h('div', { style: { color: '#000' } })
19 | , patches = diff(a, b)
20 | , div;
21 |
22 | patch(patches, container);
23 | div = container.childNodes[0];
24 | div.style.color.should.equal('rgb(0, 0, 0)');
25 |
26 | b = h('div', { style: { background: '#000' } });
27 | patches = diff(a, b);
28 | patch(patches, container);
29 | div.style.color.should.equal('');
30 | div.style.background.should.equal('rgb(0, 0, 0)');
31 |
32 | b = h('div');
33 | patches = diff(a, b);
34 | patch(patches, container);
35 | div.style.length.should.equal(0);
36 |
37 | b = h('div', { 'data-id': '123456' });
38 | patches = diff(a, b);
39 | patch(patches, container);
40 | div.getAttribute('data-id').should.equal('123456');
41 | });
42 |
43 | it('should able insert node', function () {
44 | var b = h('div', null, ['hello'])
45 | , patches = diff(a, b)
46 | , div, node;
47 |
48 | patch(patches, container);
49 | div = container.childNodes[0];
50 | node = div.childNodes[0];
51 | node.nodeType.should.equal(3);
52 | node.nodeValue.should.equal('hello');
53 |
54 | a = h('div');
55 | container.innerHTML = render(a);
56 | div = container.childNodes[0];
57 |
58 | b = h('div', null, [h('p', { style: { color: '#000' } }, ['hello'])]);
59 | patches = diff(a, b);
60 | patch(patches, container);
61 | node = div.childNodes[0];
62 | node.tagName.toLowerCase().should.equal('p');
63 | node.innerText.should.equal('hello');
64 | node.style.color.should.equal('rgb(0, 0, 0)');
65 | });
66 |
67 | it('should able replace node', function () {
68 | var b = h('div', null, [h('span', null, ['tencent'])])
69 | , patches = diff(a, b)
70 | , div
71 | , node;
72 |
73 | patch(patches, container);
74 | div = container.childNodes[0];
75 | node = div.childNodes[0];
76 | node.tagName.toLowerCase().should.equal('span');
77 | node.innerText.should.equal('tencent');
78 | });
79 |
80 | it('should able remove node', function () {
81 | var b = h('div')
82 | , patches = diff(a, b)
83 | , div;
84 |
85 | patch(patches, container);
86 | div = container.childNodes[0];
87 | div.childNodes.length.should.equal(0);
88 | });
89 |
90 | it('should able order node', function () {
91 | a = h('div', null, [h('p', { key: 1 }, ['hello']), h('p', { key: 2 }, ['tencent'])]);
92 | var b = h('div', null, [h('p', { key: 2 }, ['tencent']), h('p', { key: 1 }, ['hello'])])
93 | , patches
94 | , div
95 | , p;
96 |
97 | container.innerHTML = render(a);
98 | patches = diff(a, b);
99 | patch(patches, container);
100 | div = container.childNodes[0];
101 | p = div.childNodes[0];
102 | p.tagName.toLowerCase().should.equal('p');
103 | p.innerText.should.equal('tencent');
104 | p = div.childNodes[1];
105 | p.tagName.toLowerCase().should.equal('p');
106 | p.innerText.should.equal('hello');
107 | });
108 | });
109 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
22 |
23 |
29 |
30 |
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --require should
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | var qvd = require('../')
2 | , h = qvd.h
3 | , diff = qvd.diff
4 | , render = qvd.render
5 | , OPERATE = require('../lib/operate');
6 |
7 | describe('diff', function () {
8 | it('should not need any operate for two equal div', function () {
9 | var a1 = h('div')
10 | , a2 = h('div')
11 | , b1 = h('div', { style: { color: '#fff' } })
12 | , b2 = h('div', { style: { color: '#fff' } })
13 | , c1 = h('div', { style: { color: '#fff' } }, [h('text', 'hello')])
14 | , c2 = h('div', { style: { color: '#fff' } }, [h('text', 'hello')])
15 | , d1 = h('div', null, [h('p', null, [h('text', 'hello')])])
16 | , d2 = h('div', null, [h('p', null, [h('text', 'hello')])]);
17 |
18 | diff(a1, a2).length.should.equal(0);
19 | diff(b1, b2).length.should.equal(0);
20 | diff(c1, c2).length.should.equal(0);
21 | diff(d1, d2).length.should.equal(0);
22 | });
23 |
24 | it('should able to check the difference for two div', function () {
25 | var text1 = h('text', 'hello')
26 | , text2 = h('text', 'hello world')
27 | , p1 = h('p', { key: 1 }, [text1])
28 | , p2 = h('p', { key: 2 }, [text1])
29 | , p3 = h('p', { key: 2}, [text1])
30 | , p4 = h('p', { key: 1 }, [text2]);
31 |
32 | var res
33 | , a1 = h('div', { style: { color: '#000' } })
34 | , a2 = h('div', { style: { color: '#fff' } })
35 | , b1 = h('div', { style: { color: '#fff' } }, [h('p', null, [text1])])
36 | , b2 = h('div', { style: { color: '#fff' } }, [h('p', null, [text2])])
37 | , c1 = h('div', null, [p1])
38 | , c2 = h('div', null, [p2])
39 | , d1 = h('div', null, [p1, p2])
40 | , d2 = h('div', null, [p3, p4]);
41 |
42 | res = diff(a1, a2);
43 | res.length.should.equal(1);
44 | res[0].length.should.equal(1);
45 | res[0][0].operate.should.equal(OPERATE.PROPS);
46 | res[0][0].diff.style.color.should.equal('#fff');
47 |
48 | res = diff(b1, b2);
49 | res.length.should.equal(1);
50 | res[0].length.should.equal(1);
51 | res[0][0].operate.should.equal(OPERATE.REPLACE);
52 | res[0][0].from.should.equal(text1);
53 | res[0][0].to.should.equal(text2);
54 |
55 | res = diff(c1, c2);
56 | res.length.should.equal(2);
57 | res[0].length.should.equal(1);
58 | res[1].length.should.equal(1);
59 | res[0][0].operate.should.equal(OPERATE.INSERT);
60 | res[0][0].from.should.equal(p2);
61 | res[1][0].operate.should.equal(OPERATE.REMOVE);
62 | res[1][0].from.should.equal(p1);
63 |
64 | res = diff(d1, d2);
65 | res.length.should.equal(2);
66 | res[0].length.should.equal(1);
67 | res[1].length.should.equal(1);
68 | res[0][0].operate.should.equal(OPERATE.ORDER);
69 | res[0][0].to.should.eql({ removes: [{ from: 1, key: 2 }], inserts: [{ key: 2, to: 0 }] });
70 | res[1][0].from.should.equal(text1);
71 | res[1][0].to.should.equal(text2);
72 | });
73 | });
74 |
75 | describe('render', function () {
76 | it('should render a div which has a p in it', function () {
77 | var vd = h('div', { style: { backgroundColor: '#fff' } }, [h('p', null, [h('text', 'hello')])]);
78 | render(vd).should.equal('')
79 | });
80 |
81 | it('should render a input has value', function () {
82 | var vd = h('input', { value: 'hello' });
83 | render(vd).should.equal('');
84 | });
85 |
86 | it('should render a disabled input', function () {
87 | var vd = h('input', { disabled: '' });
88 | render(vd).should.equal('');
89 | });
90 | });
91 |
--------------------------------------------------------------------------------
/vd.js:
--------------------------------------------------------------------------------
1 | var OPERATE = require('./lib/operate');
2 |
3 | function isText(node) {
4 | return node.tag === 'text';
5 | }
6 |
7 | function isEle(node) {
8 | return node.tag !== 'text';
9 | }
10 |
11 | function isObject(obj) {
12 | return typeof obj === 'object';
13 | }
14 |
15 | function remove(arr, index, key) {
16 | arr.splice(index, 1);
17 |
18 | return {
19 | from: index,
20 | key: key
21 | };
22 | }
23 |
24 | function walk(a, b, patches, index) {
25 | patches[index] = apply = patches[index] || [];
26 | // no need to walk
27 | if (b == null) {
28 | apply.push({
29 | from: a,
30 | operate: OPERATE.REMOVE
31 | });
32 | // a and b is element
33 | } else if(isEle(a) && isEle(b)) {
34 | if (a.tag === b.tag) {
35 | var diff = diffProps(a.props, b.props);
36 | if (diff) {
37 | apply.push({
38 | diff: diff,
39 | from: a,
40 | operate: OPERATE.PROPS
41 | });
42 | }
43 | diffChildren(a, b, patches, apply, index);
44 | } else {
45 | apply.push({
46 | from: b,
47 | to: a,
48 | operate: OPERATE.REPLACE
49 | });
50 | }
51 | // at least one is text
52 | } else {
53 | if (
54 | isEle(a) || isEle(b)
55 | || a.text !== b.text
56 | ) {
57 | apply.push({
58 | from: b,
59 | to: a,
60 | operate: OPERATE.REPLACE
61 | });
62 | }
63 | }
64 | }
65 |
66 | function diffProps(a, b, apply) {
67 | var key, aVal, bVal, diff;
68 |
69 | for (key in a) {
70 | if (!(key in b)) {
71 | diff = diff || {};
72 | diff[key] = undefined;
73 | continue;
74 | }
75 |
76 | aVal = a[key];
77 | bVal = b[key];
78 |
79 | if (aVal === bVal) {
80 | continue;
81 | } else if (isObject(aVal) && isObject(bVal)) {
82 | var objectDiff = diffProps(aVal, bVal);
83 | if (objectDiff) {
84 | diff = diff || {};
85 | diff[key] = objectDiff;
86 | }
87 | } else {
88 | diff = diff || {};
89 | diff[key] = bVal;
90 | }
91 | }
92 |
93 | for (key in b) {
94 | if (!(key in a)) {
95 | diff = diff || {};
96 | diff[key] = b[key];
97 | }
98 | }
99 |
100 | return diff;
101 | }
102 |
103 | function keyIndex(children) {
104 | var keys = {}
105 | , free = []
106 | , length = children.length
107 | , i = 0
108 | , child;
109 |
110 | for (; i < length; i++) {
111 | child = children[i];
112 |
113 | if (child.key) {
114 | keys[child.key] = i;
115 | } else {
116 | free.push(i);
117 | }
118 | }
119 |
120 | return {
121 | keys: keys, // A hash of key name to index
122 | free: free // An array of unkeyed item indices
123 | };
124 | }
125 |
126 | function reorder(aChildren, bChildren) {
127 | // O(M) time, O(M) memory
128 | var bChildIndex = keyIndex(bChildren)
129 | , bKeys = bChildIndex.keys
130 | , bFree = bChildIndex.free;
131 |
132 | if (bFree.length === bChildren.length) {
133 | return {
134 | children: bChildren,
135 | moves: null
136 | };
137 | }
138 |
139 | // O(N) time, O(N) memory
140 | var aChildIndex = keyIndex(aChildren)
141 | , aKeys = aChildIndex.keys
142 | , aFree = aChildIndex.free;
143 |
144 | if (aFree.length === aChildren.length) {
145 | return {
146 | children: bChildren,
147 | moves: null
148 | };
149 | }
150 |
151 | // O(MAX(N, M)) memory
152 | var newChildren = []
153 | , freeIndex = 0
154 | , freeCount = bFree.length
155 | , deletedItems = 0
156 | , aItem
157 | , itemIndex
158 | , i = 0
159 | , l = aChildren.length;
160 |
161 | // Iterate through a and match a node in b
162 | // O(N) time,
163 | for (; i < l; i++) {
164 | aItem = aChildren[i];
165 |
166 | if (aItem.key) {
167 | if (bKeys.hasOwnProperty(aItem.key)) {
168 | // Match up the old keys
169 | itemIndex = bKeys[aItem.key];
170 | newChildren.push(bChildren[itemIndex]);
171 | } else {
172 | // Remove old keyed items
173 | itemIndex = i - deletedItems++;
174 | newChildren.push(null);
175 | }
176 | } else {
177 | // Match the item in a with the next free item in b
178 | if (freeIndex < freeCount) {
179 | itemIndex = bFree[freeIndex++];
180 | newChildren.push(bChildren[itemIndex]);
181 | } else {
182 | // There are no free items in b to match with
183 | // the free items in a, so the extra free nodes
184 | // are deleted.
185 | itemIndex = i - deletedItems++;
186 | newChildren.push(null);
187 | }
188 | }
189 | }
190 |
191 | var lastFreeIndex = freeIndex >= bFree.length ?
192 | bChildren.length :
193 | bFree[freeIndex]
194 | , j = 0
195 | , newItem;
196 |
197 | l = bChildren.length;
198 |
199 | // Iterate through b and append any new keys
200 | // O(M) time
201 | for (; j < l; j++) {
202 | newItem = bChildren[j];
203 |
204 | if (newItem.key) {
205 | if (!aKeys.hasOwnProperty(newItem.key)) {
206 | // Add any new keyed items
207 | // We are adding new items to the end and then sorting them
208 | // in place. In future we should insert new items in place.
209 | newChildren.push(newItem);
210 | }
211 | } else if (j >= lastFreeIndex) {
212 | // Add any leftover non-keyed items
213 | newChildren.push(newItem);
214 | }
215 | }
216 |
217 | var simulate = newChildren.slice()
218 | , simulateIndex = 0
219 | , removes = []
220 | , inserts = []
221 | , simulateItem
222 | , wantedItem
223 | , k = 0;
224 |
225 | for (; k < l;) {
226 | wantedItem = bChildren[k];
227 | simulateItem = simulate[simulateIndex];
228 |
229 | // remove items
230 | while (simulateItem === null && simulate.length) {
231 | removes.push(remove(simulate, simulateIndex, null));
232 | simulateItem = simulate[simulateIndex];
233 | }
234 |
235 | if (!simulateItem || simulateItem.key !== wantedItem.key) {
236 | // if we need a key in this position...
237 | if (wantedItem.key) {
238 | if (simulateItem && simulateItem.key) {
239 | // if an insert doesn't put this key in place, it needs to move
240 | if (bKeys[simulateItem.key] !== k + 1) {
241 | removes.push(remove(simulate, simulateIndex, simulateItem.key));
242 | simulateItem = simulate[simulateIndex];
243 | // if the remove didn't put the wanted item in place, we need to insert it
244 | if (!simulateItem || simulateItem.key !== wantedItem.key) {
245 | inserts.push({key: wantedItem.key, to: k});
246 | // items are matching, so skip ahead
247 | } else {
248 | simulateIndex++
249 | }
250 | } else {
251 | inserts.push({key: wantedItem.key, to: k})
252 | }
253 | } else {
254 | inserts.push({key: wantedItem.key, to: k})
255 | }
256 | k++
257 | // a key in simulate has no matching wanted key, remove it
258 | } else if (simulateItem && simulateItem.key) {
259 | removes.push(remove(simulate, simulateIndex, simulateItem.key));
260 | }
261 | } else {
262 | simulateIndex++;
263 | k++;
264 | }
265 | }
266 |
267 | // remove all the remaining nodes from simulate
268 | while(simulateIndex < simulate.length) {
269 | simulateItem = simulate[simulateIndex];
270 | removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key));
271 | }
272 |
273 | // If the only moves we have are deletes then we can just
274 | // let the delete patch remove these items.
275 | if (removes.length === deletedItems && !inserts.length) {
276 | return {
277 | children: newChildren,
278 | moves: null
279 | };
280 | }
281 |
282 | return {
283 | children: newChildren,
284 | moves: {
285 | removes: removes,
286 | inserts: inserts
287 | }
288 | };
289 | }
290 |
291 | function diffChildren(a, b, patches, apply, index) {
292 | var aChildren = a.children
293 | , orderedSet = reorder(aChildren, b.children)
294 | , bChildren = orderedSet.children
295 | , aLen = aChildren.length
296 | , bLen = bChildren.length
297 | , len = aLen > bLen ? aLen : bLen
298 | , i = 0
299 | , leftNode
300 | , rightNode;
301 |
302 | for (var i = 0; i < len; i++) {
303 | leftNode = aChildren[i];
304 | rightNode = bChildren[i];
305 | index += 1;
306 |
307 | if (!leftNode) {
308 | if (rightNode) {
309 | apply.push({
310 | from: rightNode,
311 | to: a,
312 | operate: OPERATE.INSERT
313 | });
314 | }
315 | } else {
316 | walk(leftNode, rightNode, patches, index)
317 | }
318 | }
319 |
320 | if (orderedSet.moves) {
321 | apply.push({
322 | operate: OPERATE.ORDER,
323 | from: orderedSet.moves,
324 | to: a
325 | });
326 | }
327 |
328 | return apply;
329 | }
330 |
331 | /**
332 | * h(tagName, props, children)
333 | * h('text', 'hello')
334 | * {
335 | * tag: 'div',
336 | * children: [],
337 | * props: {}
338 | * }
339 | */
340 | function h(tagName, props, children) {
341 | props = props || {};
342 | children = children || [];
343 | var key;
344 | if (tagName === 'text') {
345 | return {
346 | tag: tagName,
347 | text: props
348 | };
349 | } else {
350 | if ('key' in props) {
351 | key = props.key;
352 | delete props['key'];
353 | }
354 | for (var i = 0, l = children.length; i < l; i++) {
355 | // text
356 | typeof children[i] === 'string' &&
357 | (children[i] = h('text', children[i]));
358 | }
359 | return {
360 | tag: tagName,
361 | props: props,
362 | children: children,
363 | key: key
364 | };
365 | }
366 | }
367 |
368 | /**
369 | * diff
370 | * @param {VD} a
371 | * @param {VD} b
372 | */
373 | function diff(a, b) {
374 | var patches = {}, res = [];
375 | res.a = a;
376 | walk(a, b, patches, 0);
377 | for (var i = 0; patches[i]; i++) {
378 | patches[i].length &&
379 | res.push(patches[i])
380 | }
381 | return res;
382 | }
383 |
384 | module.exports = {
385 | h: h,
386 | diff: diff
387 | };
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | vd: {
3 | output: {
4 | filename: 'vd.js',
5 | library: 'vd',
6 | libraryTarget: 'amd'
7 | }
8 | },
9 | all: {
10 | output: {
11 | filename: 'all.js',
12 | library: 'vd',
13 | libraryTarget: 'amd'
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------