├── .gitignore ├── .travis.yml ├── LICENCE ├── README.md ├── diff.js ├── handle-thunk.js ├── is-thunk.js ├── is-vhook.js ├── is-vnode.js ├── is-vtext.js ├── is-widget.js ├── package.json ├── test ├── handle-thunk.js └── index.js ├── version.js ├── vnode.js ├── vpatch.js └── vtext.js /.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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | - "0.10" 5 | before_script: 6 | - npm install 7 | - npm install istanbul coveralls 8 | script: npm run travis-test 9 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Matt Esch. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vtree 2 | 3 | A realtime tree diffing algorithm 4 | 5 | Please note that this now lives under https://github.com/Matt-Esch/virtual-dom and all related issues shoud be opened there. This repository will eventually become a build artifact of `virtual-dom` for developers with an advanced usage pattern. 6 | 7 | ## Motivation 8 | 9 | `vtree` currently exists as part of `virtual-dom`. It is used for imitating 10 | diff operations between two `vnode` structures that imitate the structure of 11 | the active DOM node structure in the browser. 12 | 13 | ## Example 14 | 15 | ```js 16 | var VNode = require("vtree/vnode") 17 | var diff = require("vtree/diff") 18 | 19 | var leftNode = new VNode("div") 20 | var rightNode = new VNode("text") 21 | 22 | var patches = diff(leftNode, rightNode) 23 | /* 24 | -> { 25 | a: lefNode, 26 | 0: vpatch(rightNode) // a replace operation for the first node 27 | } 28 | */ 29 | ``` 30 | 31 | ## Installation 32 | 33 | `npm install vtree` 34 | 35 | ## Contributors 36 | 37 | - Matt Esch 38 | 39 | ## MIT Licenced 40 | -------------------------------------------------------------------------------- /diff.js: -------------------------------------------------------------------------------- 1 | var isArray = require("x-is-array") 2 | var isObject = require("is-object") 3 | 4 | var VPatch = require("./vpatch") 5 | var isVNode = require("./is-vnode") 6 | var isVText = require("./is-vtext") 7 | var isWidget = require("./is-widget") 8 | var isThunk = require("./is-thunk") 9 | var handleThunk = require("./handle-thunk") 10 | 11 | module.exports = diff 12 | 13 | function diff(a, b) { 14 | var patch = { a: a } 15 | walk(a, b, patch, 0) 16 | return patch 17 | } 18 | 19 | function walk(a, b, patch, index) { 20 | if (a === b) { 21 | if (isThunk(a) || isThunk(b)) { 22 | thunks(a, b, patch, index) 23 | } else { 24 | hooks(b, patch, index) 25 | } 26 | return 27 | } 28 | 29 | var apply = patch[index] 30 | 31 | if (isThunk(a) || isThunk(b)) { 32 | thunks(a, b, patch, index) 33 | } else if (b == null) { 34 | patch[index] = new VPatch(VPatch.REMOVE, a, b) 35 | destroyWidgets(a, patch, index) 36 | } else if (isVNode(b)) { 37 | if (isVNode(a)) { 38 | if (a.tagName === b.tagName && 39 | a.namespace === b.namespace && 40 | a.key === b.key) { 41 | var propsPatch = diffProps(a.properties, b.properties, b.hooks) 42 | if (propsPatch) { 43 | apply = appendPatch(apply, 44 | new VPatch(VPatch.PROPS, a, propsPatch)) 45 | } 46 | apply = diffChildren(a, b, patch, apply, index) 47 | } else { 48 | apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) 49 | destroyWidgets(a, patch, index) 50 | } 51 | } else { 52 | apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) 53 | destroyWidgets(a, patch, index) 54 | } 55 | } else if (isVText(b)) { 56 | if (!isVText(a)) { 57 | apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) 58 | destroyWidgets(a, patch, index) 59 | } else if (a.text !== b.text) { 60 | apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) 61 | } 62 | } else if (isWidget(b)) { 63 | apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b)) 64 | 65 | if (!isWidget(a)) { 66 | destroyWidgets(a, patch, index) 67 | } 68 | } 69 | 70 | if (apply) { 71 | patch[index] = apply 72 | } 73 | } 74 | 75 | function diffProps(a, b, hooks) { 76 | var diff 77 | 78 | for (var aKey in a) { 79 | if (!(aKey in b)) { 80 | diff = diff || {} 81 | diff[aKey] = undefined 82 | } 83 | 84 | var aValue = a[aKey] 85 | var bValue = b[aKey] 86 | 87 | if (hooks && aKey in hooks) { 88 | diff = diff || {} 89 | diff[aKey] = bValue 90 | } else { 91 | if (isObject(aValue) && isObject(bValue)) { 92 | if (getPrototype(bValue) !== getPrototype(aValue)) { 93 | diff = diff || {} 94 | diff[aKey] = bValue 95 | } else { 96 | var objectDiff = diffProps(aValue, bValue) 97 | if (objectDiff) { 98 | diff = diff || {} 99 | diff[aKey] = objectDiff 100 | } 101 | } 102 | } else if (aValue !== bValue) { 103 | diff = diff || {} 104 | diff[aKey] = bValue 105 | } 106 | } 107 | } 108 | 109 | for (var bKey in b) { 110 | if (!(bKey in a)) { 111 | diff = diff || {} 112 | diff[bKey] = b[bKey] 113 | } 114 | } 115 | 116 | return diff 117 | } 118 | 119 | function getPrototype(value) { 120 | if (Object.getPrototypeOf) { 121 | return Object.getPrototypeOf(value) 122 | } else if (value.__proto__) { 123 | return value.__proto__ 124 | } else if (value.constructor) { 125 | return value.constructor.prototype 126 | } 127 | } 128 | 129 | function diffChildren(a, b, patch, apply, index) { 130 | var aChildren = a.children 131 | var bChildren = reorder(aChildren, b.children) 132 | 133 | var aLen = aChildren.length 134 | var bLen = bChildren.length 135 | var len = aLen > bLen ? aLen : bLen 136 | 137 | for (var i = 0; i < len; i++) { 138 | var leftNode = aChildren[i] 139 | var rightNode = bChildren[i] 140 | index += 1 141 | 142 | if (!leftNode) { 143 | if (rightNode) { 144 | // Excess nodes in b need to be added 145 | apply = appendPatch(apply, 146 | new VPatch(VPatch.INSERT, null, rightNode)) 147 | } 148 | } else { 149 | walk(leftNode, rightNode, patch, index) 150 | } 151 | 152 | if (isVNode(leftNode) && leftNode.count) { 153 | index += leftNode.count 154 | } 155 | } 156 | 157 | if (bChildren.moves) { 158 | // Reorder nodes last 159 | apply = appendPatch(apply, new VPatch(VPatch.ORDER, a, bChildren.moves)) 160 | } 161 | 162 | return apply 163 | } 164 | 165 | // Patch records for all destroyed widgets must be added because we need 166 | // a DOM node reference for the destroy function 167 | function destroyWidgets(vNode, patch, index) { 168 | if (isWidget(vNode)) { 169 | if (typeof vNode.destroy === "function") { 170 | patch[index] = new VPatch(VPatch.REMOVE, vNode, null) 171 | } 172 | } else if (isVNode(vNode) && (vNode.hasWidgets || vNode.hasThunks)) { 173 | var children = vNode.children 174 | var len = children.length 175 | for (var i = 0; i < len; i++) { 176 | var child = children[i] 177 | index += 1 178 | 179 | destroyWidgets(child, patch, index) 180 | 181 | if (isVNode(child) && child.count) { 182 | index += child.count 183 | } 184 | } 185 | } else if (isThunk(vNode)) { 186 | thunks(vNode, null, patch, index) 187 | } 188 | } 189 | 190 | // Create a sub-patch for thunks 191 | function thunks(a, b, patch, index) { 192 | var nodes = handleThunk(a, b); 193 | var thunkPatch = diff(nodes.a, nodes.b) 194 | if (hasPatches(thunkPatch)) { 195 | patch[index] = new VPatch(VPatch.THUNK, null, thunkPatch) 196 | } 197 | } 198 | 199 | function hasPatches(patch) { 200 | for (var index in patch) { 201 | if (index !== "a") { 202 | return true; 203 | } 204 | } 205 | 206 | return false; 207 | } 208 | 209 | // Execute hooks when two nodes are identical 210 | function hooks(vNode, patch, index) { 211 | if (isVNode(vNode)) { 212 | if (vNode.hooks) { 213 | patch[index] = new VPatch(VPatch.PROPS, vNode.hooks, vNode.hooks) 214 | } 215 | 216 | if (vNode.descendantHooks) { 217 | var children = vNode.children 218 | var len = children.length 219 | for (var i = 0; i < len; i++) { 220 | var child = children[i] 221 | index += 1 222 | 223 | hooks(child, patch, index) 224 | 225 | if (isVNode(child) && child.count) { 226 | index += child.count 227 | } 228 | } 229 | } 230 | } 231 | } 232 | 233 | // List diff, naive left to right reordering 234 | function reorder(aChildren, bChildren) { 235 | 236 | var bKeys = keyIndex(bChildren) 237 | 238 | if (!bKeys) { 239 | return bChildren 240 | } 241 | 242 | var aKeys = keyIndex(aChildren) 243 | 244 | if (!aKeys) { 245 | return bChildren 246 | } 247 | 248 | var bMatch = {}, aMatch = {} 249 | 250 | for (var key in bKeys) { 251 | bMatch[bKeys[key]] = aKeys[key] 252 | } 253 | 254 | for (var key in aKeys) { 255 | aMatch[aKeys[key]] = bKeys[key] 256 | } 257 | 258 | var aLen = aChildren.length 259 | var bLen = bChildren.length 260 | var len = aLen > bLen ? aLen : bLen 261 | var shuffle = [] 262 | var freeIndex = 0 263 | var i = 0 264 | var moveIndex = 0 265 | var moves = {} 266 | var removes = moves.removes = {} 267 | var reverse = moves.reverse = {} 268 | var hasMoves = false 269 | 270 | while (freeIndex < len) { 271 | var move = aMatch[i] 272 | if (move !== undefined) { 273 | shuffle[i] = bChildren[move] 274 | if (move !== moveIndex) { 275 | moves[move] = moveIndex 276 | reverse[moveIndex] = move 277 | hasMoves = true 278 | } 279 | moveIndex++ 280 | } else if (i in aMatch) { 281 | shuffle[i] = undefined 282 | removes[i] = moveIndex++ 283 | hasMoves = true 284 | } else { 285 | while (bMatch[freeIndex] !== undefined) { 286 | freeIndex++ 287 | } 288 | 289 | if (freeIndex < len) { 290 | var freeChild = bChildren[freeIndex] 291 | if (freeChild) { 292 | shuffle[i] = freeChild 293 | if (freeIndex !== moveIndex) { 294 | hasMoves = true 295 | moves[freeIndex] = moveIndex 296 | reverse[moveIndex] = freeIndex 297 | } 298 | moveIndex++ 299 | } 300 | freeIndex++ 301 | } 302 | } 303 | i++ 304 | } 305 | 306 | if (hasMoves) { 307 | shuffle.moves = moves 308 | } 309 | 310 | return shuffle 311 | } 312 | 313 | function keyIndex(children) { 314 | var i, keys 315 | 316 | for (i = 0; i < children.length; i++) { 317 | var child = children[i] 318 | 319 | if (child.key !== undefined) { 320 | keys = keys || {} 321 | keys[child.key] = i 322 | } 323 | } 324 | 325 | return keys 326 | } 327 | 328 | function appendPatch(apply, patch) { 329 | if (apply) { 330 | if (isArray(apply)) { 331 | apply.push(patch) 332 | } else { 333 | apply = [apply, patch] 334 | } 335 | 336 | return apply 337 | } else { 338 | return patch 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /handle-thunk.js: -------------------------------------------------------------------------------- 1 | var isVNode = require("./is-vnode") 2 | var isVText = require("./is-vtext") 3 | var isWidget = require("./is-widget") 4 | var isThunk = require("./is-thunk") 5 | 6 | module.exports = handleThunk 7 | 8 | function handleThunk(a, b) { 9 | var renderedA = a 10 | var renderedB = b 11 | 12 | if (isThunk(b)) { 13 | renderedB = renderThunk(b, a) 14 | } 15 | 16 | if (isThunk(a)) { 17 | renderedA = renderThunk(a, null) 18 | } 19 | 20 | return { 21 | a: renderedA, 22 | b: renderedB 23 | } 24 | } 25 | 26 | function renderThunk(thunk, previous) { 27 | var renderedThunk = thunk.vnode 28 | 29 | if (!renderedThunk) { 30 | renderedThunk = thunk.vnode = thunk.render(previous) 31 | } 32 | 33 | if (!(isVNode(renderedThunk) || 34 | isVText(renderedThunk) || 35 | isWidget(renderedThunk))) { 36 | throw new Error("thunk did not return a valid node"); 37 | } 38 | 39 | return renderedThunk 40 | } 41 | -------------------------------------------------------------------------------- /is-thunk.js: -------------------------------------------------------------------------------- 1 | module.exports = isThunk 2 | 3 | function isThunk(t) { 4 | return t && t.type === "Thunk" 5 | } 6 | -------------------------------------------------------------------------------- /is-vhook.js: -------------------------------------------------------------------------------- 1 | module.exports = isHook 2 | 3 | function isHook(hook) { 4 | return hook && typeof hook.hook === "function" && 5 | !hook.hasOwnProperty("hook") 6 | } 7 | -------------------------------------------------------------------------------- /is-vnode.js: -------------------------------------------------------------------------------- 1 | var version = require("./version") 2 | 3 | module.exports = isVirtualNode 4 | 5 | function isVirtualNode(x) { 6 | return x && x.type === "VirtualNode" && x.version === version 7 | } 8 | -------------------------------------------------------------------------------- /is-vtext.js: -------------------------------------------------------------------------------- 1 | var version = require("./version") 2 | 3 | module.exports = isVirtualText 4 | 5 | function isVirtualText(x) { 6 | return x && x.type === "VirtualText" && x.version === version 7 | } 8 | -------------------------------------------------------------------------------- /is-widget.js: -------------------------------------------------------------------------------- 1 | module.exports = isWidget 2 | 3 | function isWidget(w) { 4 | return w && w.type === "Widget" 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vtree", 3 | "version": "0.0.22", 4 | "description": "a realtime tree diffing algorithm", 5 | "keywords": [], 6 | "author": "Matt Esch ", 7 | "repository": "git://github.com/Matt-Esch/vtree.git", 8 | "main": "index", 9 | "homepage": "https://github.com/Matt-Esch/vtree", 10 | "contributors": [ 11 | { 12 | "name": "Matt Esch" 13 | } 14 | ], 15 | "bugs": { 16 | "url": "https://github.com/Matt-Esch/vtree/issues", 17 | "email": "matt@mattesch.info" 18 | }, 19 | "dependencies": { 20 | "x-is-array": "^0.1.0", 21 | "is-object": "^0.1.2" 22 | }, 23 | "devDependencies": { 24 | "tape": "^3.0.3" 25 | }, 26 | "licenses": [ 27 | { 28 | "type": "MIT", 29 | "url": "http://github.com/Matt-Esch/vtree/raw/master/LICENSE" 30 | } 31 | ], 32 | "scripts": { 33 | "test": "node ./test/index.js", 34 | "cover": "istanbul cover --report none --print detail ./test/index.js", 35 | "view-cover": "istanbul report html && open ./coverage/index.html" 36 | }, 37 | "testling": { 38 | "files": "test/index.js", 39 | "browsers": [ 40 | "ie/8..latest", 41 | "firefox/16..latest", 42 | "firefox/nightly", 43 | "chrome/22..latest", 44 | "chrome/canary", 45 | "opera/12..latest", 46 | "opera/next", 47 | "safari/5.1..latest", 48 | "ipad/6.0..latest", 49 | "iphone/6.0..latest", 50 | "android-browser/4.2..latest" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/handle-thunk.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | 3 | var handleThunk = require("../handle-thunk") 4 | var VNode = require("../vnode") 5 | var VText = require("../vtext") 6 | 7 | test("render a new thunk to vnode", function (assert) { 8 | var aNode = { 9 | render: function (previous) { 10 | assert.error("Render should not be called for cached thunk") 11 | }, 12 | type: "Thunk" 13 | } 14 | 15 | aNode.vnode = new VNode("div") 16 | 17 | var renderedBNode = new VNode("div") 18 | 19 | var bNode = { 20 | render: function (previous) { 21 | assert.equal(previous, aNode) 22 | return renderedBNode 23 | }, 24 | type: "Thunk" 25 | } 26 | 27 | var result = handleThunk(aNode, bNode) 28 | 29 | assert.equal(result.a, aNode.vnode) 30 | assert.equal(result.b, renderedBNode) 31 | assert.equal(bNode.vnode, renderedBNode) 32 | assert.end() 33 | }) 34 | 35 | test("render a new thunk to vtext", function (assert) { 36 | var aNode = { 37 | render: function (previous) { 38 | assert.error("Render should not be called for cached thunk") 39 | }, 40 | type: "Thunk" 41 | } 42 | 43 | aNode.vnode = new VNode("div") 44 | 45 | var renderedBNode = new VText("text") 46 | 47 | var bNode = { 48 | render: function (previous) { 49 | assert.equal(previous, aNode) 50 | return renderedBNode 51 | }, 52 | type: "Thunk" 53 | } 54 | 55 | var result = handleThunk(aNode, bNode) 56 | 57 | assert.equal(result.a, aNode.vnode) 58 | assert.equal(result.b, renderedBNode) 59 | assert.equal(bNode.vnode, renderedBNode) 60 | assert.end() 61 | }) 62 | 63 | test("render a new thunk to a widget", function (assert) { 64 | var aNode = { 65 | render: function (previous) { 66 | assert.error("Render should not be called for cached thunk") 67 | }, 68 | type: "Thunk" 69 | } 70 | 71 | aNode.vnode = new VNode("div") 72 | 73 | var renderedBNode = { type: "Widget" } 74 | 75 | var bNode = { 76 | render: function (previous) { 77 | assert.equal(previous, aNode) 78 | return renderedBNode 79 | }, 80 | type: "Thunk" 81 | } 82 | 83 | var result = handleThunk(aNode, bNode) 84 | 85 | assert.equal(result.a, aNode.vnode) 86 | assert.equal(result.b, renderedBNode) 87 | assert.equal(bNode.vnode, renderedBNode) 88 | assert.end() 89 | }) 90 | 91 | test("render current thunk to a thunk throws exception", function (assert) { 92 | var aNode = { 93 | render: function (previous) { 94 | assert.error("Render should not be called for cached thunk") 95 | }, 96 | type: "Thunk" 97 | } 98 | 99 | aNode.vnode = new VNode("div") 100 | 101 | var bNode = { 102 | render: function (previous) { 103 | assert.equal(previous, aNode) 104 | return { type: "Thunk" } 105 | }, 106 | type: "Thunk" 107 | } 108 | 109 | var result 110 | 111 | try { 112 | handleThunk(aNode, bNode) 113 | } catch (e) { 114 | result = e 115 | } 116 | 117 | assert.equal(result.message, "thunk did not return a valid node") 118 | assert.end() 119 | }) 120 | 121 | test("render previous thunk to a thunk throws exception", function (assert) { 122 | var aNode = { 123 | render: function (previous) { 124 | assert.equal(previous, null) 125 | return { type: "Thunk" } 126 | }, 127 | type: "Thunk" 128 | } 129 | 130 | var renderedBNode = new VNode("div") 131 | 132 | var bNode = { 133 | render: function (previous) { 134 | assert.equal(previous, aNode) 135 | return renderedBNode 136 | }, 137 | type: "Thunk" 138 | } 139 | 140 | var result 141 | 142 | try { 143 | handleThunk(aNode, bNode) 144 | } catch (e) { 145 | result = e 146 | } 147 | 148 | assert.equal(result.message, "thunk did not return a valid node") 149 | assert.end() 150 | }) 151 | 152 | test("normal nodes are returned", function (assert) { 153 | var aNode = new VNode('div') 154 | var bNode = new VNode('div') 155 | 156 | var result = handleThunk(aNode, bNode) 157 | 158 | assert.equal(result.a, aNode) 159 | assert.equal(result.b, bNode) 160 | assert.end() 161 | }) 162 | 163 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require('./handle-thunk.js') 2 | -------------------------------------------------------------------------------- /version.js: -------------------------------------------------------------------------------- 1 | module.exports = "1" 2 | -------------------------------------------------------------------------------- /vnode.js: -------------------------------------------------------------------------------- 1 | var version = require("./version") 2 | var isVNode = require("./is-vnode") 3 | var isWidget = require("./is-widget") 4 | var isThunk = require("./is-thunk") 5 | var isVHook = require("./is-vhook") 6 | 7 | module.exports = VirtualNode 8 | 9 | var noProperties = {} 10 | var noChildren = [] 11 | 12 | function VirtualNode(tagName, properties, children, key, namespace) { 13 | this.tagName = tagName 14 | this.properties = properties || noProperties 15 | this.children = children || noChildren 16 | this.key = key != null ? String(key) : undefined 17 | this.namespace = (typeof namespace === "string") ? namespace : null 18 | 19 | var count = (children && children.length) || 0 20 | var descendants = 0 21 | var hasWidgets = false 22 | var hasThunks = false 23 | var descendantHooks = false 24 | var hooks 25 | 26 | for (var propName in properties) { 27 | if (properties.hasOwnProperty(propName)) { 28 | var property = properties[propName] 29 | if (isVHook(property)) { 30 | if (!hooks) { 31 | hooks = {} 32 | } 33 | 34 | hooks[propName] = property 35 | } 36 | } 37 | } 38 | 39 | for (var i = 0; i < count; i++) { 40 | var child = children[i] 41 | if (isVNode(child)) { 42 | descendants += child.count || 0 43 | 44 | if (!hasWidgets && child.hasWidgets) { 45 | hasWidgets = true 46 | } 47 | 48 | if (!hasThunks && child.hasThunks) { 49 | hasThunks = true 50 | } 51 | 52 | if (!descendantHooks && (child.hooks || child.descendantHooks)) { 53 | descendantHooks = true 54 | } 55 | } else if (!hasWidgets && isWidget(child)) { 56 | if (typeof child.destroy === "function") { 57 | hasWidgets = true 58 | } 59 | } else if (!hasThunks && isThunk(child)) { 60 | hasThunks = true; 61 | } 62 | } 63 | 64 | this.count = count + descendants 65 | this.hasWidgets = hasWidgets 66 | this.hasThunks = hasThunks 67 | this.hooks = hooks 68 | this.descendantHooks = descendantHooks 69 | } 70 | 71 | VirtualNode.prototype.version = version 72 | VirtualNode.prototype.type = "VirtualNode" 73 | -------------------------------------------------------------------------------- /vpatch.js: -------------------------------------------------------------------------------- 1 | var version = require("./version") 2 | 3 | VirtualPatch.NONE = 0 4 | VirtualPatch.VTEXT = 1 5 | VirtualPatch.VNODE = 2 6 | VirtualPatch.WIDGET = 3 7 | VirtualPatch.PROPS = 4 8 | VirtualPatch.ORDER = 5 9 | VirtualPatch.INSERT = 6 10 | VirtualPatch.REMOVE = 7 11 | VirtualPatch.THUNK = 8 12 | 13 | module.exports = VirtualPatch 14 | 15 | function VirtualPatch(type, vNode, patch) { 16 | this.type = Number(type) 17 | this.vNode = vNode 18 | this.patch = patch 19 | } 20 | 21 | VirtualPatch.prototype.version = version 22 | VirtualPatch.prototype.type = "VirtualPatch" 23 | -------------------------------------------------------------------------------- /vtext.js: -------------------------------------------------------------------------------- 1 | var version = require("./version") 2 | 3 | module.exports = VirtualText 4 | 5 | function VirtualText(text) { 6 | this.text = String(text) 7 | } 8 | 9 | VirtualText.prototype.version = version 10 | VirtualText.prototype.type = "VirtualText" 11 | --------------------------------------------------------------------------------