├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── build.js ├── dist ├── vdom-engine.common.js ├── vdom-engine.js ├── vdom-engine.min.js └── vdom-engine.min.js.gz ├── examples ├── counter-vanilla │ ├── index.html │ └── index.js └── js-repaint-perf │ ├── ENV.js │ ├── ga.js │ ├── index.html │ ├── react │ ├── app-component.js │ ├── app.js │ ├── index.html │ ├── lite.html │ ├── vdom-engine-app.js │ └── vdom-engine.html │ ├── styles.css │ └── vendor │ ├── JSXTransformer.js │ ├── bootstrap.min.css │ ├── memory-stats.js │ ├── monitor.js │ ├── react-lite.js │ ├── react-lite.min.js │ ├── react-with-addons.js │ ├── react-with-addons.min.js │ ├── react.addons.12.2.2.js │ └── react.min.js ├── gulpfile.js ├── package.json ├── src ├── CSSPropertyOperations.js ├── DOMPropertyOperations.js ├── constant.js ├── createElement.js ├── directive.js ├── event-system.js ├── index.js ├── render.js ├── util.js └── virtual-dom.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | coverage 5 | _book 6 | _site 7 | .publish -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | examples 4 | test 5 | coverage 6 | _book 7 | book.json 8 | docs 9 | src 10 | jest 11 | __tests__ 12 | webpack.config.js 13 | build.js 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 工业聚 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vdom-engine 2 | virtual-dom engine that help everyone to build their own modern view library and user interfaces 3 | 4 | React is awesome, but also too huge to be a view library, its way of handle props to dom is very limit and hard to extend by user. Sometimes we need a smaller and extensible virtual-dom library. 5 | 6 | # Installation 7 | 8 | ```shell 9 | npm install vdom-engine 10 | ``` 11 | 12 | # Getting Start 13 | 14 | vdom-engine is a react-like library, but only support `virtual-dom` and `stateless functional component`, these make it smaller and faster. Its api are the subset of React's api with a few different points. 15 | 16 | ## Handle props 17 | 18 | Unlike react, vdom-engine use `directive-style` to handle its props, now it has five build-in directives, `attr|prop|on|css|hook`. The `prop in props` which did not match any directive would be ignored. 19 | 20 | ```javascript 21 | import React from 'vdom-engine' 22 | const vdom = ( 23 |
30 | ) 31 | 32 | React.render(vdom, document.body) // always return undefined 33 | 34 | // use native dom api to get the dom created by vdom-engine 35 | let target = documnent.body.firstElementChild 36 | target.nodeName // div 37 | target.getAttribute('class') // my-class 38 | target.id // myId 39 | target.style.width // 100px 40 | target.removeEventListener('click', handleClick, false) // remove the event 41 | ``` 42 | 43 | ## Handle directives 44 | 45 | You can use the api `addDirective` to add a new directive, and use `removeDirective` to remove one of them. 46 | 47 | ```javascript 48 | import React from 'vdom-engine' 49 | 50 | /** 51 | * when in initialize, vdom-engine use directive.attach to map propValue to dom 52 | * when in update 53 | * if newValue is undefined or null, call directive.detach 54 | * else if newValue is not equal to preValue, call directive.attatch with newValue 55 | * propName in each method is 56 | */ 57 | 58 | // now jsx support a namespace attribute of http://www.w3.org/1999/xlink 59 | React.addDirective('attrns', { 60 | // how to use: attrns-propName={propValue} 61 | attach: (elem, propName, propValue) => { 62 | elem.setAttributeNS('http://www.w3.org/1999/xlink', propName, propValue) 63 | }, 64 | detach: (elem, propName) => { 65 | elem.removeAttribute(propName) 66 | } 67 | }) 68 | 69 | let vdom =
70 | 71 | // remove the directive 72 | // React.removeDirective('attrns') 73 | ``` 74 | ## Handle component 75 | 76 | vdom-engine support stateless functional component, the same as React. 77 | 78 | ```javascript 79 | import React from 'vdom-engine' 80 | let MyComponent = (props) => { 81 | return ( 82 |
{ props.children }
83 | ) 84 | } 85 | React.render( 86 | test children, 87 | document.body 88 | ) 89 | ``` 90 | ## Handle life-cycle methods 91 | 92 | Unlike React, vdom-engine do not support stateful component(`React.Component` or `React.createClass`), but every native-tag of virtual-dom has its life-cycle, sush as `div`, `span`, `p`, etc. 93 | 94 | Use the directive 'hook-lifyCycle' like below: 95 | 96 | ```javascript 97 | import React from 'vdom-engine' 98 | 99 | React.render( 100 |
some text
, 106 | document.body 107 | ) 108 | 109 | // call onWillMount 110 | // call onDidMount 111 | 112 | React.render( 113 |
update text
, 119 | document.body 120 | ) 121 | 122 | // call onWillUpdate 123 | // call onDidUpdate 124 | ``` 125 | 126 | ## Handle shouldComponentUpdate 127 | 128 | Please follow the [React-basic Memoization Section](https://github.com/reactjs/react-basic#memoization) 129 | 130 | ## Handle context 131 | 132 | Unlike React, context is out of `Component#getChildContext`, it pass by `React.render` from top to bottom, just like `props`. 133 | 134 | ```javascript 135 | import React from 'vdom-engine' 136 | // just add context argument explicitly 137 | let MyComponent = (props, context) => { 138 | return
props: {JSON.stringify(props)}, context: {JSON.stringify(context)}
139 | } 140 | 141 | let myContext = { 142 | a: 1, 143 | b: 2, 144 | c: 3 145 | } 146 | 147 | React.render(, document.body, myContext, () => { 148 | console.log('callback') 149 | }) 150 | 151 | ``` 152 | 153 | # Examples 154 | 155 | - [js-repaint-perf](http://lucifier129.github.io/vdom-engine/examples/js-repaint-perf) 156 | - [counter](http://lucifier129.github.io/vdom-engine/examples/counter-vanilla) 157 | 158 | # License 159 | MIT -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | // copy from Vue 2 | var fs = require('fs') 3 | var zlib = require('zlib') 4 | var rollup = require('rollup') 5 | var uglify = require('uglify-js') 6 | var babel = require('rollup-plugin-babel') 7 | var replace = require('rollup-plugin-replace') 8 | var version = process.env.VERSION || require('./package.json').version 9 | 10 | var banner = 11 | '/*!\n' + 12 | ' * vdom-engine.js v' + version + '\n' + 13 | ' * (c) ' + new Date().getFullYear() + ' Jade Gu\n' + 14 | ' * Released under the MIT License.\n' + 15 | ' */' 16 | 17 | runRollupTask('./src/index', 'vdom-engine') 18 | 19 | function runRollupTask(entry, filename) { 20 | 21 | // CommonJS build. 22 | // this is used as the "main" field in package.json 23 | // and used by bundlers like Webpack and Browserify. 24 | rollup.rollup({ 25 | entry: entry, 26 | plugins: [ 27 | babel({ 28 | loose: 'all' 29 | }) 30 | ] 31 | }) 32 | .then(function(bundle) { 33 | return write('dist/' + filename + '.common.js', bundle.generate({ 34 | format: 'cjs', 35 | banner: banner 36 | }).code) 37 | }) 38 | // Standalone Dev Build 39 | .then(function() { 40 | return rollup.rollup({ 41 | entry: entry, 42 | plugins: [ 43 | replace({ 44 | 'process.env.NODE_ENV': "'development'" 45 | }), 46 | babel({ 47 | loose: 'all' 48 | }) 49 | ] 50 | }) 51 | .then(function(bundle) { 52 | return write('dist/' + filename + '.js', bundle.generate({ 53 | format: 'umd', 54 | banner: banner, 55 | moduleName: 'Vengine' 56 | }).code) 57 | }) 58 | }) 59 | .then(function() { 60 | // Standalone Production Build 61 | return rollup.rollup({ 62 | entry: entry, 63 | plugins: [ 64 | replace({ 65 | 'process.env.NODE_ENV': "'production'" 66 | }), 67 | babel({ 68 | loose: 'all' 69 | }) 70 | ] 71 | }) 72 | .then(function(bundle) { 73 | var code = bundle.generate({ 74 | format: 'umd', 75 | moduleName: 'Vengine' 76 | }).code 77 | var minified = banner + '\n' + uglify.minify(code, { 78 | fromString: true 79 | }).code 80 | return write('dist/' + filename + '.min.js', minified) 81 | }) 82 | .then(zip) 83 | }) 84 | .catch(logError) 85 | 86 | function zip() { 87 | return new Promise(function(resolve, reject) { 88 | fs.readFile('dist/' + filename + '.min.js', function(err, buf) { 89 | if (err) return reject(err) 90 | zlib.gzip(buf, function(err, buf) { 91 | if (err) return reject(err) 92 | write('dist/' + filename + '.min.js.gz', buf).then(resolve) 93 | }) 94 | }) 95 | }) 96 | } 97 | } 98 | 99 | function write(dest, code) { 100 | return new Promise(function(resolve, reject) { 101 | fs.writeFile(dest, code, function(err) { 102 | if (err) return reject(err) 103 | console.log(blue(dest) + ' ' + getSize(code)) 104 | resolve() 105 | }) 106 | }) 107 | } 108 | 109 | function getSize(code) { 110 | return (code.length / 1024).toFixed(2) + 'kb' 111 | } 112 | 113 | function logError(e) { 114 | console.log(e) 115 | } 116 | 117 | function blue(str) { 118 | return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m' 119 | } 120 | -------------------------------------------------------------------------------- /dist/vdom-engine.common.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * vdom-engine.js v0.1.6 3 | * (c) 2016 Jade Gu 4 | * Released under the MIT License. 5 | */ 6 | 'use strict'; 7 | 8 | var directives = {}; 9 | var DIRECTIVE_SPEC = /^([^-]+)-(.+)$/; 10 | 11 | function addDirective(name, configs) { 12 | directives[name] = configs; 13 | } 14 | 15 | function removeDirective(name) { 16 | delete directives[name]; 17 | } 18 | 19 | var currentName = null; 20 | function matchDirective(propKey) { 21 | var matches = propKey.match(DIRECTIVE_SPEC); 22 | if (matches) { 23 | currentName = matches[2]; 24 | return directives[matches[1]]; 25 | } 26 | } 27 | 28 | function attachProp(elem, propKey, propValue) { 29 | var directive = matchDirective(propKey); 30 | if (directive) { 31 | directive.attach(elem, currentName, propValue); 32 | } 33 | } 34 | 35 | function detachProp(elem, propKey) { 36 | var directive = matchDirective(propKey); 37 | if (directive) { 38 | directive.detach(elem, currentName); 39 | } 40 | } 41 | 42 | function attachProps(elem, props) { 43 | for (var propKey in props) { 44 | if (propKey !== 'children' && props[propKey] != null) { 45 | attachProp(elem, propKey, props[propKey]); 46 | } 47 | } 48 | } 49 | 50 | function patchProps(elem, props, newProps) { 51 | for (var propKey in props) { 52 | if (propKey === 'children') { 53 | continue; 54 | } 55 | var newValue = newProps[propKey]; 56 | if (newValue !== props[propKey]) { 57 | if (newValue == null) { 58 | detachProp(elem, propKey); 59 | } else { 60 | attachProp(elem, propKey, newValue); 61 | } 62 | } 63 | } 64 | for (var propKey in newProps) { 65 | if (propKey === 'children') { 66 | continue; 67 | } 68 | if (!(propKey in props)) { 69 | attachProp(elem, propKey, newProps[propKey]); 70 | } 71 | } 72 | } 73 | 74 | var DOMPropDirective = { 75 | attach: attachDOMProp, 76 | detach: detachDOMProp 77 | }; 78 | 79 | var DOMAttrDirective = { 80 | attach: attachDOMAttr, 81 | detach: detachDOMAttr 82 | }; 83 | 84 | function attachDOMProp(elem, propName, propValue) { 85 | elem[propName] = propValue; 86 | } 87 | 88 | function detachDOMProp(elem, propName) { 89 | elem[propName] = ''; 90 | } 91 | 92 | function attachDOMAttr(elem, attrName, attrValue) { 93 | elem.setAttribute(attrName, attrValue + ''); 94 | } 95 | 96 | function detachDOMAttr(elem, attrName) { 97 | elem.removeAttribute(attrName); 98 | } 99 | 100 | function isFn(obj) { 101 | return typeof obj === 'function'; 102 | } 103 | 104 | var isArr = Array.isArray; 105 | 106 | function noop() {} 107 | 108 | function identity(obj) { 109 | return obj; 110 | } 111 | 112 | function pipe(fn1, fn2) { 113 | return function () { 114 | fn1.apply(this, arguments); 115 | return fn2.apply(this, arguments); 116 | }; 117 | } 118 | 119 | function flatEach(list, iteratee, a) { 120 | var len = list.length; 121 | var i = -1; 122 | 123 | while (len--) { 124 | var item = list[++i]; 125 | if (isArr(item)) { 126 | flatEach(item, iteratee, a); 127 | } else { 128 | iteratee(item, a); 129 | } 130 | } 131 | } 132 | 133 | function addItem(list, item) { 134 | list[list.length] = item; 135 | } 136 | 137 | function extend(to, from) { 138 | if (!from) { 139 | return to; 140 | } 141 | var keys = Object.keys(from); 142 | var i = keys.length; 143 | while (i--) { 144 | to[keys[i]] = from[keys[i]]; 145 | } 146 | return to; 147 | } 148 | 149 | var uid = 0; 150 | 151 | function getUid() { 152 | return ++uid; 153 | } 154 | 155 | if (!Object.freeze) { 156 | Object.freeze = identity; 157 | } 158 | 159 | // event config 160 | var notBubbleEvents = { 161 | onmouseleave: 1, 162 | onmouseenter: 1, 163 | onload: 1, 164 | onunload: 1, 165 | onscroll: 1, 166 | onfocus: 1, 167 | onblur: 1, 168 | onrowexit: 1, 169 | onbeforeunload: 1, 170 | onstop: 1, 171 | ondragdrop: 1, 172 | ondragenter: 1, 173 | ondragexit: 1, 174 | ondraggesture: 1, 175 | ondragover: 1, 176 | oncontextmenu: 1 177 | }; 178 | 179 | function detachEvents(node, props) { 180 | node.eventStore = null; 181 | for (var key in props) { 182 | // key start with 'on-' 183 | if (key.indexOf('on-') === 0) { 184 | key = getEventName(key); 185 | if (notBubbleEvents[key]) { 186 | node[key] = null; 187 | } 188 | } 189 | } 190 | } 191 | 192 | var eventDirective = { 193 | attach: attachEvent, 194 | detach: detachEvent 195 | }; 196 | 197 | // Mobile Safari does not fire properly bubble click events on 198 | // non-interactive elements, which means delegated click listeners do not 199 | // fire. The workaround for this bug involves attaching an empty click 200 | // listener on the target node. 201 | var inMobile = ('ontouchstart' in document); 202 | var emptyFunction = function emptyFunction() {}; 203 | var ON_CLICK_KEY = 'onclick'; 204 | 205 | function getEventName(key) { 206 | return key.replace(/^on-/, 'on').toLowerCase(); 207 | } 208 | 209 | var eventTypes = {}; 210 | function attachEvent(elem, eventType, listener) { 211 | eventType = 'on' + eventType; 212 | 213 | if (notBubbleEvents[eventType] === 1) { 214 | elem[eventType] = listener; 215 | return; 216 | } 217 | 218 | var eventStore = elem.eventStore || (elem.eventStore = {}); 219 | eventStore[eventType] = listener; 220 | 221 | if (!eventTypes[eventType]) { 222 | // onclick -> click 223 | document.addEventListener(eventType.substr(2), dispatchEvent, false); 224 | eventTypes[eventType] = true; 225 | } 226 | 227 | if (inMobile && eventType === ON_CLICK_KEY) { 228 | elem.addEventListener('click', emptyFunction, false); 229 | } 230 | 231 | var nodeName = elem.nodeName; 232 | 233 | if (eventType === 'onchange' && (nodeName === 'INPUT' || nodeName === 'TEXTAREA')) { 234 | attachEvent(elem, 'oninput', listener); 235 | } 236 | } 237 | 238 | function detachEvent(elem, eventType) { 239 | eventType = 'on' + eventType; 240 | if (notBubbleEvents[eventType] === 1) { 241 | elem[eventType] = null; 242 | return; 243 | } 244 | 245 | var eventStore = elem.eventStore || (elem.eventStore = {}); 246 | delete eventStore[eventType]; 247 | 248 | if (inMobile && eventType === ON_CLICK_KEY) { 249 | elem.removeEventListener('click', emptyFunction, false); 250 | } 251 | 252 | var nodeName = elem.nodeName; 253 | 254 | if (eventType === 'onchange' && (nodeName === 'INPUT' || nodeName === 'TEXTAREA')) { 255 | delete eventStore['oninput']; 256 | } 257 | } 258 | 259 | function dispatchEvent(event) { 260 | var target = event.target; 261 | var type = event.type; 262 | 263 | var eventType = 'on' + type; 264 | var syntheticEvent = null; 265 | while (target) { 266 | var _target = target; 267 | var eventStore = _target.eventStore; 268 | 269 | var listener = eventStore && eventStore[eventType]; 270 | if (!listener) { 271 | target = target.parentNode; 272 | continue; 273 | } 274 | if (!syntheticEvent) { 275 | syntheticEvent = createSyntheticEvent(event); 276 | } 277 | syntheticEvent.currentTarget = target; 278 | listener.call(target, syntheticEvent); 279 | if (syntheticEvent.$cancalBubble) { 280 | break; 281 | } 282 | target = target.parentNode; 283 | } 284 | } 285 | 286 | function createSyntheticEvent(nativeEvent) { 287 | var syntheticEvent = {}; 288 | var cancalBubble = function cancalBubble() { 289 | return syntheticEvent.$cancalBubble = true; 290 | }; 291 | syntheticEvent.nativeEvent = nativeEvent; 292 | syntheticEvent.persist = noop; 293 | for (var key in nativeEvent) { 294 | if (typeof nativeEvent[key] !== 'function') { 295 | syntheticEvent[key] = nativeEvent[key]; 296 | } else if (key === 'stopPropagation' || key === 'stopImmediatePropagation') { 297 | syntheticEvent[key] = cancalBubble; 298 | } else { 299 | syntheticEvent[key] = nativeEvent[key].bind(nativeEvent); 300 | } 301 | } 302 | return syntheticEvent; 303 | } 304 | 305 | var styleDirective = { 306 | attach: attachStyle, 307 | detach: detachStyle 308 | }; 309 | 310 | function attachStyle(elem, styleName, styleValue) { 311 | setStyleValue(elem.style, styleName, styleValue); 312 | } 313 | 314 | function detachStyle(elem, styleName) { 315 | elem.style[styleName] = ''; 316 | } 317 | 318 | /** 319 | * CSS properties which accept numbers but are not in units of "px". 320 | */ 321 | var isUnitlessNumber = { 322 | animationIterationCount: 1, 323 | borderImageOutset: 1, 324 | borderImageSlice: 1, 325 | borderImageWidth: 1, 326 | boxFlex: 1, 327 | boxFlexGroup: 1, 328 | boxOrdinalGroup: 1, 329 | columnCount: 1, 330 | flex: 1, 331 | flexGrow: 1, 332 | flexPositive: 1, 333 | flexShrink: 1, 334 | flexNegative: 1, 335 | flexOrder: 1, 336 | gridRow: 1, 337 | gridColumn: 1, 338 | fontWeight: 1, 339 | lineClamp: 1, 340 | lineHeight: 1, 341 | opacity: 1, 342 | order: 1, 343 | orphans: 1, 344 | tabSize: 1, 345 | widows: 1, 346 | zIndex: 1, 347 | zoom: 1, 348 | 349 | // SVG-related properties 350 | fillOpacity: 1, 351 | floodOpacity: 1, 352 | stopOpacity: 1, 353 | strokeDasharray: 1, 354 | strokeDashoffset: 1, 355 | strokeMiterlimit: 1, 356 | strokeOpacity: 1, 357 | strokeWidth: 1 358 | }; 359 | 360 | function prefixKey(prefix, key) { 361 | return prefix + key.charAt(0).toUpperCase() + key.substring(1); 362 | } 363 | 364 | var prefixes = ['Webkit', 'ms', 'Moz', 'O']; 365 | 366 | Object.keys(isUnitlessNumber).forEach(function (prop) { 367 | prefixes.forEach(function (prefix) { 368 | isUnitlessNumber[prefixKey(prefix, prop)] = 1; 369 | }); 370 | }); 371 | 372 | var RE_NUMBER = /^-?\d+(\.\d+)?$/; 373 | function setStyleValue(elemStyle, styleName, styleValue) { 374 | 375 | if (!isUnitlessNumber[styleName] && RE_NUMBER.test(styleValue)) { 376 | elemStyle[styleName] = styleValue + 'px'; 377 | return; 378 | } 379 | 380 | if (styleName === 'float') { 381 | styleName = 'cssFloat'; 382 | } 383 | 384 | if (styleValue == null || typeof styleValue === 'boolean') { 385 | styleValue = ''; 386 | } 387 | 388 | elemStyle[styleName] = styleValue; 389 | } 390 | 391 | var SVGNamespaceURI = 'http://www.w3.org/2000/svg'; 392 | var COMPONENT_ID = 'liteid'; 393 | var VELEMENT = 1; 394 | var VSTATELESS = 2; 395 | var VCOMMENT = 3; 396 | var HTML_KEY = 'prop-innerHTML'; 397 | var HOOK_WILL_MOUNT = 'hook-willMount'; 398 | var HOOK_DID_MOUNT = 'hook-didMount'; 399 | var HOOK_WILL_UPDATE = 'hook-willUpdate'; 400 | var HOOK_DID_UPDATE = 'hook-didUpdate'; 401 | var HOOK_WILL_UNMOUNT = 'hook-willUnmount'; 402 | 403 | function initVnode(vnode, context, namespaceURI) { 404 | var vtype = vnode.vtype; 405 | 406 | var node = null; 407 | if (!vtype) { 408 | // init text 409 | node = document.createTextNode(vnode); 410 | } else if (vtype === VELEMENT) { 411 | // init element 412 | node = initVelem(vnode, context, namespaceURI); 413 | } else if (vtype === VSTATELESS) { 414 | // init stateless component 415 | node = initVstateless(vnode, context, namespaceURI); 416 | } else if (vtype === VCOMMENT) { 417 | // init comment 418 | node = document.createComment('react-empty: ' + vnode.uid); 419 | } 420 | return node; 421 | } 422 | 423 | function updateVnode(vnode, newVnode, node, context) { 424 | var vtype = vnode.vtype; 425 | 426 | if (vtype === VSTATELESS) { 427 | return updateVstateless(vnode, newVnode, node, context); 428 | } 429 | 430 | // ignore VCOMMENT and other vtypes 431 | if (vtype !== VELEMENT) { 432 | return node; 433 | } 434 | 435 | if (vnode.props[HTML_KEY] != null) { 436 | updateVelem(vnode, newVnode, node, context); 437 | initVchildren(newVnode, node, context); 438 | } else { 439 | updateVChildren(vnode, newVnode, node, context); 440 | updateVelem(vnode, newVnode, node, context); 441 | } 442 | return node; 443 | } 444 | 445 | function updateVChildren(vnode, newVnode, node, context) { 446 | var patches = { 447 | removes: [], 448 | updates: [], 449 | creates: [] 450 | }; 451 | // console.time('time') 452 | diffVchildren(patches, vnode, newVnode, node, context); 453 | 454 | flatEach(patches.removes, applyDestroy); 455 | 456 | flatEach(patches.updates, applyUpdate); 457 | 458 | flatEach(patches.creates, applyCreate); 459 | // console.timeEnd('time') 460 | } 461 | 462 | function applyUpdate(data) { 463 | if (!data) { 464 | return; 465 | } 466 | var node = data.node; 467 | 468 | // update 469 | if (data.shouldUpdate) { 470 | var vnode = data.vnode; 471 | var newVnode = data.newVnode; 472 | var context = data.context; 473 | 474 | if (!vnode.vtype) { 475 | node.nodeValue = newVnode; 476 | } else if (vnode.vtype === VELEMENT) { 477 | updateVelem(vnode, newVnode, node, context); 478 | } else if (vnode.vtype === VSTATELESS) { 479 | node = updateVstateless(vnode, newVnode, node, context); 480 | } 481 | } 482 | 483 | // re-order 484 | if (data.index !== data.fromIndex) { 485 | var existNode = node.parentNode.childNodes[index]; 486 | if (existNode !== node) { 487 | node.parentNode.insertBefore(node, existNode); 488 | } 489 | } 490 | } 491 | 492 | function applyDestroy(data) { 493 | destroyVnode(data.vnode, data.node); 494 | data.node.parentNode.removeChild(data.node); 495 | } 496 | 497 | function applyCreate(data) { 498 | var parentNode = data.parentNode; 499 | var existNode = parentNode.childNodes[data.index]; 500 | var node = initVnode(data.vnode, data.context, parentNode.namespaceURI); 501 | parentNode.insertBefore(node, existNode); 502 | } 503 | 504 | /** 505 | * Only vnode which has props.children need to call destroy function 506 | * to check whether subTree has component that need to call lify-cycle method and release cache. 507 | */ 508 | 509 | function destroyVnode(vnode, node) { 510 | var vtype = vnode.vtype; 511 | 512 | if (vtype === VELEMENT) { 513 | // destroy element 514 | destroyVelem(vnode, node); 515 | } else if (vtype === VSTATELESS) { 516 | // destroy stateless component 517 | destroyVstateless(vnode, node); 518 | } 519 | } 520 | 521 | function initVelem(velem, context, namespaceURI) { 522 | var type = velem.type; 523 | var props = velem.props; 524 | 525 | var node = null; 526 | 527 | if (type === 'svg' || namespaceURI === SVGNamespaceURI) { 528 | node = document.createElementNS(SVGNamespaceURI, type); 529 | namespaceURI = SVGNamespaceURI; 530 | } else { 531 | node = document.createElement(type); 532 | } 533 | 534 | initVchildren(node, props.children, context); 535 | attachProps(node, props); 536 | 537 | if (props[HOOK_WILL_MOUNT]) { 538 | props[HOOK_WILL_MOUNT].call(null, node, props); 539 | } 540 | 541 | if (props[HOOK_DID_MOUNT]) { 542 | addItem(pendingHooks, { 543 | type: HOOK_DID_MOUNT, 544 | node: node, 545 | props: props 546 | }); 547 | } 548 | 549 | return node; 550 | } 551 | 552 | function initVchildren(node, vchildren, context) { 553 | var namespaceURI = node.namespaceURI; 554 | 555 | for (var i = 0, len = vchildren.length; i < len; i++) { 556 | node.appendChild(initVnode(vchildren[i], context, namespaceURI)); 557 | } 558 | } 559 | 560 | function diffVchildren(patches, vnode, newVnode, node, context) { 561 | var childNodes = node.childNodes; 562 | 563 | var vchildren = vnode.props.children; 564 | var newVchildren = newVnode.props.children; 565 | var vchildrenLen = vchildren.length; 566 | var newVchildrenLen = newVchildren.length; 567 | 568 | if (vchildrenLen === 0) { 569 | if (newVchildrenLen === 0) { 570 | return; 571 | } 572 | for (var i = 0; i < newVchildrenLen; i++) { 573 | addItem(patches.creates, { 574 | vnode: newVchildren[i], 575 | parentNode: node, 576 | context: context, 577 | index: i 578 | }); 579 | } 580 | return; 581 | } else if (newVchildrenLen === 0) { 582 | for (var i = 0; i < vchildrenLen; i++) { 583 | addItem(patches.removes, { 584 | vnode: vchildren[i], 585 | node: childNodes[i] 586 | }); 587 | } 588 | return; 589 | } 590 | 591 | var matches = {}; 592 | var updates = Array(newVchildrenLen); 593 | var removes = null; 594 | var creates = null; 595 | 596 | // isEqual 597 | for (var i = 0; i < vchildrenLen; i++) { 598 | var _vnode = vchildren[i]; 599 | for (var j = 0; j < newVchildrenLen; j++) { 600 | if (updates[j]) { 601 | continue; 602 | } 603 | var _newVnode = newVchildren[j]; 604 | if (_vnode === _newVnode) { 605 | var shouldUpdate = false; 606 | if (context) { 607 | if (_vnode.vtype === VSTATELESS) { 608 | /** 609 | * stateless component: (props, context) =>
610 | * if context argument is specified and context is exist, should re-render 611 | */ 612 | if (_vnode.type.length > 1) { 613 | shouldUpdate = true; 614 | } 615 | } 616 | } 617 | updates[j] = { 618 | shouldUpdate: shouldUpdate, 619 | vnode: _vnode, 620 | newVnode: _newVnode, 621 | node: childNodes[i], 622 | context: context, 623 | index: j, 624 | fromIndex: i 625 | }; 626 | matches[i] = true; 627 | break; 628 | } 629 | } 630 | } 631 | 632 | // isSimilar 633 | for (var i = 0; i < vchildrenLen; i++) { 634 | if (matches[i]) { 635 | continue; 636 | } 637 | var _vnode2 = vchildren[i]; 638 | var shouldRemove = true; 639 | for (var j = 0; j < newVchildrenLen; j++) { 640 | if (updates[j]) { 641 | continue; 642 | } 643 | var _newVnode2 = newVchildren[j]; 644 | if (_newVnode2.type === _vnode2.type && _newVnode2.key === _vnode2.key) { 645 | updates[j] = { 646 | shouldUpdate: true, 647 | vnode: _vnode2, 648 | newVnode: _newVnode2, 649 | node: childNodes[i], 650 | context: context, 651 | index: j, 652 | fromIndex: i 653 | }; 654 | shouldRemove = false; 655 | break; 656 | } 657 | } 658 | if (shouldRemove) { 659 | if (!removes) { 660 | removes = []; 661 | } 662 | addItem(removes, { 663 | vnode: _vnode2, 664 | node: childNodes[i] 665 | }); 666 | } 667 | } 668 | 669 | for (var i = 0; i < newVchildrenLen; i++) { 670 | var item = updates[i]; 671 | if (!item) { 672 | if (!creates) { 673 | creates = []; 674 | } 675 | addItem(creates, { 676 | vnode: newVchildren[i], 677 | parentNode: node, 678 | context: context, 679 | index: i 680 | }); 681 | } else if (item.vnode.vtype === VELEMENT) { 682 | diffVchildren(patches, item.vnode, item.newVnode, item.node, item.context); 683 | } 684 | } 685 | 686 | if (removes) { 687 | addItem(patches.removes, removes); 688 | } 689 | if (creates) { 690 | addItem(patches.creates, creates); 691 | } 692 | addItem(patches.updates, updates); 693 | } 694 | 695 | function updateVelem(velem, newVelem, node) { 696 | var newProps = newVelem.props; 697 | if (newProps[HOOK_WILL_UPDATE]) { 698 | newProps[HOOK_WILL_UPDATE].call(null, node, newProps); 699 | } 700 | patchProps(node, velem.props, newProps); 701 | if (newProps[HOOK_DID_UPDATE]) { 702 | newProps[HOOK_DID_UPDATE].call(null, node, newProps); 703 | } 704 | return node; 705 | } 706 | 707 | function destroyVelem(velem, node) { 708 | var props = velem.props; 709 | 710 | var vchildren = props.children; 711 | var childNodes = node.childNodes; 712 | 713 | for (var i = 0, len = vchildren.length; i < len; i++) { 714 | destroyVnode(vchildren[i], childNodes[i]); 715 | } 716 | 717 | if (isFn(props[HOOK_WILL_UNMOUNT])) { 718 | props[HOOK_WILL_UNMOUNT].call(null, node, props); 719 | } 720 | 721 | detachEvents(node, props); 722 | } 723 | 724 | function initVstateless(vstateless, context, namespaceURI) { 725 | var vnode = renderVstateless(vstateless, context); 726 | var node = initVnode(vnode, context, namespaceURI); 727 | node.cache = node.cache || {}; 728 | node.cache[vstateless.uid] = vnode; 729 | return node; 730 | } 731 | 732 | function updateVstateless(vstateless, newVstateless, node, context) { 733 | var uid = vstateless.uid; 734 | var vnode = node.cache[uid]; 735 | delete node.cache[uid]; 736 | var newVnode = renderVstateless(newVstateless, context); 737 | var newNode = compareTwoVnodes(vnode, newVnode, node, context); 738 | newNode.cache = newNode.cache || {}; 739 | newNode.cache[newVstateless.uid] = newVnode; 740 | if (newNode !== node) { 741 | extend(newNode.cache, node.cache); 742 | } 743 | return newNode; 744 | } 745 | 746 | function destroyVstateless(vstateless, node) { 747 | var uid = vstateless.uid; 748 | var vnode = node.cache[uid]; 749 | delete node.cache[uid]; 750 | destroyVnode(vnode, node); 751 | } 752 | 753 | function renderVstateless(vstateless, context) { 754 | var factory = vstateless.type; 755 | var props = vstateless.props; 756 | 757 | var vnode = factory(props, context); 758 | if (vnode && vnode.render) { 759 | vnode = vnode.render(); 760 | } 761 | if (vnode === null || vnode === false) { 762 | vnode = { 763 | vtype: VCOMMENT, 764 | uid: getUid() 765 | }; 766 | } else if (!vnode || !vnode.vtype) { 767 | throw new Error('@' + factory.name + '#render:You may have returned undefined, an array or some other invalid object'); 768 | } 769 | return vnode; 770 | } 771 | 772 | var pendingHooks = []; 773 | var clearPendingMount = function clearPendingMount() { 774 | var len = pendingHooks.length; 775 | if (!len) { 776 | return; 777 | } 778 | var list = pendingHooks; 779 | var i = -1; 780 | while (len--) { 781 | var item = list[++i]; 782 | item.props[item.type].call(null, item.node, item.props); 783 | } 784 | pendingHooks.length = 0; 785 | }; 786 | 787 | function compareTwoVnodes(vnode, newVnode, node, context) { 788 | var newNode = node; 789 | if (newVnode == null) { 790 | // remove 791 | destroyVnode(vnode, node); 792 | node.parentNode.removeChild(node); 793 | } else if (vnode.type !== newVnode.type || vnode.key !== newVnode.key) { 794 | // replace 795 | destroyVnode(vnode, node); 796 | newNode = initVnode(newVnode, context, node.namespaceURI); 797 | node.parentNode.replaceChild(newNode, node); 798 | } else if (vnode !== newVnode || context) { 799 | // same type and same key -> update 800 | newNode = updateVnode(vnode, newVnode, node, context); 801 | } 802 | return newNode; 803 | } 804 | 805 | var pendingRendering = {}; 806 | var vnodeStore = {}; 807 | 808 | function render(vnode, container, context, callback) { 809 | if (!vnode.vtype) { 810 | throw new Error('cannot render ' + vnode + ' to container'); 811 | } 812 | var id = container[COMPONENT_ID] || (container[COMPONENT_ID] = getUid()); 813 | var argsCache = pendingRendering[id]; 814 | 815 | if (isFn(context)) { 816 | callback = context; 817 | context = undefined; 818 | } 819 | 820 | // component lify cycle method maybe call root rendering 821 | // should bundle them and render by only one time 822 | if (argsCache) { 823 | if (argsCache === true) { 824 | pendingRendering[id] = { 825 | vnode: vnode, 826 | context: context, 827 | callback: callback 828 | }; 829 | } else { 830 | argsCache.vnode = vnode; 831 | argsCache.context = context; 832 | if (callback) { 833 | argsCache.callback = argsCache.callback ? pipe(argsCache.callback, callback) : callback; 834 | } 835 | } 836 | return; 837 | } 838 | 839 | pendingRendering[id] = true; 840 | if (vnodeStore.hasOwnProperty(id)) { 841 | compareTwoVnodes(vnodeStore[id], vnode, container.firstChild, context); 842 | } else { 843 | var rootNode = initVnode(vnode, context, container.namespaceURI); 844 | var childNode = null; 845 | while (childNode = container.lastChild) { 846 | container.removeChild(childNode); 847 | } 848 | container.appendChild(rootNode); 849 | } 850 | vnodeStore[id] = vnode; 851 | clearPendingMount(); 852 | 853 | argsCache = pendingRendering[id]; 854 | pendingRendering[id] = null; 855 | 856 | if (typeof argsCache === 'object') { 857 | render(argsCache.vnode, container, argsCache.context, argsCache.callback); 858 | } 859 | 860 | if (callback) { 861 | callback(); 862 | } 863 | } 864 | 865 | function destroy(container) { 866 | if (!container.nodeName) { 867 | throw new Error('expect node'); 868 | } 869 | var id = container[COMPONENT_ID]; 870 | var vnode = null; 871 | if (vnode = vnodeStore[id]) { 872 | destroyVnode(vnode, container.firstChild); 873 | container.removeChild(container.firstChild); 874 | delete vnodeStore[id]; 875 | delete pendingRendering[id]; 876 | return true; 877 | } 878 | return false; 879 | } 880 | 881 | function createElement(type, props) /* ...children */{ 882 | var finalProps = {}; 883 | var key = null; 884 | if (props != null) { 885 | for (var propKey in props) { 886 | if (propKey === 'key') { 887 | if (props.key !== undefined) { 888 | key = '' + props.key; 889 | } 890 | } else { 891 | finalProps[propKey] = props[propKey]; 892 | } 893 | } 894 | } 895 | 896 | var defaultProps = type.defaultProps; 897 | if (defaultProps) { 898 | for (var propKey in defaultProps) { 899 | if (finalProps[propKey] === undefined) { 900 | finalProps[propKey] = defaultProps[propKey]; 901 | } 902 | } 903 | } 904 | 905 | var argsLen = arguments.length; 906 | var finalChildren = []; 907 | 908 | if (argsLen > 2) { 909 | for (var i = 2; i < argsLen; i++) { 910 | var child = arguments[i]; 911 | if (isArr(child)) { 912 | flatEach(child, collectChild, finalChildren); 913 | } else { 914 | collectChild(child, finalChildren); 915 | } 916 | } 917 | } 918 | 919 | finalProps.children = finalChildren; 920 | 921 | var vtype = null; 922 | if (typeof type === 'string') { 923 | vtype = VELEMENT; 924 | } else if (typeof type === 'function') { 925 | vtype = VSTATELESS; 926 | } else { 927 | throw new Error('unexpect type [ ' + type + ' ]'); 928 | } 929 | 930 | var vnode = { 931 | vtype: vtype, 932 | type: type, 933 | props: finalProps, 934 | key: key 935 | }; 936 | if (vtype === VSTATELESS) { 937 | vnode.uid = getUid(); 938 | } 939 | 940 | return vnode; 941 | } 942 | 943 | function isValidElement(obj) { 944 | return obj != null && !!obj.vtype; 945 | } 946 | 947 | function createFactory(type) { 948 | var factory = function factory() { 949 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 950 | args[_key] = arguments[_key]; 951 | } 952 | 953 | return createElement.apply(undefined, [type].concat(args)); 954 | }; 955 | factory.type = type; 956 | return factory; 957 | } 958 | 959 | function collectChild(child, children) { 960 | if (child != null && typeof child !== 'boolean') { 961 | children[children.length] = child.vtype ? child : '' + child; 962 | } 963 | } 964 | 965 | addDirective('attr', DOMAttrDirective); 966 | addDirective('prop', DOMPropDirective); 967 | addDirective('on', eventDirective); 968 | addDirective('css', styleDirective); 969 | 970 | var Vengine = { 971 | createElement: createElement, 972 | createFactory: createFactory, 973 | isValidElement: isValidElement, 974 | addDirective: addDirective, 975 | removeDirective: removeDirective, 976 | render: render, 977 | destroy: destroy 978 | }; 979 | 980 | module.exports = Vengine; -------------------------------------------------------------------------------- /dist/vdom-engine.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * vdom-engine.js v0.1.6 3 | * (c) 2016 Jade Gu 4 | * Released under the MIT License. 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 8 | typeof define === 'function' && define.amd ? define(factory) : 9 | global.Vengine = factory(); 10 | }(this, function () { 'use strict'; 11 | 12 | var directives = {}; 13 | var DIRECTIVE_SPEC = /^([^-]+)-(.+)$/; 14 | 15 | function addDirective(name, configs) { 16 | directives[name] = configs; 17 | } 18 | 19 | function removeDirective(name) { 20 | delete directives[name]; 21 | } 22 | 23 | var currentName = null; 24 | function matchDirective(propKey) { 25 | var matches = propKey.match(DIRECTIVE_SPEC); 26 | if (matches) { 27 | currentName = matches[2]; 28 | return directives[matches[1]]; 29 | } 30 | } 31 | 32 | function attachProp(elem, propKey, propValue) { 33 | var directive = matchDirective(propKey); 34 | if (directive) { 35 | directive.attach(elem, currentName, propValue); 36 | } 37 | } 38 | 39 | function detachProp(elem, propKey) { 40 | var directive = matchDirective(propKey); 41 | if (directive) { 42 | directive.detach(elem, currentName); 43 | } 44 | } 45 | 46 | function attachProps(elem, props) { 47 | for (var propKey in props) { 48 | if (propKey !== 'children' && props[propKey] != null) { 49 | attachProp(elem, propKey, props[propKey]); 50 | } 51 | } 52 | } 53 | 54 | function patchProps(elem, props, newProps) { 55 | for (var propKey in props) { 56 | if (propKey === 'children') { 57 | continue; 58 | } 59 | var newValue = newProps[propKey]; 60 | if (newValue !== props[propKey]) { 61 | if (newValue == null) { 62 | detachProp(elem, propKey); 63 | } else { 64 | attachProp(elem, propKey, newValue); 65 | } 66 | } 67 | } 68 | for (var propKey in newProps) { 69 | if (propKey === 'children') { 70 | continue; 71 | } 72 | if (!(propKey in props)) { 73 | attachProp(elem, propKey, newProps[propKey]); 74 | } 75 | } 76 | } 77 | 78 | var DOMPropDirective = { 79 | attach: attachDOMProp, 80 | detach: detachDOMProp 81 | }; 82 | 83 | var DOMAttrDirective = { 84 | attach: attachDOMAttr, 85 | detach: detachDOMAttr 86 | }; 87 | 88 | function attachDOMProp(elem, propName, propValue) { 89 | elem[propName] = propValue; 90 | } 91 | 92 | function detachDOMProp(elem, propName) { 93 | elem[propName] = ''; 94 | } 95 | 96 | function attachDOMAttr(elem, attrName, attrValue) { 97 | elem.setAttribute(attrName, attrValue + ''); 98 | } 99 | 100 | function detachDOMAttr(elem, attrName) { 101 | elem.removeAttribute(attrName); 102 | } 103 | 104 | function isFn(obj) { 105 | return typeof obj === 'function'; 106 | } 107 | 108 | var isArr = Array.isArray; 109 | 110 | function noop() {} 111 | 112 | function identity(obj) { 113 | return obj; 114 | } 115 | 116 | function pipe(fn1, fn2) { 117 | return function () { 118 | fn1.apply(this, arguments); 119 | return fn2.apply(this, arguments); 120 | }; 121 | } 122 | 123 | function flatEach(list, iteratee, a) { 124 | var len = list.length; 125 | var i = -1; 126 | 127 | while (len--) { 128 | var item = list[++i]; 129 | if (isArr(item)) { 130 | flatEach(item, iteratee, a); 131 | } else { 132 | iteratee(item, a); 133 | } 134 | } 135 | } 136 | 137 | function addItem(list, item) { 138 | list[list.length] = item; 139 | } 140 | 141 | function extend(to, from) { 142 | if (!from) { 143 | return to; 144 | } 145 | var keys = Object.keys(from); 146 | var i = keys.length; 147 | while (i--) { 148 | to[keys[i]] = from[keys[i]]; 149 | } 150 | return to; 151 | } 152 | 153 | var uid = 0; 154 | 155 | function getUid() { 156 | return ++uid; 157 | } 158 | 159 | if (!Object.freeze) { 160 | Object.freeze = identity; 161 | } 162 | 163 | // event config 164 | var notBubbleEvents = { 165 | onmouseleave: 1, 166 | onmouseenter: 1, 167 | onload: 1, 168 | onunload: 1, 169 | onscroll: 1, 170 | onfocus: 1, 171 | onblur: 1, 172 | onrowexit: 1, 173 | onbeforeunload: 1, 174 | onstop: 1, 175 | ondragdrop: 1, 176 | ondragenter: 1, 177 | ondragexit: 1, 178 | ondraggesture: 1, 179 | ondragover: 1, 180 | oncontextmenu: 1 181 | }; 182 | 183 | function detachEvents(node, props) { 184 | node.eventStore = null; 185 | for (var key in props) { 186 | // key start with 'on-' 187 | if (key.indexOf('on-') === 0) { 188 | key = getEventName(key); 189 | if (notBubbleEvents[key]) { 190 | node[key] = null; 191 | } 192 | } 193 | } 194 | } 195 | 196 | var eventDirective = { 197 | attach: attachEvent, 198 | detach: detachEvent 199 | }; 200 | 201 | // Mobile Safari does not fire properly bubble click events on 202 | // non-interactive elements, which means delegated click listeners do not 203 | // fire. The workaround for this bug involves attaching an empty click 204 | // listener on the target node. 205 | var inMobile = ('ontouchstart' in document); 206 | var emptyFunction = function emptyFunction() {}; 207 | var ON_CLICK_KEY = 'onclick'; 208 | 209 | function getEventName(key) { 210 | return key.replace(/^on-/, 'on').toLowerCase(); 211 | } 212 | 213 | var eventTypes = {}; 214 | function attachEvent(elem, eventType, listener) { 215 | eventType = 'on' + eventType; 216 | 217 | if (notBubbleEvents[eventType] === 1) { 218 | elem[eventType] = listener; 219 | return; 220 | } 221 | 222 | var eventStore = elem.eventStore || (elem.eventStore = {}); 223 | eventStore[eventType] = listener; 224 | 225 | if (!eventTypes[eventType]) { 226 | // onclick -> click 227 | document.addEventListener(eventType.substr(2), dispatchEvent, false); 228 | eventTypes[eventType] = true; 229 | } 230 | 231 | if (inMobile && eventType === ON_CLICK_KEY) { 232 | elem.addEventListener('click', emptyFunction, false); 233 | } 234 | 235 | var nodeName = elem.nodeName; 236 | 237 | if (eventType === 'onchange' && (nodeName === 'INPUT' || nodeName === 'TEXTAREA')) { 238 | attachEvent(elem, 'oninput', listener); 239 | } 240 | } 241 | 242 | function detachEvent(elem, eventType) { 243 | eventType = 'on' + eventType; 244 | if (notBubbleEvents[eventType] === 1) { 245 | elem[eventType] = null; 246 | return; 247 | } 248 | 249 | var eventStore = elem.eventStore || (elem.eventStore = {}); 250 | delete eventStore[eventType]; 251 | 252 | if (inMobile && eventType === ON_CLICK_KEY) { 253 | elem.removeEventListener('click', emptyFunction, false); 254 | } 255 | 256 | var nodeName = elem.nodeName; 257 | 258 | if (eventType === 'onchange' && (nodeName === 'INPUT' || nodeName === 'TEXTAREA')) { 259 | delete eventStore['oninput']; 260 | } 261 | } 262 | 263 | function dispatchEvent(event) { 264 | var target = event.target; 265 | var type = event.type; 266 | 267 | var eventType = 'on' + type; 268 | var syntheticEvent = null; 269 | while (target) { 270 | var _target = target; 271 | var eventStore = _target.eventStore; 272 | 273 | var listener = eventStore && eventStore[eventType]; 274 | if (!listener) { 275 | target = target.parentNode; 276 | continue; 277 | } 278 | if (!syntheticEvent) { 279 | syntheticEvent = createSyntheticEvent(event); 280 | } 281 | syntheticEvent.currentTarget = target; 282 | listener.call(target, syntheticEvent); 283 | if (syntheticEvent.$cancalBubble) { 284 | break; 285 | } 286 | target = target.parentNode; 287 | } 288 | } 289 | 290 | function createSyntheticEvent(nativeEvent) { 291 | var syntheticEvent = {}; 292 | var cancalBubble = function cancalBubble() { 293 | return syntheticEvent.$cancalBubble = true; 294 | }; 295 | syntheticEvent.nativeEvent = nativeEvent; 296 | syntheticEvent.persist = noop; 297 | for (var key in nativeEvent) { 298 | if (typeof nativeEvent[key] !== 'function') { 299 | syntheticEvent[key] = nativeEvent[key]; 300 | } else if (key === 'stopPropagation' || key === 'stopImmediatePropagation') { 301 | syntheticEvent[key] = cancalBubble; 302 | } else { 303 | syntheticEvent[key] = nativeEvent[key].bind(nativeEvent); 304 | } 305 | } 306 | return syntheticEvent; 307 | } 308 | 309 | var styleDirective = { 310 | attach: attachStyle, 311 | detach: detachStyle 312 | }; 313 | 314 | function attachStyle(elem, styleName, styleValue) { 315 | setStyleValue(elem.style, styleName, styleValue); 316 | } 317 | 318 | function detachStyle(elem, styleName) { 319 | elem.style[styleName] = ''; 320 | } 321 | 322 | /** 323 | * CSS properties which accept numbers but are not in units of "px". 324 | */ 325 | var isUnitlessNumber = { 326 | animationIterationCount: 1, 327 | borderImageOutset: 1, 328 | borderImageSlice: 1, 329 | borderImageWidth: 1, 330 | boxFlex: 1, 331 | boxFlexGroup: 1, 332 | boxOrdinalGroup: 1, 333 | columnCount: 1, 334 | flex: 1, 335 | flexGrow: 1, 336 | flexPositive: 1, 337 | flexShrink: 1, 338 | flexNegative: 1, 339 | flexOrder: 1, 340 | gridRow: 1, 341 | gridColumn: 1, 342 | fontWeight: 1, 343 | lineClamp: 1, 344 | lineHeight: 1, 345 | opacity: 1, 346 | order: 1, 347 | orphans: 1, 348 | tabSize: 1, 349 | widows: 1, 350 | zIndex: 1, 351 | zoom: 1, 352 | 353 | // SVG-related properties 354 | fillOpacity: 1, 355 | floodOpacity: 1, 356 | stopOpacity: 1, 357 | strokeDasharray: 1, 358 | strokeDashoffset: 1, 359 | strokeMiterlimit: 1, 360 | strokeOpacity: 1, 361 | strokeWidth: 1 362 | }; 363 | 364 | function prefixKey(prefix, key) { 365 | return prefix + key.charAt(0).toUpperCase() + key.substring(1); 366 | } 367 | 368 | var prefixes = ['Webkit', 'ms', 'Moz', 'O']; 369 | 370 | Object.keys(isUnitlessNumber).forEach(function (prop) { 371 | prefixes.forEach(function (prefix) { 372 | isUnitlessNumber[prefixKey(prefix, prop)] = 1; 373 | }); 374 | }); 375 | 376 | var RE_NUMBER = /^-?\d+(\.\d+)?$/; 377 | function setStyleValue(elemStyle, styleName, styleValue) { 378 | 379 | if (!isUnitlessNumber[styleName] && RE_NUMBER.test(styleValue)) { 380 | elemStyle[styleName] = styleValue + 'px'; 381 | return; 382 | } 383 | 384 | if (styleName === 'float') { 385 | styleName = 'cssFloat'; 386 | } 387 | 388 | if (styleValue == null || typeof styleValue === 'boolean') { 389 | styleValue = ''; 390 | } 391 | 392 | elemStyle[styleName] = styleValue; 393 | } 394 | 395 | var SVGNamespaceURI = 'http://www.w3.org/2000/svg'; 396 | var COMPONENT_ID = 'liteid'; 397 | var VELEMENT = 1; 398 | var VSTATELESS = 2; 399 | var VCOMMENT = 3; 400 | var HTML_KEY = 'prop-innerHTML'; 401 | var HOOK_WILL_MOUNT = 'hook-willMount'; 402 | var HOOK_DID_MOUNT = 'hook-didMount'; 403 | var HOOK_WILL_UPDATE = 'hook-willUpdate'; 404 | var HOOK_DID_UPDATE = 'hook-didUpdate'; 405 | var HOOK_WILL_UNMOUNT = 'hook-willUnmount'; 406 | 407 | function initVnode(vnode, context, namespaceURI) { 408 | var vtype = vnode.vtype; 409 | 410 | var node = null; 411 | if (!vtype) { 412 | // init text 413 | node = document.createTextNode(vnode); 414 | } else if (vtype === VELEMENT) { 415 | // init element 416 | node = initVelem(vnode, context, namespaceURI); 417 | } else if (vtype === VSTATELESS) { 418 | // init stateless component 419 | node = initVstateless(vnode, context, namespaceURI); 420 | } else if (vtype === VCOMMENT) { 421 | // init comment 422 | node = document.createComment('react-empty: ' + vnode.uid); 423 | } 424 | return node; 425 | } 426 | 427 | function updateVnode(vnode, newVnode, node, context) { 428 | var vtype = vnode.vtype; 429 | 430 | if (vtype === VSTATELESS) { 431 | return updateVstateless(vnode, newVnode, node, context); 432 | } 433 | 434 | // ignore VCOMMENT and other vtypes 435 | if (vtype !== VELEMENT) { 436 | return node; 437 | } 438 | 439 | if (vnode.props[HTML_KEY] != null) { 440 | updateVelem(vnode, newVnode, node, context); 441 | initVchildren(newVnode, node, context); 442 | } else { 443 | updateVChildren(vnode, newVnode, node, context); 444 | updateVelem(vnode, newVnode, node, context); 445 | } 446 | return node; 447 | } 448 | 449 | function updateVChildren(vnode, newVnode, node, context) { 450 | var patches = { 451 | removes: [], 452 | updates: [], 453 | creates: [] 454 | }; 455 | // console.time('time') 456 | diffVchildren(patches, vnode, newVnode, node, context); 457 | 458 | flatEach(patches.removes, applyDestroy); 459 | 460 | flatEach(patches.updates, applyUpdate); 461 | 462 | flatEach(patches.creates, applyCreate); 463 | // console.timeEnd('time') 464 | } 465 | 466 | function applyUpdate(data) { 467 | if (!data) { 468 | return; 469 | } 470 | var node = data.node; 471 | 472 | // update 473 | if (data.shouldUpdate) { 474 | var vnode = data.vnode; 475 | var newVnode = data.newVnode; 476 | var context = data.context; 477 | 478 | if (!vnode.vtype) { 479 | node.nodeValue = newVnode; 480 | } else if (vnode.vtype === VELEMENT) { 481 | updateVelem(vnode, newVnode, node, context); 482 | } else if (vnode.vtype === VSTATELESS) { 483 | node = updateVstateless(vnode, newVnode, node, context); 484 | } 485 | } 486 | 487 | // re-order 488 | if (data.index !== data.fromIndex) { 489 | var existNode = node.parentNode.childNodes[index]; 490 | if (existNode !== node) { 491 | node.parentNode.insertBefore(node, existNode); 492 | } 493 | } 494 | } 495 | 496 | function applyDestroy(data) { 497 | destroyVnode(data.vnode, data.node); 498 | data.node.parentNode.removeChild(data.node); 499 | } 500 | 501 | function applyCreate(data) { 502 | var parentNode = data.parentNode; 503 | var existNode = parentNode.childNodes[data.index]; 504 | var node = initVnode(data.vnode, data.context, parentNode.namespaceURI); 505 | parentNode.insertBefore(node, existNode); 506 | } 507 | 508 | /** 509 | * Only vnode which has props.children need to call destroy function 510 | * to check whether subTree has component that need to call lify-cycle method and release cache. 511 | */ 512 | 513 | function destroyVnode(vnode, node) { 514 | var vtype = vnode.vtype; 515 | 516 | if (vtype === VELEMENT) { 517 | // destroy element 518 | destroyVelem(vnode, node); 519 | } else if (vtype === VSTATELESS) { 520 | // destroy stateless component 521 | destroyVstateless(vnode, node); 522 | } 523 | } 524 | 525 | function initVelem(velem, context, namespaceURI) { 526 | var type = velem.type; 527 | var props = velem.props; 528 | 529 | var node = null; 530 | 531 | if (type === 'svg' || namespaceURI === SVGNamespaceURI) { 532 | node = document.createElementNS(SVGNamespaceURI, type); 533 | namespaceURI = SVGNamespaceURI; 534 | } else { 535 | node = document.createElement(type); 536 | } 537 | 538 | initVchildren(node, props.children, context); 539 | attachProps(node, props); 540 | 541 | if (props[HOOK_WILL_MOUNT]) { 542 | props[HOOK_WILL_MOUNT].call(null, node, props); 543 | } 544 | 545 | if (props[HOOK_DID_MOUNT]) { 546 | addItem(pendingHooks, { 547 | type: HOOK_DID_MOUNT, 548 | node: node, 549 | props: props 550 | }); 551 | } 552 | 553 | return node; 554 | } 555 | 556 | function initVchildren(node, vchildren, context) { 557 | var namespaceURI = node.namespaceURI; 558 | 559 | for (var i = 0, len = vchildren.length; i < len; i++) { 560 | node.appendChild(initVnode(vchildren[i], context, namespaceURI)); 561 | } 562 | } 563 | 564 | function diffVchildren(patches, vnode, newVnode, node, context) { 565 | var childNodes = node.childNodes; 566 | 567 | var vchildren = vnode.props.children; 568 | var newVchildren = newVnode.props.children; 569 | var vchildrenLen = vchildren.length; 570 | var newVchildrenLen = newVchildren.length; 571 | 572 | if (vchildrenLen === 0) { 573 | if (newVchildrenLen === 0) { 574 | return; 575 | } 576 | for (var i = 0; i < newVchildrenLen; i++) { 577 | addItem(patches.creates, { 578 | vnode: newVchildren[i], 579 | parentNode: node, 580 | context: context, 581 | index: i 582 | }); 583 | } 584 | return; 585 | } else if (newVchildrenLen === 0) { 586 | for (var i = 0; i < vchildrenLen; i++) { 587 | addItem(patches.removes, { 588 | vnode: vchildren[i], 589 | node: childNodes[i] 590 | }); 591 | } 592 | return; 593 | } 594 | 595 | var matches = {}; 596 | var updates = Array(newVchildrenLen); 597 | var removes = null; 598 | var creates = null; 599 | 600 | // isEqual 601 | for (var i = 0; i < vchildrenLen; i++) { 602 | var _vnode = vchildren[i]; 603 | for (var j = 0; j < newVchildrenLen; j++) { 604 | if (updates[j]) { 605 | continue; 606 | } 607 | var _newVnode = newVchildren[j]; 608 | if (_vnode === _newVnode) { 609 | var shouldUpdate = false; 610 | if (context) { 611 | if (_vnode.vtype === VSTATELESS) { 612 | /** 613 | * stateless component: (props, context) =>
614 | * if context argument is specified and context is exist, should re-render 615 | */ 616 | if (_vnode.type.length > 1) { 617 | shouldUpdate = true; 618 | } 619 | } 620 | } 621 | updates[j] = { 622 | shouldUpdate: shouldUpdate, 623 | vnode: _vnode, 624 | newVnode: _newVnode, 625 | node: childNodes[i], 626 | context: context, 627 | index: j, 628 | fromIndex: i 629 | }; 630 | matches[i] = true; 631 | break; 632 | } 633 | } 634 | } 635 | 636 | // isSimilar 637 | for (var i = 0; i < vchildrenLen; i++) { 638 | if (matches[i]) { 639 | continue; 640 | } 641 | var _vnode2 = vchildren[i]; 642 | var shouldRemove = true; 643 | for (var j = 0; j < newVchildrenLen; j++) { 644 | if (updates[j]) { 645 | continue; 646 | } 647 | var _newVnode2 = newVchildren[j]; 648 | if (_newVnode2.type === _vnode2.type && _newVnode2.key === _vnode2.key) { 649 | updates[j] = { 650 | shouldUpdate: true, 651 | vnode: _vnode2, 652 | newVnode: _newVnode2, 653 | node: childNodes[i], 654 | context: context, 655 | index: j, 656 | fromIndex: i 657 | }; 658 | shouldRemove = false; 659 | break; 660 | } 661 | } 662 | if (shouldRemove) { 663 | if (!removes) { 664 | removes = []; 665 | } 666 | addItem(removes, { 667 | vnode: _vnode2, 668 | node: childNodes[i] 669 | }); 670 | } 671 | } 672 | 673 | for (var i = 0; i < newVchildrenLen; i++) { 674 | var item = updates[i]; 675 | if (!item) { 676 | if (!creates) { 677 | creates = []; 678 | } 679 | addItem(creates, { 680 | vnode: newVchildren[i], 681 | parentNode: node, 682 | context: context, 683 | index: i 684 | }); 685 | } else if (item.vnode.vtype === VELEMENT) { 686 | diffVchildren(patches, item.vnode, item.newVnode, item.node, item.context); 687 | } 688 | } 689 | 690 | if (removes) { 691 | addItem(patches.removes, removes); 692 | } 693 | if (creates) { 694 | addItem(patches.creates, creates); 695 | } 696 | addItem(patches.updates, updates); 697 | } 698 | 699 | function updateVelem(velem, newVelem, node) { 700 | var newProps = newVelem.props; 701 | if (newProps[HOOK_WILL_UPDATE]) { 702 | newProps[HOOK_WILL_UPDATE].call(null, node, newProps); 703 | } 704 | patchProps(node, velem.props, newProps); 705 | if (newProps[HOOK_DID_UPDATE]) { 706 | newProps[HOOK_DID_UPDATE].call(null, node, newProps); 707 | } 708 | return node; 709 | } 710 | 711 | function destroyVelem(velem, node) { 712 | var props = velem.props; 713 | 714 | var vchildren = props.children; 715 | var childNodes = node.childNodes; 716 | 717 | for (var i = 0, len = vchildren.length; i < len; i++) { 718 | destroyVnode(vchildren[i], childNodes[i]); 719 | } 720 | 721 | if (isFn(props[HOOK_WILL_UNMOUNT])) { 722 | props[HOOK_WILL_UNMOUNT].call(null, node, props); 723 | } 724 | 725 | detachEvents(node, props); 726 | } 727 | 728 | function initVstateless(vstateless, context, namespaceURI) { 729 | var vnode = renderVstateless(vstateless, context); 730 | var node = initVnode(vnode, context, namespaceURI); 731 | node.cache = node.cache || {}; 732 | node.cache[vstateless.uid] = vnode; 733 | return node; 734 | } 735 | 736 | function updateVstateless(vstateless, newVstateless, node, context) { 737 | var uid = vstateless.uid; 738 | var vnode = node.cache[uid]; 739 | delete node.cache[uid]; 740 | var newVnode = renderVstateless(newVstateless, context); 741 | var newNode = compareTwoVnodes(vnode, newVnode, node, context); 742 | newNode.cache = newNode.cache || {}; 743 | newNode.cache[newVstateless.uid] = newVnode; 744 | if (newNode !== node) { 745 | extend(newNode.cache, node.cache); 746 | } 747 | return newNode; 748 | } 749 | 750 | function destroyVstateless(vstateless, node) { 751 | var uid = vstateless.uid; 752 | var vnode = node.cache[uid]; 753 | delete node.cache[uid]; 754 | destroyVnode(vnode, node); 755 | } 756 | 757 | function renderVstateless(vstateless, context) { 758 | var factory = vstateless.type; 759 | var props = vstateless.props; 760 | 761 | var vnode = factory(props, context); 762 | if (vnode && vnode.render) { 763 | vnode = vnode.render(); 764 | } 765 | if (vnode === null || vnode === false) { 766 | vnode = { 767 | vtype: VCOMMENT, 768 | uid: getUid() 769 | }; 770 | } else if (!vnode || !vnode.vtype) { 771 | throw new Error('@' + factory.name + '#render:You may have returned undefined, an array or some other invalid object'); 772 | } 773 | return vnode; 774 | } 775 | 776 | var pendingHooks = []; 777 | var clearPendingMount = function clearPendingMount() { 778 | var len = pendingHooks.length; 779 | if (!len) { 780 | return; 781 | } 782 | var list = pendingHooks; 783 | var i = -1; 784 | while (len--) { 785 | var item = list[++i]; 786 | item.props[item.type].call(null, item.node, item.props); 787 | } 788 | pendingHooks.length = 0; 789 | }; 790 | 791 | function compareTwoVnodes(vnode, newVnode, node, context) { 792 | var newNode = node; 793 | if (newVnode == null) { 794 | // remove 795 | destroyVnode(vnode, node); 796 | node.parentNode.removeChild(node); 797 | } else if (vnode.type !== newVnode.type || vnode.key !== newVnode.key) { 798 | // replace 799 | destroyVnode(vnode, node); 800 | newNode = initVnode(newVnode, context, node.namespaceURI); 801 | node.parentNode.replaceChild(newNode, node); 802 | } else if (vnode !== newVnode || context) { 803 | // same type and same key -> update 804 | newNode = updateVnode(vnode, newVnode, node, context); 805 | } 806 | return newNode; 807 | } 808 | 809 | var pendingRendering = {}; 810 | var vnodeStore = {}; 811 | 812 | function render(vnode, container, context, callback) { 813 | if (!vnode.vtype) { 814 | throw new Error('cannot render ' + vnode + ' to container'); 815 | } 816 | var id = container[COMPONENT_ID] || (container[COMPONENT_ID] = getUid()); 817 | var argsCache = pendingRendering[id]; 818 | 819 | if (isFn(context)) { 820 | callback = context; 821 | context = undefined; 822 | } 823 | 824 | // component lify cycle method maybe call root rendering 825 | // should bundle them and render by only one time 826 | if (argsCache) { 827 | if (argsCache === true) { 828 | pendingRendering[id] = { 829 | vnode: vnode, 830 | context: context, 831 | callback: callback 832 | }; 833 | } else { 834 | argsCache.vnode = vnode; 835 | argsCache.context = context; 836 | if (callback) { 837 | argsCache.callback = argsCache.callback ? pipe(argsCache.callback, callback) : callback; 838 | } 839 | } 840 | return; 841 | } 842 | 843 | pendingRendering[id] = true; 844 | if (vnodeStore.hasOwnProperty(id)) { 845 | compareTwoVnodes(vnodeStore[id], vnode, container.firstChild, context); 846 | } else { 847 | var rootNode = initVnode(vnode, context, container.namespaceURI); 848 | var childNode = null; 849 | while (childNode = container.lastChild) { 850 | container.removeChild(childNode); 851 | } 852 | container.appendChild(rootNode); 853 | } 854 | vnodeStore[id] = vnode; 855 | clearPendingMount(); 856 | 857 | argsCache = pendingRendering[id]; 858 | pendingRendering[id] = null; 859 | 860 | if (typeof argsCache === 'object') { 861 | render(argsCache.vnode, container, argsCache.context, argsCache.callback); 862 | } 863 | 864 | if (callback) { 865 | callback(); 866 | } 867 | } 868 | 869 | function destroy(container) { 870 | if (!container.nodeName) { 871 | throw new Error('expect node'); 872 | } 873 | var id = container[COMPONENT_ID]; 874 | var vnode = null; 875 | if (vnode = vnodeStore[id]) { 876 | destroyVnode(vnode, container.firstChild); 877 | container.removeChild(container.firstChild); 878 | delete vnodeStore[id]; 879 | delete pendingRendering[id]; 880 | return true; 881 | } 882 | return false; 883 | } 884 | 885 | function createElement(type, props) /* ...children */{ 886 | var finalProps = {}; 887 | var key = null; 888 | if (props != null) { 889 | for (var propKey in props) { 890 | if (propKey === 'key') { 891 | if (props.key !== undefined) { 892 | key = '' + props.key; 893 | } 894 | } else { 895 | finalProps[propKey] = props[propKey]; 896 | } 897 | } 898 | } 899 | 900 | var defaultProps = type.defaultProps; 901 | if (defaultProps) { 902 | for (var propKey in defaultProps) { 903 | if (finalProps[propKey] === undefined) { 904 | finalProps[propKey] = defaultProps[propKey]; 905 | } 906 | } 907 | } 908 | 909 | var argsLen = arguments.length; 910 | var finalChildren = []; 911 | 912 | if (argsLen > 2) { 913 | for (var i = 2; i < argsLen; i++) { 914 | var child = arguments[i]; 915 | if (isArr(child)) { 916 | flatEach(child, collectChild, finalChildren); 917 | } else { 918 | collectChild(child, finalChildren); 919 | } 920 | } 921 | } 922 | 923 | finalProps.children = finalChildren; 924 | 925 | var vtype = null; 926 | if (typeof type === 'string') { 927 | vtype = VELEMENT; 928 | } else if (typeof type === 'function') { 929 | vtype = VSTATELESS; 930 | } else { 931 | throw new Error('unexpect type [ ' + type + ' ]'); 932 | } 933 | 934 | var vnode = { 935 | vtype: vtype, 936 | type: type, 937 | props: finalProps, 938 | key: key 939 | }; 940 | if (vtype === VSTATELESS) { 941 | vnode.uid = getUid(); 942 | } 943 | 944 | return vnode; 945 | } 946 | 947 | function isValidElement(obj) { 948 | return obj != null && !!obj.vtype; 949 | } 950 | 951 | function createFactory(type) { 952 | var factory = function factory() { 953 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 954 | args[_key] = arguments[_key]; 955 | } 956 | 957 | return createElement.apply(undefined, [type].concat(args)); 958 | }; 959 | factory.type = type; 960 | return factory; 961 | } 962 | 963 | function collectChild(child, children) { 964 | if (child != null && typeof child !== 'boolean') { 965 | children[children.length] = child.vtype ? child : '' + child; 966 | } 967 | } 968 | 969 | addDirective('attr', DOMAttrDirective); 970 | addDirective('prop', DOMPropDirective); 971 | addDirective('on', eventDirective); 972 | addDirective('css', styleDirective); 973 | 974 | var Vengine = { 975 | createElement: createElement, 976 | createFactory: createFactory, 977 | isValidElement: isValidElement, 978 | addDirective: addDirective, 979 | removeDirective: removeDirective, 980 | render: render, 981 | destroy: destroy 982 | }; 983 | 984 | return Vengine; 985 | 986 | })); -------------------------------------------------------------------------------- /dist/vdom-engine.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * vdom-engine.js v0.1.6 3 | * (c) 2016 Jade Gu 4 | * Released under the MIT License. 5 | */ 6 | !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):e.Vengine=n()}(this,function(){"use strict";function e(e,n){Z[e]=n}function n(e){delete Z[e]}function t(e){var n=e.match(_);return n?(ee=n[2],Z[n[1]]):void 0}function o(e,n,o){var r=t(n);r&&r.attach(e,ee,o)}function r(e,n){var o=t(n);o&&o.detach(e,ee)}function a(e,n){for(var t in n)"children"!==t&&null!=n[t]&&o(e,t,n[t])}function i(e,n,t){for(var a in n)if("children"!==a){var i=t[a];i!==n[a]&&(null==i?r(e,a):o(e,a,i))}for(var a in t)"children"!==a&&(a in n||o(e,a,t[a]))}function c(e,n,t){e[n]=t}function l(e,n){e[n]=""}function d(e,n,t){e.setAttribute(n,t+"")}function u(e,n){e.removeAttribute(n)}function f(e){return"function"==typeof e}function v(){}function p(e){return e}function s(e,n){return function(){return e.apply(this,arguments),n.apply(this,arguments)}}function h(e,n,t){for(var o=e.length,r=-1;o--;){var a=e[++r];oe(a)?h(a,n,t):n(a,t)}}function y(e,n){e[e.length]=n}function m(e,n){if(!n)return e;for(var t=Object.keys(n),o=t.length;o--;)e[t[o]]=n[t[o]];return e}function x(){return++re}function g(e,n){e.eventStore=null;for(var t in n)0===t.indexOf("on-")&&(t=b(t),ae[t]&&(e[t]=null))}function b(e){return e.replace(/^on-/,"on").toLowerCase()}function k(e,n,t){if(n="on"+n,1===ae[n])return void(e[n]=t);var o=e.eventStore||(e.eventStore={});o[n]=t,ue[n]||(document.addEventListener(n.substr(2),N,!1),ue[n]=!0),ce&&n===de&&e.addEventListener("click",le,!1);var r=e.nodeName;"onchange"!==n||"INPUT"!==r&&"TEXTAREA"!==r||k(e,"oninput",t)}function w(e,n){if(n="on"+n,1===ae[n])return void(e[n]=null);var t=e.eventStore||(e.eventStore={});delete t[n],ce&&n===de&&e.removeEventListener("click",le,!1);var o=e.nodeName;"onchange"!==n||"INPUT"!==o&&"TEXTAREA"!==o||delete t.oninput}function N(e){for(var n=e.target,t=e.type,o="on"+t,r=null;n;){var a=n,i=a.eventStore,c=i&&i[o];if(c){if(r||(r=C(e)),r.currentTarget=n,c.call(n,r),r.$cancalBubble)break;n=n.parentNode}else n=n.parentNode}}function C(e){var n={},t=function(){return n.$cancalBubble=!0};n.nativeEvent=e,n.persist=v;for(var o in e)"function"!=typeof e[o]?n[o]=e[o]:"stopPropagation"===o||"stopImmediatePropagation"===o?n[o]=t:n[o]=e[o].bind(e);return n}function E(e,n,t){U(e.style,n,t)}function I(e,n){e.style[n]=""}function O(e,n){return e+n.charAt(0).toUpperCase()+n.substring(1)}function U(e,n,t){return!ve[n]&&se.test(t)?void(e[n]=t+"px"):("float"===n&&(n="cssFloat"),null!=t&&"boolean"!=typeof t||(t=""),void(e[n]=t))}function A(e,n,t){var o=e.vtype,r=null;return o?o===me?r=z(e,n,t):o===xe?r=F(e,n,t):o===ge&&(r=document.createComment("react-empty: "+e.uid)):r=document.createTextNode(e),r}function S(e,n,t,o){var r=e.vtype;return r===xe?W(e,n,t,o):r!==me?t:(null!=e.props[be]?(B(e,n,t,o),L(n,t,o)):(T(e,n,t,o),B(e,n,t,o)),t)}function T(e,n,t,o){var r={removes:[],updates:[],creates:[]};M(r,e,n,t,o),h(r.removes,P),h(r.updates,j),h(r.creates,R)}function j(e){if(e){var n=e.node;if(e.shouldUpdate){var t=e.vnode,o=e.newVnode,r=e.context;t.vtype?t.vtype===me?B(t,o,n,r):t.vtype===xe&&(n=W(t,o,n,r)):n.nodeValue=o}if(e.index!==e.fromIndex){var a=n.parentNode.childNodes[index];a!==n&&n.parentNode.insertBefore(n,a)}}}function P(e){V(e.vnode,e.node),e.node.parentNode.removeChild(e.node)}function R(e){var n=e.parentNode,t=n.childNodes[e.index],o=A(e.vnode,e.context,n.namespaceURI);n.insertBefore(o,t)}function V(e,n){var t=e.vtype;t===me?D(e,n):t===xe&&$(e,n)}function z(e,n,t){var o=e.type,r=e.props,i=null;return"svg"===o||t===he?(i=document.createElementNS(he,o),t=he):i=document.createElement(o),L(i,r.children,n),a(i,r),r[ke]&&r[ke].call(null,i,r),r[we]&&y(Ie,{type:we,node:i,props:r}),i}function L(e,n,t){for(var o=e.namespaceURI,r=0,a=n.length;a>r;r++)e.appendChild(A(n[r],t,o))}function M(e,n,t,o,r){var a=o.childNodes,i=n.props.children,c=t.props.children,l=i.length,d=c.length;if(0!==l)if(0!==d){for(var u={},f=Array(d),v=null,p=null,s=0;l>s;s++)for(var h=i[s],m=0;d>m;m++)if(!f[m]){var x=c[m];if(h===x){var g=!1;r&&h.vtype===xe&&h.type.length>1&&(g=!0),f[m]={shouldUpdate:g,vnode:h,newVnode:x,node:a[s],context:r,index:m,fromIndex:s},u[s]=!0;break}}for(var s=0;l>s;s++)if(!u[s]){for(var b=i[s],k=!0,m=0;d>m;m++)if(!f[m]){var w=c[m];if(w.type===b.type&&w.key===b.key){f[m]={shouldUpdate:!0,vnode:b,newVnode:w,node:a[s],context:r,index:m,fromIndex:s},k=!1;break}}k&&(v||(v=[]),y(v,{vnode:b,node:a[s]}))}for(var s=0;d>s;s++){var N=f[s];N?N.vnode.vtype===me&&M(e,N.vnode,N.newVnode,N.node,N.context):(p||(p=[]),y(p,{vnode:c[s],parentNode:o,context:r,index:s}))}v&&y(e.removes,v),p&&y(e.creates,p),y(e.updates,f)}else for(var s=0;l>s;s++)y(e.removes,{vnode:i[s],node:a[s]});else{if(0===d)return;for(var s=0;d>s;s++)y(e.creates,{vnode:c[s],parentNode:o,context:r,index:s})}}function B(e,n,t){var o=n.props;return o[Ne]&&o[Ne].call(null,t,o),i(t,e.props,o),o[Ce]&&o[Ce].call(null,t,o),t}function D(e,n){for(var t=e.props,o=t.children,r=n.childNodes,a=0,i=o.length;i>a;a++)V(o[a],r[a]);f(t[Ee])&&t[Ee].call(null,n,t),g(n,t)}function F(e,n,t){var o=G(e,n),r=A(o,n,t);return r.cache=r.cache||{},r.cache[e.uid]=o,r}function W(e,n,t,o){var r=e.uid,a=t.cache[r];delete t.cache[r];var i=G(n,o),c=H(a,i,t,o);return c.cache=c.cache||{},c.cache[n.uid]=i,c!==t&&m(c.cache,t.cache),c}function $(e,n){var t=e.uid,o=n.cache[t];delete n.cache[t],V(o,n)}function G(e,n){var t=e.type,o=e.props,r=t(o,n);if(r&&r.render&&(r=r.render()),null===r||r===!1)r={vtype:ge,uid:x()};else if(!r||!r.vtype)throw new Error("@"+t.name+"#render:You may have returned undefined, an array or some other invalid object");return r}function H(e,n,t,o){var r=t;return null==n?(V(e,t),t.parentNode.removeChild(t)):e.type!==n.type||e.key!==n.key?(V(e,t),r=A(n,o,t.namespaceURI),t.parentNode.replaceChild(r,t)):(e!==n||o)&&(r=S(e,n,t,o)),r}function X(e,n,t,o){if(!e.vtype)throw new Error("cannot render "+e+" to container");var r=n[ye]||(n[ye]=x()),a=Ue[r];if(f(t)&&(o=t,t=void 0),a)return void(a===!0?Ue[r]={vnode:e,context:t,callback:o}:(a.vnode=e,a.context=t,o&&(a.callback=a.callback?s(a.callback,o):o)));if(Ue[r]=!0,Ae.hasOwnProperty(r))H(Ae[r],e,n.firstChild,t);else{for(var i=A(e,t,n.namespaceURI),c=null;c=n.lastChild;)n.removeChild(c);n.appendChild(i)}Ae[r]=e,Oe(),a=Ue[r],Ue[r]=null,"object"==typeof a&&X(a.vnode,n,a.context,a.callback),o&&o()}function Y(e){if(!e.nodeName)throw new Error("expect node");var n=e[ye],t=null;return(t=Ae[n])?(V(t,e.firstChild),e.removeChild(e.firstChild),delete Ae[n],delete Ue[n],!0):!1}function q(e,n){var t={},o=null;if(null!=n)for(var r in n)"key"===r?void 0!==n.key&&(o=""+n.key):t[r]=n[r];var a=e.defaultProps;if(a)for(var r in a)void 0===t[r]&&(t[r]=a[r]);var i=arguments.length,c=[];if(i>2)for(var l=2;i>l;l++){var d=arguments[l];oe(d)?h(d,Q,c):Q(d,c)}t.children=c;var u=null;if("string"==typeof e)u=me;else{if("function"!=typeof e)throw new Error("unexpect type [ "+e+" ]");u=xe}var f={vtype:u,type:e,props:t,key:o};return u===xe&&(f.uid=x()),f}function J(e){return null!=e&&!!e.vtype}function K(e){var n=function(){for(var n=arguments.length,t=Array(n),o=0;n>o;o++)t[o]=arguments[o];return q.apply(void 0,[e].concat(t))};return n.type=e,n}function Q(e,n){null!=e&&"boolean"!=typeof e&&(n[n.length]=e.vtype?e:""+e)}var Z={},_=/^([^-]+)-(.+)$/,ee=null,ne={attach:c,detach:l},te={attach:d,detach:u},oe=Array.isArray,re=0;Object.freeze||(Object.freeze=p);var ae={onmouseleave:1,onmouseenter:1,onload:1,onunload:1,onscroll:1,onfocus:1,onblur:1,onrowexit:1,onbeforeunload:1,onstop:1,ondragdrop:1,ondragenter:1,ondragexit:1,ondraggesture:1,ondragover:1,oncontextmenu:1},ie={attach:k,detach:w},ce="ontouchstart"in document,le=function(){},de="onclick",ue={},fe={attach:E,detach:I},ve={animationIterationCount:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridColumn:1,fontWeight:1,lineClamp:1,lineHeight:1,opacity:1,order:1,orphans:1,tabSize:1,widows:1,zIndex:1,zoom:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1},pe=["Webkit","ms","Moz","O"];Object.keys(ve).forEach(function(e){pe.forEach(function(n){ve[O(n,e)]=1})});var se=/^-?\d+(\.\d+)?$/,he="http://www.w3.org/2000/svg",ye="liteid",me=1,xe=2,ge=3,be="prop-innerHTML",ke="hook-willMount",we="hook-didMount",Ne="hook-willUpdate",Ce="hook-didUpdate",Ee="hook-willUnmount",Ie=[],Oe=function(){var e=Ie.length;if(e){for(var n=Ie,t=-1;e--;){var o=n[++t];o.props[o.type].call(null,o.node,o.props)}Ie.length=0}},Ue={},Ae={};e("attr",te),e("prop",ne),e("on",ie),e("css",fe);var Se={createElement:q,createFactory:K,isValidElement:J,addDirective:e,removeDirective:n,render:X,destroy:Y};return Se}); -------------------------------------------------------------------------------- /dist/vdom-engine.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifier129/vdom-engine/aded08691f5e14538d5b63f852e069cdaf68f16a/dist/vdom-engine.min.js.gz -------------------------------------------------------------------------------- /examples/counter-vanilla/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Vanilla conter 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/counter-vanilla/index.js: -------------------------------------------------------------------------------- 1 | let Counter = ({ count, onIncr, onDecr, onOdd, onAsync }) => { 2 | return ( 3 |

4 | Clicked: { count } times 5 | {' '} 6 | 7 | {' '} 8 | 9 | {' '} 10 | 11 | {' '} 12 | 13 | {' '} 14 | 15 | {' '} 16 | 17 |

18 | ) 19 | } 20 | 21 | // Singleton redux-like store 22 | let store = { 23 | state: { 24 | count: 0 25 | }, 26 | listeners: [], 27 | getState() { 28 | return this.state 29 | }, 30 | subscribe(fn) { 31 | this.listeners.push(fn) 32 | }, 33 | notify() { 34 | this.listeners.forEach(fn => fn(this.state)) 35 | }, 36 | replaceState(prevState, nextState) { 37 | this.state = nextState 38 | if (nextState !== prevState) { 39 | this.notify() 40 | } 41 | }, 42 | getNextState(reducer, ...args) { 43 | let { state, replaceState } = this 44 | let nextState = reducer(state, ...args) 45 | if (typeof nextState.then === 'function') { 46 | nextState.then(replaceState.bind(this, state)) 47 | } else { 48 | this.replaceState(state, nextState) 49 | } 50 | }, 51 | init(callback) { 52 | let { reducers, getNextState } = this 53 | this.actions = Object.keys(reducers).reduce((actions, key) => { 54 | let reducer = reducers[key].bind(reducers) 55 | actions[key] = getNextState.bind(this, reducer) 56 | return actions 57 | }, {}) 58 | callback && callback() 59 | }, 60 | reducers: { 61 | onIncr(state, ...args) { 62 | let { count } = state 63 | return Object.assign({}, state, { 64 | count: ++count 65 | }) 66 | }, 67 | onDecr(state, ...args) { 68 | let { count } = state 69 | return Object.assign({}, state, { 70 | count: --count 71 | }) 72 | }, 73 | onOdd(state, ...args) { 74 | let { count } = state 75 | if (count % 2 !== 0) { 76 | return this.onIncr(state, ...args) 77 | } 78 | return state 79 | }, 80 | onAsync(state, ...args) { 81 | let increment = this.onIncr.bind(this, state, ...args) 82 | return new Promise((resolve, reject) => { 83 | setTimeout(resolve.bind(null, increment()), 1000) 84 | }) 85 | } 86 | } 87 | } 88 | 89 | let renderView = () => { 90 | React.render( 91 | , 92 | document.getElementById('container') 93 | ) 94 | } 95 | 96 | store.subscribe(renderView) 97 | store.init(renderView) -------------------------------------------------------------------------------- /examples/js-repaint-perf/ENV.js: -------------------------------------------------------------------------------- 1 | var ENV = ENV || (function() { 2 | 3 | var _base; 4 | 5 | (_base = String.prototype).lpad || (_base.lpad = function(padding, toLength) { 6 | return padding.repeat((toLength - this.length) / padding.length).concat(this); 7 | }); 8 | 9 | function formatElapsed(value) { 10 | str = parseFloat(value).toFixed(2); 11 | if (value > 60) { 12 | minutes = Math.floor(value / 60); 13 | comps = (value % 60).toFixed(2).split('.'); 14 | seconds = comps[0].lpad('0', 2); 15 | ms = comps[1]; 16 | str = minutes + ":" + seconds + "." + ms; 17 | } 18 | return str; 19 | } 20 | 21 | function getElapsedClassName(elapsed) { 22 | var className = 'Query elapsed'; 23 | if (elapsed >= 10.0) { 24 | className += ' warn_long'; 25 | } 26 | else if (elapsed >= 1.0) { 27 | className += ' warn'; 28 | } 29 | else { 30 | className += ' short'; 31 | } 32 | return className; 33 | } 34 | 35 | var lastGeneratedDatabases = []; 36 | 37 | function getData() { 38 | // generate some dummy data 39 | data = { 40 | start_at: new Date().getTime() / 1000, 41 | databases: {} 42 | }; 43 | 44 | for (var i = 1; i <= ENV.rows; i++) { 45 | data.databases["cluster" + i] = { 46 | queries: [] 47 | }; 48 | 49 | data.databases["cluster" + i + "slave"] = { 50 | queries: [] 51 | }; 52 | } 53 | 54 | Object.keys(data.databases).forEach(function(dbname) { 55 | 56 | if (lastGeneratedDatabases.length == 0 || Math.random() < ENV.mutations()) { 57 | var info = data.databases[dbname]; 58 | var r = Math.floor((Math.random() * 10) + 1); 59 | for (var i = 0; i < r; i++) { 60 | var elapsed = Math.random() * 15; 61 | var q = { 62 | canvas_action: null, 63 | canvas_context_id: null, 64 | canvas_controller: null, 65 | canvas_hostname: null, 66 | canvas_job_tag: null, 67 | canvas_pid: null, 68 | elapsed: elapsed, 69 | formatElapsed: formatElapsed(elapsed), 70 | elapsedClassName: getElapsedClassName(elapsed), 71 | query: "SELECT blah FROM something", 72 | waiting: Math.random() < 0.5 73 | }; 74 | 75 | if (Math.random() < 0.2) { 76 | q.query = " in transaction"; 77 | } 78 | 79 | if (Math.random() < 0.1) { 80 | q.query = "vacuum"; 81 | } 82 | 83 | info.queries.push(q); 84 | } 85 | 86 | info.queries = info.queries.sort(function (a, b) { 87 | return b.elapsed - a.elapsed; 88 | }); 89 | } else { 90 | data.databases[dbname] = lastGeneratedDatabases[dbname]; 91 | } 92 | }); 93 | 94 | lastGeneratedDatabases = data.databases; 95 | 96 | return data; 97 | } 98 | 99 | var lastDatabases = { 100 | toArray: function() { 101 | return Object.keys(this).filter(function(k) { return k !== 'toArray'; }).map(function(k) { return this[k]; }.bind(this)) 102 | } 103 | }; 104 | 105 | function generateData() { 106 | var databases = []; 107 | var newData = getData(); 108 | Object.keys(newData.databases).forEach(function(dbname) { 109 | var sampleInfo = newData.databases[dbname]; 110 | var database = { 111 | dbname: dbname, 112 | samples: [] 113 | }; 114 | 115 | function countClassName(queries) { 116 | var countClassName = "label"; 117 | if (queries.length >= 20) { 118 | countClassName += " label-important"; 119 | } 120 | else if (queries.length >= 10) { 121 | countClassName += " label-warning"; 122 | } 123 | else { 124 | countClassName += " label-success"; 125 | } 126 | return countClassName; 127 | } 128 | 129 | function topFiveQueries(queries) { 130 | var tfq = queries.slice(0, 5); 131 | while (tfq.length < 5) { 132 | tfq.push({ query: "", formatElapsed: '', elapsedClassName: '' }); 133 | } 134 | return tfq; 135 | } 136 | 137 | var samples = database.samples; 138 | samples.push({ 139 | time: newData.start_at, 140 | queries: sampleInfo.queries, 141 | topFiveQueries: topFiveQueries(sampleInfo.queries), 142 | countClassName: countClassName(sampleInfo.queries) 143 | }); 144 | if (samples.length > 5) { 145 | samples.splice(0, samples.length - 5); 146 | } 147 | var samples = database.samples; 148 | database.lastSample = database.samples[database.samples.length - 1]; 149 | databases.push(database); 150 | }); 151 | return { 152 | toArray: function() { 153 | return databases; 154 | } 155 | }; 156 | } 157 | 158 | var mutationsValue = 0.5; 159 | 160 | function mutations(value) { 161 | if (value) { 162 | mutationsValue = value; 163 | return mutationsValue; 164 | } else { 165 | return mutationsValue; 166 | } 167 | } 168 | 169 | var body = document.querySelector('body'); 170 | var theFirstChild = body.firstChild; 171 | 172 | var sliderContainer = document.createElement( 'div' ); 173 | sliderContainer.style.cssText = "display: flex"; 174 | var slider = document.createElement('input'); 175 | var text = document.createElement('label'); 176 | text.innerHTML = 'mutations : ' + (mutationsValue * 100).toFixed(0) + '%'; 177 | text.id = "ratioval"; 178 | slider.setAttribute("type", "range"); 179 | slider.style.cssText = 'margin-bottom: 10px; margin-top: 5px'; 180 | slider.addEventListener('change', function(e) { 181 | ENV.mutations(e.target.value / 100); 182 | document.querySelector('#ratioval').innerHTML = 'mutations : ' + (ENV.mutations() * 100).toFixed(0) + '%'; 183 | }); 184 | sliderContainer.appendChild( text ); 185 | sliderContainer.appendChild( slider ); 186 | body.insertBefore( sliderContainer, theFirstChild ); 187 | 188 | return { 189 | generateData: generateData, 190 | rows: 50, 191 | timeout: 0, 192 | mutations: mutations 193 | }; 194 | })(); 195 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/ga.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 3 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 4 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 5 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 6 | 7 | ga('create', 'UA-63822970-1', 'auto'); 8 | ga('send', 'pageview'); 9 | })(); 10 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | js-repain-perf 5 | 6 | 7 |

js-repain-perf

8 | 13 | 14 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/react/app-component.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var Query = React.createClass({ 4 | render: function() { 5 | return ( 6 | 7 | {this.props.formatElapsed} 8 |
9 |
{this.props.query}
10 |
11 |
12 | 13 | ); 14 | } 15 | }) 16 | 17 | var sample = function (database) { 18 | var _queries = []; 19 | database.lastSample.topFiveQueries.forEach(function(query, index) { 20 | _queries.push( 21 | 26 | ); 27 | }); 28 | return [ 29 | 30 | 31 | {database.lastSample.queries.length} 32 | 33 | , 34 | _queries 35 | ]; 36 | }; 37 | 38 | var Database = React.createClass({ 39 | render: function() { 40 | var lastSample = this.props.lastSample; 41 | return ( 42 | 43 | 44 | {this.props.dbname} 45 | 46 | {sample(this.props)} 47 | 48 | ); 49 | } 50 | }); 51 | 52 | var DBMon = React.createClass({ 53 | getInitialState: function() { 54 | return { 55 | databases: [] 56 | }; 57 | }, 58 | 59 | loadSamples: function () { 60 | this.setState({ 61 | databases: ENV.generateData().toArray() 62 | }); 63 | Monitoring.renderRate.ping(); 64 | setTimeout(this.loadSamples, ENV.timeout); 65 | }, 66 | 67 | componentDidMount: function() { 68 | this.loadSamples(); 69 | }, 70 | 71 | render: function() { 72 | var databases = []; 73 | Object.keys(this.state.databases).forEach(function(dbname) { 74 | databases.push( 75 | 78 | ); 79 | }.bind(this)); 80 | 81 | var databases = this.state.databases.map(function(database) { 82 | return 86 | }); 87 | 88 | return ( 89 |
90 | 91 | 92 | {databases} 93 | 94 |
95 |
96 | ); 97 | } 98 | }); 99 | 100 | React.render(, document.getElementById('dbmon')); 101 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/react/app.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | var DBMon = React.createClass({ 4 | getInitialState: function() { 5 | return { 6 | databases: [] 7 | }; 8 | }, 9 | 10 | loadSamples: function () { 11 | this.setState({ databases: ENV.generateData().toArray() }); 12 | Monitoring.renderRate.ping(); 13 | setTimeout(this.loadSamples.bind(this), ENV.timeout); 14 | }, 15 | 16 | componentDidMount: function() { 17 | this.loadSamples(); 18 | }, 19 | 20 | render: function() { 21 | return ( 22 |
23 | 24 | 25 | { 26 | this.state.databases.map(function(database) { 27 | return ( 28 | 29 | 32 | 37 | { 38 | database.lastSample.topFiveQueries.map(function(query, index) { 39 | return ( 40 | 47 | ); 48 | }) 49 | } 50 | 51 | ); 52 | }) 53 | } 54 | 55 |
30 | {database.dbname} 31 | 33 | 34 | {database.lastSample.queries.length} 35 | 36 | 41 | {query.formatElapsed} 42 |
43 |
{query.query}
44 |
45 |
46 |
56 |
57 | ); 58 | } 59 | }); 60 | 61 | console.time('mount') 62 | ReactDOM.render(, document.getElementById('dbmon')); 63 | console.timeEnd('mount') 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | dbmon (react) 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/react/lite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | dbmon (react) 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/react/vdom-engine-app.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 3 | function DBMon(props) { 4 | return ( 5 |
6 | 7 | 8 | { 9 | props.databases.map(function(database) { 10 | return ( 11 | 14 | 17 | 22 | { 23 | database.lastSample.topFiveQueries.map(function(query, index) { 24 | return ( 25 | 32 | ); 33 | }) 34 | } 35 | 36 | ); 37 | }) 38 | } 39 | 40 |
15 | {database.dbname} 16 | 18 | 19 | {database.lastSample.queries.length} 20 | 21 | 26 | {query.formatElapsed} 27 |
28 |
{query.query}
29 |
30 |
31 |
41 |
42 | ); 43 | } 44 | 45 | var renderDBMon = function() { 46 | React.render(, document.getElementById('dbmon')); 47 | Monitoring.renderRate.ping(); 48 | setTimeout(renderDBMon, ENV.timeout); 49 | 50 | } 51 | console.time('mount') 52 | renderDBMon() 53 | console.timeEnd('mount') 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/react/vdom-engine.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | dbmon (react) 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/styles.css: -------------------------------------------------------------------------------- 1 | .Query { 2 | position: relative; 3 | } 4 | 5 | .Query:hover .popover { 6 | left: -100%; 7 | width: 100%; 8 | display: block; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/vendor/memory-stats.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | * @author jetienne / http://jetienne.com/ 4 | * @author paulirish / http://paulirish.com/ 5 | */ 6 | var MemoryStats = function (){ 7 | 8 | var msMin = 100; 9 | var msMax = 0; 10 | 11 | var container = document.createElement( 'div' ); 12 | container.id = 'stats'; 13 | container.style.cssText = 'width:80px;opacity:0.9;cursor:pointer'; 14 | 15 | var msDiv = document.createElement( 'div' ); 16 | msDiv.id = 'ms'; 17 | msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;'; 18 | container.appendChild( msDiv ); 19 | 20 | var msText = document.createElement( 'div' ); 21 | msText.id = 'msText'; 22 | msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px'; 23 | msText.innerHTML= 'Memory'; 24 | msDiv.appendChild( msText ); 25 | 26 | var msGraph = document.createElement( 'div' ); 27 | msGraph.id = 'msGraph'; 28 | msGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0f0'; 29 | msDiv.appendChild( msGraph ); 30 | 31 | while ( msGraph.children.length < 74 ) { 32 | 33 | var bar = document.createElement( 'span' ); 34 | bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#131'; 35 | msGraph.appendChild( bar ); 36 | 37 | } 38 | 39 | var updateGraph = function ( dom, height, color ) { 40 | 41 | var child = dom.appendChild( dom.firstChild ); 42 | child.style.height = height + 'px'; 43 | if( color ) child.style.backgroundColor = color; 44 | 45 | } 46 | 47 | var perf = window.performance || {}; 48 | // polyfill usedJSHeapSize 49 | if (!perf && !perf.memory){ 50 | perf.memory = { usedJSHeapSize : 0 }; 51 | } 52 | if (perf && !perf.memory){ 53 | perf.memory = { usedJSHeapSize : 0 }; 54 | } 55 | 56 | // support of the API? 57 | if( perf.memory.totalJSHeapSize === 0 ){ 58 | console.warn('totalJSHeapSize === 0... performance.memory is only available in Chrome .') 59 | } 60 | 61 | // TODO, add a sanity check to see if values are bucketed. 62 | // If so, reminde user to adopt the --enable-precise-memory-info flag. 63 | // open -a "/Applications/Google Chrome.app" --args --enable-precise-memory-info 64 | 65 | var lastTime = Date.now(); 66 | var lastUsedHeap= perf.memory.usedJSHeapSize; 67 | return { 68 | domElement: container, 69 | 70 | update: function () { 71 | 72 | // refresh only 30time per second 73 | if( Date.now() - lastTime < 1000/30 ) return; 74 | lastTime = Date.now() 75 | 76 | var delta = perf.memory.usedJSHeapSize - lastUsedHeap; 77 | lastUsedHeap = perf.memory.usedJSHeapSize; 78 | var color = delta < 0 ? '#830' : '#131'; 79 | 80 | var ms = perf.memory.usedJSHeapSize; 81 | msMin = Math.min( msMin, ms ); 82 | msMax = Math.max( msMax, ms ); 83 | msText.textContent = "Mem: " + bytesToSize(ms, 2); 84 | 85 | var normValue = ms / (30*1024*1024); 86 | var height = Math.min( 30, 30 - normValue * 30 ); 87 | updateGraph( msGraph, height, color); 88 | 89 | function bytesToSize( bytes, nFractDigit ){ 90 | var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; 91 | if (bytes == 0) return 'n/a'; 92 | nFractDigit = nFractDigit !== undefined ? nFractDigit : 0; 93 | var precision = Math.pow(10, nFractDigit); 94 | var i = Math.floor(Math.log(bytes) / Math.log(1024)); 95 | return Math.round(bytes*precision / Math.pow(1024, i))/precision + ' ' + sizes[i]; 96 | }; 97 | } 98 | 99 | } 100 | 101 | }; -------------------------------------------------------------------------------- /examples/js-repaint-perf/vendor/monitor.js: -------------------------------------------------------------------------------- 1 | var Monitoring = Monitoring || (function() { 2 | 3 | var stats = new MemoryStats(); 4 | stats.domElement.style.position = 'fixed'; 5 | stats.domElement.style.right = '0px'; 6 | stats.domElement.style.bottom = '0px'; 7 | document.body.appendChild( stats.domElement ); 8 | requestAnimationFrame(function rAFloop(){ 9 | stats.update(); 10 | requestAnimationFrame(rAFloop); 11 | }); 12 | 13 | var RenderRate = function () { 14 | var container = document.createElement( 'div' ); 15 | container.id = 'stats'; 16 | container.style.cssText = 'width:150px;opacity:0.9;cursor:pointer;position:fixed;right:80px;bottom:0px;'; 17 | 18 | var msDiv = document.createElement( 'div' ); 19 | msDiv.id = 'ms'; 20 | msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;'; 21 | container.appendChild( msDiv ); 22 | 23 | var msText = document.createElement( 'div' ); 24 | msText.id = 'msText'; 25 | msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px'; 26 | msText.innerHTML= 'Repaint rate: 0/sec'; 27 | msDiv.appendChild( msText ); 28 | 29 | var bucketSize = 20; 30 | var bucket = []; 31 | var lastTime = Date.now(); 32 | return { 33 | domElement: container, 34 | ping: function () { 35 | var start = lastTime; 36 | var stop = Date.now(); 37 | var rate = 1000 / (stop - start); 38 | bucket.push(rate); 39 | if (bucket.length > bucketSize) { 40 | bucket.shift(); 41 | } 42 | var sum = 0; 43 | for (var i = 0; i < bucket.length; i++) { 44 | sum = sum + bucket[i]; 45 | } 46 | msText.textContent = "Repaint rate: " + (sum / bucket.length).toFixed(2) + "/sec"; 47 | lastTime = stop; 48 | } 49 | } 50 | }; 51 | 52 | var renderRate = new RenderRate(); 53 | document.body.appendChild( renderRate.domElement ); 54 | 55 | return { 56 | memoryStats: stats, 57 | renderRate: renderRate 58 | }; 59 | 60 | })(); 61 | -------------------------------------------------------------------------------- /examples/js-repaint-perf/vendor/react-lite.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * react-lite.js v0.15.12 3 | * (c) 2016 Jade Gu 4 | * Released under the MIT License. 5 | */ 6 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.React=t()}(this,function(){"use strict";function e(e,t,n,r,o){var i={vtype:e,type:t,props:n,refs:Fe,key:r,ref:o};return(e===Me||e===Re)&&(i.uid=ee()),i}function t(e,t,n){var r=e.vtype,o=null;return r?r===Ue?o=l(e,t,n):r===Re?o=x(e,t,n):r===Me?o=v(e,t,n):r===De&&(o=document.createComment("react-empty: "+e.uid)):o=document.createTextNode(e),o}function n(e,t,n,o){var i=e.vtype;if(i===Re)return k(e,t,n,o);if(i===Me)return m(e,t,n,o);if(i!==Ue)return n;var a=e.props[Te]&&e.props[Te].__html;return null!=a?(f(e,t,n,o),c(t,n,o)):(r(e,t,n,o),f(e,t,n,o)),n}function r(e,t,n,r){var s={removes:[],updates:[],creates:[]};d(s,e,t,n,r),J(s.removes,i),J(s.updates,o),J(s.creates,a)}function o(e){if(e){var t=e.vnode,n=e.node;e.shouldIgnore||(t.vtype?t.vtype===Ue?f(t,e.newVnode,n,e.parentContext):t.vtype===Me?n=m(t,e.newVnode,n,e.parentContext):t.vtype===Re&&(n=k(t,e.newVnode,n,e.parentContext)):n.replaceData(0,n.length,e.newVnode));var r=n.parentNode.childNodes[e.index];return r!==n&&n.parentNode.insertBefore(n,r),n}}function i(e){s(e.vnode,e.node),e.node.parentNode.removeChild(e.node)}function a(e){var n=t(e.vnode,e.parentContext,e.parentNode.namespaceURI);e.parentNode.insertBefore(n,e.parentNode.childNodes[e.index])}function s(e,t){var n=e.vtype;n===Ue?h(e,t):n===Re?b(e,t):n===Me&&g(e,t)}function l(e,t,n){var r=e.type,o=e.props,i=null;"svg"===r||n===Ae?(i=document.createElementNS(Ae,r),n=Ae):i=document.createElement(r),c(e,i,t);var a=r.indexOf("-")>=0||null!=o.is;return oe(i,o,a),T(e.refs,e.ref,i),i}function c(e,n,r){for(var o=n.vchildren=u(e),i=n.namespaceURI,a=0,s=o.length;s>a;a++)n.appendChild(t(o[a],r,i))}function u(e){var t=e.props.children,n=[];return st(t)?J(t,p,n):p(t,n),n}function p(e,t){null!=e&&"boolean"!=typeof e&&(t[t.length]=e.vtype?e:""+e)}function d(e,t,n,r,o){var i=r.childNodes,a=r.vchildren,s=r.vchildren=u(n),l=a.length,c=s.length;if(0!==l)if(0!==c){for(var p=Array(c),f=null,h=null,v=0;l>v;v++)for(var m=a[v],g=0;c>g;g++)if(!p[g]){var y=s[g];if(m===y){var x=!0;o&&(m.vtype===Re||m.vtype===Me)&&m.type.contextTypes&&(x=!1),p[g]={shouldIgnore:x,vnode:m,newVnode:y,node:i[v],parentContext:o,index:g},a[v]=null;break}}for(var v=0;l>v;v++){var k=a[v];if(null!==k){for(var b=!0,g=0;c>g;g++)if(!p[g]){var C=s[g];if(C.type===k.type&&C.key===k.key&&C.refs===k.refs){p[g]={vnode:k,newVnode:C,node:i[v],parentContext:o,index:g},b=!1;break}}b&&(f||(f=[]),f.push({vnode:k,node:i[v]}))}}for(var v=0;c>v;v++){var w=p[v];w?w.vnode.vtype===Ue&&d(e,w.vnode,w.newVnode,w.node,w.parentContext):(h||(h=[]),h.push({vnode:s[v],parentNode:r,parentContext:o,index:v}))}f&&e.removes.push(f),h&&e.creates.push(h),e.updates.push(p)}else for(var v=0;l>v;v++)e.removes.push({vnode:a[v],node:i[v]});else if(c>0)for(var v=0;c>v;v++)e.creates.push({vnode:s[v],parentNode:r,parentContext:o,index:v})}function f(e,t,n){var r=e.type.indexOf("-")>=0||null!=e.props.is;return ie(n,e.props,t.props,r),e.ref!==t.ref&&(A(e.refs,e.ref),T(t.refs,t.ref,n)),n}function h(e,t){for(var n=(e.props,t.vchildren),r=t.childNodes,o=0,i=n.length;i>o;o++)s(n[o],r[o]);A(e.refs,e.ref),t.eventStore=t.vchildren=null}function v(e,n,r){var o=y(e,n),i=t(o,n,r);return i.cache=i.cache||{},i.cache[e.uid]=o,i}function m(e,t,n,r){var o=e.uid,i=n.cache[o];delete n.cache[o];var a=y(t,r),s=O(i,a,n,r);return s.cache=s.cache||{},s.cache[t.uid]=a,s!==n&&E(s.cache,n.cache,s),s}function g(e,t){var n=e.uid,r=t.cache[n];delete t.cache[n],s(r,t)}function y(t,n){var r=t.type,o=t.props,i=C(n,r.contextTypes),a=r(o,i);if(a&&a.render&&(a=a.render()),null===a||a===!1)a=e(De);else if(!a||!a.vtype)throw new Error("@"+r.name+"#render:You may have returned undefined, an array or some other invalid object");return a}function x(e,n,r){var o=e.type,i=e.props,a=e.uid,s=C(n,o.contextTypes),l=new o(i,s),c=l.$updater,u=l.$cache;u.parentContext=n,c.isPending=!0,l.props=l.props||i,l.context=l.context||s,l.componentWillMount&&(l.componentWillMount(),l.state=c.getState());var p=w(l),d=t(p,P(l,n),r);return d.cache=d.cache||{},d.cache[a]=l,u.vnode=p,u.node=d,u.isMounted=!0,Ie.push(l),T(e.refs,e.ref,l),d}function k(e,t,n,r){var o=e.uid,i=n.cache[o],a=i.$updater,s=i.$cache,l=t.type,c=t.props,u=C(r,l.contextTypes);return delete n.cache[o],n.cache[t.uid]=i,s.parentContext=r,i.componentWillReceiveProps&&(a.isPending=!0,i.componentWillReceiveProps(c,u),a.isPending=!1),a.emitUpdate(c,u),e.ref!==t.ref&&(A(e.refs,e.ref),T(t.refs,t.ref,i)),s.node}function b(e,t){var n=e.uid,r=t.cache[n],o=r.$cache;delete t.cache[n],A(e.refs,e.ref),r.setState=r.forceUpdate=G,r.componentWillUnmount&&r.componentWillUnmount(),s(o.vnode,t),delete r.setState,o.isMounted=!1,o.node=o.parentContext=o.vnode=r.refs=r.context=null}function C(e,t){var n={};if(!t||!e)return n;for(var r in t)t.hasOwnProperty(r)&&(n[r]=e[r]);return n}function w(t,n){Fe=t.refs;var r=t.render();if(null===r||r===!1)r=e(De);else if(!r||!r.vtype)throw new Error("@"+t.constructor.name+"#render:You may have returned undefined, an array or some other invalid object");return Fe=null,r}function P(e,t){if(e.getChildContext){var n=e.getChildContext();n&&(t=Q(Q({},t),n))}return t}function S(){var e=Ie.length;if(e){var t=Ie;Ie=[];for(var n=-1;e--;){var r=t[++n],o=r.$updater;r.componentDidMount&&r.componentDidMount(),o.isPending=!1,o.emitUpdate()}}}function O(e,r,o,i){var a=o;return null==r?(s(e,o),o.parentNode.removeChild(o)):e.type!==r.type||e.key!==r.key?(s(e,o),a=t(r,i,o.namespaceURI),o.parentNode.replaceChild(a,o)):(e!==r||i)&&(a=n(e,r,o,i)),a}function N(){return this}function T(e,t,n){e&&null!=t&&n&&(n.nodeName&&!n.getDOMNode&&(n.getDOMNode=N),Y(t)?t(n):e[t]=n)}function A(e,t){e&&null!=t&&(Y(t)?t(null):delete e[t])}function E(e,t,n){for(var r in t)if(t.hasOwnProperty(r)){var o=t[r];e[r]=o,o.forceUpdate&&(o.$cache.node=n)}}function U(e){this.instance=e,this.pendingStates=[],this.pendingCallbacks=[],this.isPending=!1,this.nextProps=this.nextContext=null,this.clearCallbacks=this.clearCallbacks.bind(this)}function M(e,t){this.$updater=new U(this),this.$cache={isMounted:!1},this.props=e,this.state={},this.refs={},this.context=t}function R(e,t,n,r,o){var i=!0;if(e.shouldComponentUpdate&&(i=e.shouldComponentUpdate(t,n,r)),i===!1)return e.props=t,e.state=n,void(e.context=r||{});var a=e.$cache;a.props=t,a.state=n,a.context=r||{},e.forceUpdate(o)}function D(e){return e="onDoubleClick"===e?"ondblclick":e,e.toLowerCase()}function F(e,t,n){if(t=D(t),1===$e[t])return void(e[t]=n);var r=e.eventStore||(e.eventStore={});r[t]=n,je[t]||(document.addEventListener(t.substr(2),L,!1),je[t]=!0),ze&&t===Be&&e.addEventListener("click",Ve,!1);var o=e.nodeName;"onchange"!==t||"INPUT"!==o&&"TEXTAREA"!==o||F(e,"oninput",n)}function I(e,t){if(t=D(t),1===$e[t])return void(e[t]=null);var n=e.eventStore||(e.eventStore={});delete n[t],ze&&t===Be&&e.removeEventListener("click",Ve,!1);var r=e.nodeName;"onchange"!==t||"INPUT"!==r&&"TEXTAREA"!==r||delete n.oninput}function L(e){var t=e.target,n=e.type,r="on"+n,o=void 0;for(Le.isPending=!0;t;){var i=t,a=i.eventStore,s=a&&a[r];if(s){if(o||(o=$(e)),o.currentTarget=t,s.call(t,o),o.$cancalBubble)break;t=t.parentNode}else t=t.parentNode}Le.isPending=!1,Le.batchUpdate()}function $(e){var t={},n=function(){return t.$cancalBubble=!0};t.nativeEvent=e;for(var r in e)"function"!=typeof e[r]?t[r]=e[r]:"stopPropagation"===r||"stopImmediatePropagation"===r?t[r]=n:t[r]=e[r].bind(e);return t}function z(e,t){for(var n in t)t.hasOwnProperty(n)&&_(e,n,t[n])}function V(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]="")}function B(e,t,n){if(t!==n){if(!n&&t)return void V(e,t);if(n&&!t)return void z(e,n);for(var r in t)n.hasOwnProperty(r)?n[r]!==t[r]&&_(e,r,n[r]):e[r]="";for(var r in n)t.hasOwnProperty(r)||_(e,r,n[r])}}function j(e,t){return e+t.charAt(0).toUpperCase()+t.substring(1)}function _(e,t,n){return!_e[t]&&qe.test(n)?void(e[t]=n+"px"):("float"===t&&(t="cssFloat"),(null==n||"boolean"==typeof n)&&(n=""),void(e[t]=n))}function W(e){var t=e.props,n=e.attrNS,r=e.domAttrs,o=e.domProps;for(var i in t)if(t.hasOwnProperty(i)){var a=t[i];Ze[i]={attributeName:r.hasOwnProperty(i)?r[i]:i.toLowerCase(),propertyName:o.hasOwnProperty(i)?o[i]:i,attributeNamespace:n.hasOwnProperty(i)?n[i]:null,mustUseProperty:q(a,Ke),hasBooleanValue:q(a,Je),hasNumericValue:q(a,Qe),hasPositiveNumericValue:q(a,et),hasOverloadedBooleanValue:q(a,tt)}}}function q(e,t){return(e&t)===t}function H(e,t,n){var r=Ze.hasOwnProperty(t)&&Ze[t];if(r)if(null==n||r.hasBooleanValue&&!n||r.hasNumericValue&&isNaN(n)||r.hasPositiveNumericValue&&1>n||r.hasOverloadedBooleanValue&&n===!1)X(e,t);else if(r.mustUseProperty){var o=r.propertyName;("value"!==o||""+e[o]!=""+n)&&(e[o]=n)}else{var i=r.attributeName,a=r.attributeNamespace;a?e.setAttributeNS(a,i,""+n):r.hasBooleanValue||r.hasOverloadedBooleanValue&&n===!0?e.setAttribute(i,""):e.setAttribute(i,""+n)}else Ge(t)&&Ye.test(t)&&(null==n?e.removeAttribute(t):e.setAttribute(t,""+n))}function X(e,t){var n=Ze.hasOwnProperty(t)&&Ze[t];if(n)if(n.mustUseProperty){var r=n.propertyName;n.hasBooleanValue?e[r]=!1:("value"!==r||""+e[r]!="")&&(e[r]="")}else e.removeAttribute(n.attributeName);else Ge(t)&&e.removeAttribute(t)}function Y(e){return"function"==typeof e}function G(){}function Z(e){return e}function K(e,t){return function(){return e.apply(this,arguments),t.apply(this,arguments)}}function J(e,t,n){for(var r=e.length,o=-1;r--;){var i=e[++o];st(i)?J(i,t,n):t(i,n)}}function Q(e,t){if(!t)return e;for(var n=Object.keys(t),r=n.length;r--;)e[n[r]]=t[n[r]];return e}function ee(){return++lt}function te(e,t,n,r){ct.test(t)?F(e,t,n):"style"===t?z(e.style,n):t===Te?n&&null!=n.__html&&(e.innerHTML=n.__html):r?null==n?e.removeAttribute(t):e.setAttribute(t,""+n):H(e,t,n)}function ne(e,t,n,r){ct.test(t)?I(e,t):"style"===t?V(e.style,n):t===Te?e.innerHTML="":r?e.removeAttribute(t):X(e,t)}function re(e,t,n,r,o){return("value"===t||"checked"===t)&&(r=e[t]),n!==r?void 0===n?void ne(e,t,r,o):void("style"===t?B(e.style,r,n):te(e,t,n,o)):void 0}function oe(e,t,n){for(var r in t)"children"!==r&&te(e,r,t[r],n)}function ie(e,t,n,r){for(var o in t)"children"!==o&&(n.hasOwnProperty(o)?re(e,o,n[o],t[o],r):ne(e,o,t[o],r));for(var o in n)"children"===o||t.hasOwnProperty(o)||te(e,o,n[o],r)}function ae(e,n,r,o){if(!e.vtype)throw new Error("cannot render "+e+" to container");var i=n[Ee]||(n[Ee]=ee()),a=ut[i];if(a)return void(a===!0?ut[i]=a={vnode:e,callback:r,parentContext:o}:(a.vnode=e,a.parentContext=o,a.callback&&(a.callback=a.callback?K(a.callback,r):r)));ut[i]=!0;var s=null,l=null;if(s=pt[i])l=O(s,e,n.firstChild,o);else{l=t(e,o,n.namespaceURI);for(var c=null;c=n.lastChild;)n.removeChild(c);n.appendChild(l)}pt[i]=e;var u=Le.isPending;Le.isPending=!0,S(),a=ut[i],delete ut[i];var p=null;return st(a)?p=ae(a.vnode,n,a.parentContext,a.callback):e.vtype===Ue?p=l:e.vtype===Re&&(p=l.cache[e.uid]),u||(Le.isPending=!1,Le.batchUpdate()),r&&r.call(p),p}function se(e,t,n){return ae(e,t,n)}function le(e,t,n,r){var o=e.vnode?e.vnode.context:e.$cache.parentContext;return ae(t,n,r,o)}function ce(e){if(!e.nodeName)throw new Error("expect node");var t=e[Ee],n=null;return(n=pt[t])?(s(n,e.firstChild),e.removeChild(e.firstChild),delete pt[t],!0):!1}function ue(e){if(null==e)return null;if(e.nodeName)return e;var t=e;if(t.getDOMNode&&t.$cache.isMounted)return t.getDOMNode();throw new Error("findDOMNode can not find Node")}function pe(t,n,r){var o=null;if("string"==typeof t)o=Ue;else{if("function"!=typeof t)throw new Error("React.createElement: unexpect type [ "+t+" ]");o=t.prototype&&"function"==typeof t.prototype.forceUpdate?Re:Me}var i=null,a=null,s={};if(null!=n)for(var l in n)n.hasOwnProperty(l)&&("key"===l?void 0!==n.key&&(i=""+n.key):"ref"===l?void 0!==n.ref&&(a=n.ref):s[l]=n[l]);var c=t.defaultProps;if(c)for(var l in c)void 0===s[l]&&(s[l]=c[l]);var u=arguments.length,p=r;if(u>3){p=Array(u-2);for(var d=2;u>d;d++)p[d-2]=arguments[d]}return void 0!==p&&(s.children=p),e(o,t,s,i,a)}function de(e){return null!=e&&!!e.vtype}function fe(e,t){for(var n=e.type,r=e.key,o=e.ref,i=Q(Q({key:r,ref:o},e.props),t),a=arguments.length,s=Array(a>2?a-2:0),l=2;a>l;l++)s[l-2]=arguments[l];var c=pe.apply(void 0,[n,i].concat(s));return c.ref===e.ref&&(c.refs=e.refs),c}function he(e){var t=function(){for(var t=arguments.length,n=Array(t),r=0;t>r;r++)n[r]=arguments[r];return pe.apply(void 0,[e].concat(n))};return t.type=e,t}function ve(e){if(de(e))return e;throw new Error("expect only one child")}function me(e,t,n){if(null==e)return e;var r=0;st(e)?J(e,function(e){t.call(n,e,r++)}):t.call(n,e,r)}function ge(e,t,n){if(null==e)return e;var r=[],o={};me(e,function(e,i){var a={};a.child=t.call(n,e,i)||e,a.isEqual=a.child===e;var s=a.key=ke(e,i);o.hasOwnProperty(s)?o[s]+=1:o[s]=0,a.index=o[s],r.push(a)});var i=[];return r.forEach(function(e){var t=e.child,n=e.key,r=e.index,a=e.isEqual;if(null!=t&&"boolean"!=typeof t){if(!de(t)||null==n)return void i.push(t);0!==o[n]&&(n+=":"+r),a||(n=be(t.key||"")+"/"+n),t=fe(t,{key:n}),i.push(t)}}),i}function ye(e){var t=0;return me(e,function(){t++}),t}function xe(e){return ge(e,Z)||[]}function ke(e,t){var n=void 0;return n=de(e)&&"string"==typeof e.key?".$"+e.key:"."+t.toString(36)}function be(e){return(""+e).replace(gt,"//")}function Ce(e,t){e.forEach(function(e){e&&(st(e.mixins)&&Ce(e.mixins,t),t(e))})}function we(e,t){for(var n in t)if(t.hasOwnProperty(n)){var r=t[n];if("getInitialState"!==n){var o=e[n];Y(o)&&Y(r)?e[n]=K(o,r):e[n]=r}else e.$getInitialStates.push(r)}}function Pe(e,t){t.propTypes&&(e.propTypes=e.propTypes||{},Q(e.propTypes,t.propTypes)),t.contextTypes&&(e.contextTypes=e.contextTypes||{},Q(e.contextTypes,t.contextTypes)),Q(e,t.statics),Y(t.getDefaultProps)&&(e.defaultProps=e.defaultProps||{},Q(e.defaultProps,t.getDefaultProps()))}function Se(e,t){for(var n in t)t.hasOwnProperty(n)&&Y(t[n])&&(e[n]=t[n].bind(e))}function Oe(){var e=this,t={},n=this.setState;return this.setState=xt,this.$getInitialStates.forEach(function(n){Y(n)&&Q(t,n.call(e))}),this.setState=n,t}function Ne(e){function t(n,r){M.call(this,n,r),this.constructor=t,e.autobind!==!1&&Se(this,t.prototype),this.state=this.getInitialState()||this.state}if(!Y(e.render))throw new Error("createClass: spec.render is not function");var n=e.mixins||[],r=n.concat(e);e.mixins=null,t.displayName=e.displayName;var o=t.prototype=new xt;return o.$getInitialStates=[],Ce(r,function(e){we(o,e),Pe(t,e)}),o.getInitialState=Oe,e.mixins=n,t}var Te="dangerouslySetInnerHTML",Ae="http://www.w3.org/2000/svg",Ee="liteid",Ue=2,Me=3,Re=4,De=5,Fe=null,Ie=[],Le={updaters:[],isPending:!1,add:function(e){this.updaters.push(e)},batchUpdate:function(){if(!this.isPending){this.isPending=!0;for(var e=this.updaters,t=void 0;t=e.pop();)t.updateComponent();this.isPending=!1}}};U.prototype={emitUpdate:function(e,t){this.nextProps=e,this.nextContext=t,e||!Le.isPending?this.updateComponent():Le.add(this)},updateComponent:function(){var e=this.instance,t=this.pendingStates,n=this.nextProps,r=this.nextContext;(n||t.length>0)&&(n=n||e.props,r=r||e.context,this.nextProps=this.nextContext=null,R(e,n,this.getState(),r,this.clearCallbacks))},addState:function(e){e&&(this.pendingStates.push(e),this.isPending||this.emitUpdate())},replaceState:function(e){var t=this.pendingStates;t.pop(),t.push([e])},getState:function(){var e=this.instance,t=this.pendingStates,n=e.state,r=e.props;return t.length&&(n=Q({},n),t.forEach(function(t){return st(t)?void(n=Q({},t[0])):(Y(t)&&(t=t.call(e,n,r)),void Q(n,t))}),t.length=0),n},clearCallbacks:function(){var e=this.pendingCallbacks,t=this.instance;e.length>0&&(this.pendingCallbacks=[],e.forEach(function(e){return e.call(t)}))},addCallback:function(e){Y(e)&&this.pendingCallbacks.push(e)}},M.prototype={constructor:M,forceUpdate:function(e){var t=this.$updater,n=this.$cache,r=this.props,o=this.state,i=this.context;if(!t.isPending&&n.isMounted){var a=n.props||r,s=n.state||o,l=n.context||{},c=n.parentContext,u=n.node,p=n.vnode;n.props=n.state=n.context=null,t.isPending=!0,this.componentWillUpdate&&this.componentWillUpdate(a,s,l),this.state=s,this.props=a,this.context=l;var d=w(this),f=O(p,d,u,P(this,c));f!==u&&(f.cache=f.cache||{},E(f.cache,u.cache,f)),n.vnode=d,n.node=f,S(),this.componentDidUpdate&&this.componentDidUpdate(r,o,i),e&&e.call(this),t.isPending=!1,t.emitUpdate()}},setState:function(e,t){var n=this.$updater;n.addCallback(t),n.addState(e)},replaceState:function(e,t){var n=this.$updater;n.addCallback(t),n.replaceState(e)},getDOMNode:function(){var e=this.$cache.node;return e&&"#comment"===e.nodeName?null:e},isMounted:function(){return this.$cache.isMounted}};var $e={onmouseleave:1,onmouseenter:1,onload:1,onunload:1,onscroll:1,onfocus:1,onblur:1,onrowexit:1,onbeforeunload:1,onstop:1,ondragdrop:1,ondragenter:1,ondragexit:1,ondraggesture:1,ondragover:1,oncontextmenu:1},ze="ontouchstart"in document,Ve=function(){},Be="onclick",je={},_e={animationIterationCount:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridColumn:1,fontWeight:1,lineClamp:1,lineHeight:1,opacity:1,order:1,orphans:1,tabSize:1,widows:1,zIndex:1,zoom:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1},We=["Webkit","ms","Moz","O"];Object.keys(_e).forEach(function(e){We.forEach(function(t){_e[j(t,e)]=1})});var qe=/^-?\d+(\.\d+)?$/,He=":A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD",Xe=He+"\\-.0-9\\uB7\\u0300-\\u036F\\u203F-\\u2040",Ye=new RegExp("^["+He+"]["+Xe+"]*$"),Ge=RegExp.prototype.test.bind(new RegExp("^(data|aria)-["+Xe+"]*$")),Ze={},Ke=1,Je=4,Qe=8,et=24,tt=32,nt={props:{accept:0,acceptCharset:0,accessKey:0,action:0,allowFullScreen:Je,allowTransparency:0,alt:0,async:Je,autoComplete:0,autoFocus:Je,autoPlay:Je,capture:Je,cellPadding:0,cellSpacing:0,charSet:0,challenge:0,checked:Ke|Je,cite:0,classID:0,className:0,cols:et,colSpan:0,content:0,contentEditable:0,contextMenu:0,controls:Je,coords:0,crossOrigin:0,data:0,dateTime:0,"default":Je,defaultValue:Ke,defaultChecked:Ke|Je,defer:Je,dir:0,disabled:Je,download:tt,draggable:0,encType:0,form:0,formAction:0,formEncType:0,formMethod:0,formNoValidate:Je,formTarget:0,frameBorder:0,headers:0,height:0,hidden:Je,high:0,href:0,hrefLang:0,htmlFor:0,httpEquiv:0,icon:0,id:0,inputMode:0,integrity:0,is:0,keyParams:0,keyType:0,kind:0,label:0,lang:0,list:0,loop:Je,low:0,manifest:0,marginHeight:0,marginWidth:0,max:0,maxLength:0,media:0,mediaGroup:0,method:0,min:0,minLength:0,multiple:Ke|Je,muted:Ke|Je,name:0,nonce:0,noValidate:Je,open:Je,optimum:0,pattern:0,placeholder:0,poster:0,preload:0,profile:0,radioGroup:0,readOnly:Je,rel:0,required:Je,reversed:Je,role:0,rows:et,rowSpan:Qe,sandbox:0,scope:0,scoped:Je,scrolling:0,seamless:Je,selected:Ke|Je,shape:0,size:et,sizes:0,span:et,spellCheck:0,src:0,srcDoc:0,srcLang:0,srcSet:0,start:Qe,step:0,style:0,summary:0,tabIndex:0,target:0,title:0,type:0,useMap:0,value:Ke,width:0,wmode:0,wrap:0,about:0,datatype:0,inlist:0,prefix:0,property:0,resource:0,"typeof":0,vocab:0,autoCapitalize:0,autoCorrect:0,autoSave:0,color:0,itemProp:0,itemScope:Je,itemType:0,itemID:0,itemRef:0,results:0,security:0,unselectable:0},attrNS:{},domAttrs:{acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},domProps:{}},rt="http://www.w3.org/1999/xlink",ot="http://www.w3.org/XML/1998/namespace",it={accentHeight:"accent-height",accumulate:0,additive:0,alignmentBaseline:"alignment-baseline",allowReorder:"allowReorder",alphabetic:0,amplitude:0,arabicForm:"arabic-form",ascent:0,attributeName:"attributeName",attributeType:"attributeType",autoReverse:"autoReverse",azimuth:0,baseFrequency:"baseFrequency",baseProfile:"baseProfile",baselineShift:"baseline-shift",bbox:0,begin:0,bias:0,by:0,calcMode:"calcMode",capHeight:"cap-height",clip:0,clipPath:"clip-path",clipRule:"clip-rule",clipPathUnits:"clipPathUnits",colorInterpolation:"color-interpolation",colorInterpolationFilters:"color-interpolation-filters",colorProfile:"color-profile",colorRendering:"color-rendering",contentScriptType:"contentScriptType",contentStyleType:"contentStyleType",cursor:0,cx:0,cy:0,d:0,decelerate:0,descent:0,diffuseConstant:"diffuseConstant",direction:0,display:0,divisor:0,dominantBaseline:"dominant-baseline",dur:0,dx:0,dy:0,edgeMode:"edgeMode",elevation:0,enableBackground:"enable-background",end:0,exponent:0,externalResourcesRequired:"externalResourcesRequired",fill:0,fillOpacity:"fill-opacity",fillRule:"fill-rule",filter:0,filterRes:"filterRes",filterUnits:"filterUnits",floodColor:"flood-color",floodOpacity:"flood-opacity",focusable:0,fontFamily:"font-family",fontSize:"font-size",fontSizeAdjust:"font-size-adjust",fontStretch:"font-stretch",fontStyle:"font-style",fontVariant:"font-variant",fontWeight:"font-weight",format:0,from:0,fx:0,fy:0,g1:0,g2:0,glyphName:"glyph-name",glyphOrientationHorizontal:"glyph-orientation-horizontal",glyphOrientationVertical:"glyph-orientation-vertical",glyphRef:"glyphRef",gradientTransform:"gradientTransform",gradientUnits:"gradientUnits",hanging:0,horizAdvX:"horiz-adv-x",horizOriginX:"horiz-origin-x",ideographic:0,imageRendering:"image-rendering","in":0,in2:0,intercept:0,k:0,k1:0,k2:0,k3:0,k4:0,kernelMatrix:"kernelMatrix",kernelUnitLength:"kernelUnitLength",kerning:0,keyPoints:"keyPoints",keySplines:"keySplines",keyTimes:"keyTimes",lengthAdjust:"lengthAdjust",letterSpacing:"letter-spacing",lightingColor:"lighting-color",limitingConeAngle:"limitingConeAngle",local:0,markerEnd:"marker-end",markerMid:"marker-mid",markerStart:"marker-start",markerHeight:"markerHeight",markerUnits:"markerUnits",markerWidth:"markerWidth",mask:0,maskContentUnits:"maskContentUnits",maskUnits:"maskUnits",mathematical:0,mode:0,numOctaves:"numOctaves",offset:0,opacity:0,operator:0,order:0,orient:0,orientation:0,origin:0,overflow:0,overlinePosition:"overline-position",overlineThickness:"overline-thickness",paintOrder:"paint-order",panose1:"panose-1",pathLength:"pathLength",patternContentUnits:"patternContentUnits",patternTransform:"patternTransform",patternUnits:"patternUnits",pointerEvents:"pointer-events",points:0,pointsAtX:"pointsAtX",pointsAtY:"pointsAtY",pointsAtZ:"pointsAtZ",preserveAlpha:"preserveAlpha",preserveAspectRatio:"preserveAspectRatio",primitiveUnits:"primitiveUnits",r:0,radius:0,refX:"refX",refY:"refY",renderingIntent:"rendering-intent",repeatCount:"repeatCount",repeatDur:"repeatDur",requiredExtensions:"requiredExtensions",requiredFeatures:"requiredFeatures",restart:0,result:0,rotate:0,rx:0,ry:0,scale:0,seed:0,shapeRendering:"shape-rendering",slope:0,spacing:0,specularConstant:"specularConstant",specularExponent:"specularExponent",speed:0,spreadMethod:"spreadMethod",startOffset:"startOffset",stdDeviation:"stdDeviation",stemh:0,stemv:0,stitchTiles:"stitchTiles",stopColor:"stop-color",stopOpacity:"stop-opacity",strikethroughPosition:"strikethrough-position",strikethroughThickness:"strikethrough-thickness",string:0,stroke:0,strokeDasharray:"stroke-dasharray",strokeDashoffset:"stroke-dashoffset",strokeLinecap:"stroke-linecap",strokeLinejoin:"stroke-linejoin",strokeMiterlimit:"stroke-miterlimit",strokeOpacity:"stroke-opacity",strokeWidth:"stroke-width",surfaceScale:"surfaceScale",systemLanguage:"systemLanguage",tableValues:"tableValues",targetX:"targetX",targetY:"targetY",textAnchor:"text-anchor",textDecoration:"text-decoration",textRendering:"text-rendering",textLength:"textLength",to:0,transform:0,u1:0,u2:0,underlinePosition:"underline-position",underlineThickness:"underline-thickness",unicode:0,unicodeBidi:"unicode-bidi",unicodeRange:"unicode-range",unitsPerEm:"units-per-em",vAlphabetic:"v-alphabetic",vHanging:"v-hanging",vIdeographic:"v-ideographic",vMathematical:"v-mathematical",values:0,vectorEffect:"vector-effect",version:0,vertAdvY:"vert-adv-y",vertOriginX:"vert-origin-x",vertOriginY:"vert-origin-y",viewBox:"viewBox",viewTarget:"viewTarget",visibility:0,widths:0,wordSpacing:"word-spacing",writingMode:"writing-mode",x:0,xHeight:"x-height",x1:0,x2:0,xChannelSelector:"xChannelSelector",xlinkActuate:"xlink:actuate",xlinkArcrole:"xlink:arcrole",xlinkHref:"xlink:href",xlinkRole:"xlink:role",xlinkShow:"xlink:show",xlinkTitle:"xlink:title",xlinkType:"xlink:type",xmlBase:"xml:base",xmlLang:"xml:lang",xmlSpace:"xml:space",y:0,y1:0,y2:0,yChannelSelector:"yChannelSelector",z:0,zoomAndPan:"zoomAndPan"},at={props:{},attrNS:{xlinkActuate:rt,xlinkArcrole:rt,xlinkHref:rt,xlinkRole:rt,xlinkShow:rt,xlinkTitle:rt,xlinkType:rt,xmlBase:ot,xmlLang:ot,xmlSpace:ot},domAttrs:{},domProps:{}};Object.keys(it).map(function(e){at.props[e]=0,it[e]&&(at.domAttrs[e]=it[e])}),W(nt),W(at);var st=Array.isArray,lt=0,ct=/^on/i;Object.freeze||(Object.freeze=Z);var ut={},pt={},dt=Object.freeze({render:se,unstable_renderSubtreeIntoContainer:le,unmountComponentAtNode:ce,findDOMNode:ue}),ft="a|abbr|address|area|article|aside|audio|b|base|bdi|bdo|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|data|datalist|dd|del|details|dfn|dialog|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|main|map|mark|menu|menuitem|meta|meter|nav|noscript|object|ol|optgroup|option|output|p|param|picture|pre|progress|q|rp|rt|ruby|s|samp|script|section|select|small|source|span|strong|style|sub|summary|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|track|u|ul|var|video|wbr|circle|clipPath|defs|ellipse|g|image|line|linearGradient|mask|path|pattern|polygon|polyline|radialGradient|rect|stop|svg|text|tspan",ht={};ft.split("|").forEach(function(e){ht[e]=he(e)});var vt=function bt(){return bt};vt.isRequired=vt;var mt={array:vt,bool:vt,func:vt,number:vt,object:vt,string:vt,any:vt,arrayOf:vt,element:vt,instanceOf:vt,node:vt,objectOf:vt,oneOf:vt,oneOfType:vt,shape:vt},gt=/\/(?!\/)/g,yt=Object.freeze({only:ve,forEach:me,map:ge,count:ye,toArray:xe}),xt=function(){};xt.prototype=M.prototype;var kt=Q({version:"0.15.1",cloneElement:fe,isValidElement:de,createElement:pe,createFactory:he,Component:M,createClass:Ne,Children:yt,PropTypes:mt,DOM:ht},dt);return kt.__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=dt,kt}); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | var ghPages = require('gulp-gh-pages') 3 | var path = require('path') 4 | 5 | 6 | gulp.task('deploy', ['compile'], function(cb) { 7 | return gulp.src('./_site/**/*') 8 | .pipe(ghPages()) 9 | }) 10 | 11 | gulp.task('compile', ['dist', 'examples']) 12 | 13 | gulp.task('dist', function() { 14 | return gulp.src('./dist/**/*') 15 | .pipe(gulp.dest('./_site/dist')) 16 | }) 17 | 18 | gulp.task('examples', function() { 19 | return gulp.src('./examples/**/*') 20 | .pipe(gulp.dest('./_site/examples')) 21 | }) 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vdom-engine", 3 | "version": "0.1.6", 4 | "description": "virtual-dom engine that help everyone build their own modern view library and user interfaces", 5 | "main": "dist/vdom-engine.common.js", 6 | "jsnext:main": "src/index.js", 7 | "scripts": { 8 | "test": "", 9 | "build:addons": "node_modules/.bin/babel ./addons --out-dir ./lib && babel ./src --out-dir ./lib", 10 | "build": "node build.js", 11 | "deploy": "node_modules/.bin/gulp deploy", 12 | "prepublish": "npm run build" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/Lucifier129/vdom-engine.git" 17 | }, 18 | "keywords": [ 19 | "react", 20 | "vdom-engine", 21 | "component", 22 | "virtual-dom" 23 | ], 24 | "author": "Jade Gu (https://github.com/Lucifier129)", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/Lucifier129/vdom-engine/issues" 28 | }, 29 | "homepage": "https://github.com/Lucifier129/vdom-engine", 30 | "devDependencies": { 31 | "babel": "^5.8.35", 32 | "babel-core": "^5.8.25", 33 | "babel-jest": "^5.3.0", 34 | "babel-loader": "^5.3.2", 35 | "babel-runtime": "^5.8.25", 36 | "gulp": "^3.9.1", 37 | "gulp-gh-pages": "^0.5.4", 38 | "rollup": "^0.21.0", 39 | "rollup-plugin-babel": "^1.0.0", 40 | "rollup-plugin-replace": "^1.1.0", 41 | "uglify-js": "^2.6.1", 42 | "webpack": "^1.12.2" 43 | }, 44 | "npmName": "vdom-engine", 45 | "npmFileMap": [ 46 | { 47 | "basePath": "/dist/", 48 | "files": [ 49 | "*.js" 50 | ] 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /src/CSSPropertyOperations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS Property Operations 3 | */ 4 | 5 | export let styleDirective = { 6 | attach: attachStyle, 7 | detach: detachStyle 8 | } 9 | 10 | function attachStyle(elem, styleName, styleValue) { 11 | setStyleValue(elem.style, styleName, styleValue) 12 | } 13 | 14 | function detachStyle(elem, styleName) { 15 | elem.style[styleName] = '' 16 | } 17 | 18 | /** 19 | * CSS properties which accept numbers but are not in units of "px". 20 | */ 21 | const isUnitlessNumber = { 22 | animationIterationCount: 1, 23 | borderImageOutset: 1, 24 | borderImageSlice: 1, 25 | borderImageWidth: 1, 26 | boxFlex: 1, 27 | boxFlexGroup: 1, 28 | boxOrdinalGroup: 1, 29 | columnCount: 1, 30 | flex: 1, 31 | flexGrow: 1, 32 | flexPositive: 1, 33 | flexShrink: 1, 34 | flexNegative: 1, 35 | flexOrder: 1, 36 | gridRow: 1, 37 | gridColumn: 1, 38 | fontWeight: 1, 39 | lineClamp: 1, 40 | lineHeight: 1, 41 | opacity: 1, 42 | order: 1, 43 | orphans: 1, 44 | tabSize: 1, 45 | widows: 1, 46 | zIndex: 1, 47 | zoom: 1, 48 | 49 | // SVG-related properties 50 | fillOpacity: 1, 51 | floodOpacity: 1, 52 | stopOpacity: 1, 53 | strokeDasharray: 1, 54 | strokeDashoffset: 1, 55 | strokeMiterlimit: 1, 56 | strokeOpacity: 1, 57 | strokeWidth: 1, 58 | } 59 | 60 | function prefixKey(prefix, key) { 61 | return prefix + key.charAt(0).toUpperCase() + key.substring(1) 62 | } 63 | 64 | let prefixes = ['Webkit', 'ms', 'Moz', 'O'] 65 | 66 | Object.keys(isUnitlessNumber).forEach(function(prop) { 67 | prefixes.forEach(function(prefix) { 68 | isUnitlessNumber[prefixKey(prefix, prop)] = 1 69 | }) 70 | }) 71 | 72 | let RE_NUMBER = /^-?\d+(\.\d+)?$/ 73 | function setStyleValue(elemStyle, styleName, styleValue) { 74 | 75 | if (!isUnitlessNumber[styleName] && RE_NUMBER.test(styleValue)) { 76 | elemStyle[styleName] = styleValue + 'px' 77 | return 78 | } 79 | 80 | if (styleName === 'float') { 81 | styleName = 'cssFloat' 82 | } 83 | 84 | if (styleValue == null || typeof styleValue === 'boolean') { 85 | styleValue = '' 86 | } 87 | 88 | elemStyle[styleName] = styleValue 89 | } -------------------------------------------------------------------------------- /src/DOMPropertyOperations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DOM Property Operations 3 | */ 4 | export let DOMPropDirective = { 5 | attach: attachDOMProp, 6 | detach: detachDOMProp 7 | } 8 | 9 | export let DOMAttrDirective = { 10 | attach: attachDOMAttr, 11 | detach: detachDOMAttr 12 | } 13 | 14 | function attachDOMProp(elem, propName, propValue) { 15 | elem[propName] = propValue 16 | } 17 | 18 | function detachDOMProp(elem, propName) { 19 | elem[propName] = '' 20 | } 21 | 22 | function attachDOMAttr(elem, attrName, attrValue) { 23 | elem.setAttribute(attrName, attrValue + '') 24 | } 25 | 26 | function detachDOMAttr(elem, attrName) { 27 | elem.removeAttribute(attrName) 28 | } -------------------------------------------------------------------------------- /src/constant.js: -------------------------------------------------------------------------------- 1 | /* 2 | key/value configs 3 | */ 4 | export const SVGNamespaceURI = 'http://www.w3.org/2000/svg' 5 | export const COMPONENT_ID = 'liteid' 6 | export const VELEMENT = 1 7 | export const VSTATELESS = 2 8 | export const VCOMMENT = 3 9 | export const HTML_KEY = 'prop-innerHTML' 10 | export const HOOK_WILL_MOUNT = 'hook-willMount' 11 | export const HOOK_DID_MOUNT = 'hook-didMount' 12 | export const HOOK_WILL_UPDATE = 'hook-willUpdate' 13 | export const HOOK_DID_UPDATE = 'hook-didUpdate' 14 | export const HOOK_WILL_UNMOUNT = 'hook-willUnmount' -------------------------------------------------------------------------------- /src/createElement.js: -------------------------------------------------------------------------------- 1 | import { VELEMENT, VSTATELESS } from './constant' 2 | import * as _ from './util' 3 | 4 | export function createElement(type, props, /* ...children */) { 5 | let finalProps = {} 6 | let key = null 7 | if (props != null) { 8 | for (let propKey in props) { 9 | if (propKey === 'key') { 10 | if (props.key !== undefined) { 11 | key = '' + props.key 12 | } 13 | } else { 14 | finalProps[propKey] = props[propKey] 15 | } 16 | } 17 | } 18 | 19 | let defaultProps = type.defaultProps 20 | if (defaultProps) { 21 | for (let propKey in defaultProps) { 22 | if (finalProps[propKey] === undefined) { 23 | finalProps[propKey] = defaultProps[propKey] 24 | } 25 | } 26 | } 27 | 28 | let argsLen = arguments.length 29 | let finalChildren = [] 30 | 31 | if (argsLen > 2) { 32 | for (let i = 2; i < argsLen; i++) { 33 | let child = arguments[i] 34 | if (_.isArr(child)) { 35 | _.flatEach(child, collectChild, finalChildren) 36 | } else { 37 | collectChild(child, finalChildren) 38 | } 39 | } 40 | } 41 | 42 | finalProps.children = finalChildren 43 | 44 | let vtype = null 45 | if (typeof type === 'string') { 46 | vtype = VELEMENT 47 | } else if (typeof type === 'function') { 48 | vtype = VSTATELESS 49 | } else { 50 | throw new Error(`unexpect type [ ${type} ]`) 51 | } 52 | 53 | let vnode = { 54 | vtype: vtype, 55 | type: type, 56 | props: finalProps, 57 | key: key, 58 | } 59 | if (vtype === VSTATELESS) { 60 | vnode.uid = _.getUid() 61 | } 62 | 63 | return vnode 64 | } 65 | 66 | export function isValidElement(obj) { 67 | return obj != null && !!obj.vtype 68 | } 69 | 70 | export function createFactory(type) { 71 | let factory = (...args) => createElement(type, ...args) 72 | factory.type = type 73 | return factory 74 | } 75 | 76 | function collectChild(child, children) { 77 | if (child != null && typeof child !== 'boolean') { 78 | children[children.length] = child.vtype ? child : '' + child 79 | } 80 | } -------------------------------------------------------------------------------- /src/directive.js: -------------------------------------------------------------------------------- 1 | // directive store 2 | let directives = {} 3 | let DIRECTIVE_SPEC = /^([^-]+)-(.+)$/ 4 | 5 | export function addDirective(name, configs) { 6 | directives[name] = configs 7 | } 8 | 9 | export function removeDirective(name) { 10 | delete directives[name] 11 | } 12 | 13 | let currentName = null 14 | function matchDirective(propKey) { 15 | let matches = propKey.match(DIRECTIVE_SPEC) 16 | if (matches) { 17 | currentName = matches[2] 18 | return directives[matches[1]] 19 | } 20 | } 21 | 22 | function attachProp(elem, propKey, propValue) { 23 | let directive = matchDirective(propKey) 24 | if (directive) { 25 | directive.attach(elem, currentName, propValue) 26 | } 27 | } 28 | 29 | function detachProp(elem, propKey) { 30 | let directive = matchDirective(propKey) 31 | if (directive) { 32 | directive.detach(elem, currentName) 33 | } 34 | } 35 | 36 | 37 | export function attachProps(elem, props) { 38 | for (let propKey in props) { 39 | if (propKey !== 'children' && props[propKey] != null) { 40 | attachProp(elem, propKey, props[propKey]) 41 | } 42 | } 43 | } 44 | 45 | export function patchProps(elem, props, newProps) { 46 | for (let propKey in props) { 47 | if (propKey === 'children') { 48 | continue 49 | } 50 | let newValue = newProps[propKey] 51 | if (newValue !== props[propKey]) { 52 | if (newValue == null) { 53 | detachProp(elem, propKey) 54 | } else { 55 | attachProp(elem, propKey, newValue) 56 | } 57 | } 58 | } 59 | for (let propKey in newProps) { 60 | if (propKey === 'children') { 61 | continue 62 | } 63 | if (!(propKey in props)) { 64 | attachProp(elem, propKey, newProps[propKey]) 65 | } 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /src/event-system.js: -------------------------------------------------------------------------------- 1 | import * as _ from './util' 2 | // event config 3 | const notBubbleEvents = { 4 | onmouseleave: 1, 5 | onmouseenter: 1, 6 | onload: 1, 7 | onunload: 1, 8 | onscroll: 1, 9 | onfocus: 1, 10 | onblur: 1, 11 | onrowexit: 1, 12 | onbeforeunload: 1, 13 | onstop: 1, 14 | ondragdrop: 1, 15 | ondragenter: 1, 16 | ondragexit: 1, 17 | ondraggesture: 1, 18 | ondragover: 1, 19 | oncontextmenu: 1 20 | } 21 | 22 | export function detachEvents(node, props) { 23 | node.eventStore = null 24 | for (let key in props) { 25 | // key start with 'on-' 26 | if (key.indexOf('on-') === 0) { 27 | key = getEventName(key) 28 | if (notBubbleEvents[key]) { 29 | node[key] = null 30 | } 31 | } 32 | } 33 | } 34 | 35 | export let eventDirective = { 36 | attach: attachEvent, 37 | detach: detachEvent 38 | } 39 | 40 | // Mobile Safari does not fire properly bubble click events on 41 | // non-interactive elements, which means delegated click listeners do not 42 | // fire. The workaround for this bug involves attaching an empty click 43 | // listener on the target node. 44 | let inMobile = 'ontouchstart' in document 45 | let emptyFunction = () => {} 46 | let ON_CLICK_KEY = 'onclick' 47 | 48 | function getEventName(key) { 49 | return key.replace(/^on-/, 'on').toLowerCase() 50 | } 51 | 52 | let eventTypes = {} 53 | function attachEvent(elem, eventType, listener) { 54 | eventType = 'on' + eventType 55 | 56 | if (notBubbleEvents[eventType] === 1) { 57 | elem[eventType] = listener 58 | return 59 | } 60 | 61 | let eventStore = elem.eventStore || (elem.eventStore = {}) 62 | eventStore[eventType] = listener 63 | 64 | if (!eventTypes[eventType]) { 65 | // onclick -> click 66 | document.addEventListener(eventType.substr(2), dispatchEvent, false) 67 | eventTypes[eventType] = true 68 | } 69 | 70 | if (inMobile && eventType === ON_CLICK_KEY) { 71 | elem.addEventListener('click', emptyFunction, false) 72 | } 73 | 74 | let nodeName = elem.nodeName 75 | 76 | if (eventType === 'onchange' && (nodeName === 'INPUT' || nodeName === 'TEXTAREA')) { 77 | attachEvent(elem, 'oninput', listener) 78 | } 79 | } 80 | 81 | function detachEvent(elem, eventType) { 82 | eventType = 'on' + eventType 83 | if (notBubbleEvents[eventType] === 1) { 84 | elem[eventType] = null 85 | return 86 | } 87 | 88 | let eventStore = elem.eventStore || (elem.eventStore = {}) 89 | delete eventStore[eventType] 90 | 91 | if (inMobile && eventType === ON_CLICK_KEY) { 92 | elem.removeEventListener('click', emptyFunction, false) 93 | } 94 | 95 | let nodeName = elem.nodeName 96 | 97 | if (eventType === 'onchange' && (nodeName === 'INPUT' || nodeName === 'TEXTAREA')) { 98 | delete eventStore['oninput'] 99 | } 100 | } 101 | 102 | function dispatchEvent(event) { 103 | let { target, type } = event 104 | let eventType = 'on' + type 105 | let syntheticEvent = null 106 | while (target) { 107 | let { eventStore } = target 108 | let listener = eventStore && eventStore[eventType] 109 | if (!listener) { 110 | target = target.parentNode 111 | continue 112 | } 113 | if (!syntheticEvent) { 114 | syntheticEvent = createSyntheticEvent(event) 115 | } 116 | syntheticEvent.currentTarget = target 117 | listener.call(target, syntheticEvent) 118 | if (syntheticEvent.$cancalBubble) { 119 | break 120 | } 121 | target = target.parentNode 122 | } 123 | } 124 | 125 | function createSyntheticEvent(nativeEvent) { 126 | let syntheticEvent = {} 127 | let cancalBubble = () => syntheticEvent.$cancalBubble = true 128 | syntheticEvent.nativeEvent = nativeEvent 129 | syntheticEvent.persist = _.noop 130 | for (let key in nativeEvent) { 131 | if (typeof nativeEvent[key] !== 'function') { 132 | syntheticEvent[key] = nativeEvent[key] 133 | } else if (key === 'stopPropagation' || key === 'stopImmediatePropagation') { 134 | syntheticEvent[key] = cancalBubble 135 | } else { 136 | syntheticEvent[key] = nativeEvent[key].bind(nativeEvent) 137 | } 138 | } 139 | return syntheticEvent 140 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // placeholder 2 | import * as _ from './util' 3 | import { createElement, createFactory, isValidElement } from './createElement' 4 | import { addDirective, removeDirective } from './directive' 5 | import { render, destroy } from './render' 6 | import { DOMAttrDirective, DOMPropDirective } from './DOMPropertyOperations' 7 | import { styleDirective } from './CSSPropertyOperations' 8 | import { eventDirective } from './event-system' 9 | 10 | addDirective('attr', DOMAttrDirective) 11 | addDirective('prop', DOMPropDirective) 12 | addDirective('on', eventDirective) 13 | addDirective('css', styleDirective) 14 | 15 | const Vengine = { 16 | createElement, 17 | createFactory, 18 | isValidElement, 19 | addDirective, 20 | removeDirective, 21 | render, 22 | destroy 23 | } 24 | 25 | export default Vengine -------------------------------------------------------------------------------- /src/render.js: -------------------------------------------------------------------------------- 1 | import * as _ from './util' 2 | import { COMPONENT_ID } from './constant' 3 | import { 4 | initVnode, 5 | destroyVnode, 6 | compareTwoVnodes, 7 | clearPendingMount 8 | } from './virtual-dom' 9 | 10 | let pendingRendering = {} 11 | let vnodeStore = {} 12 | export function render(vnode, container, context, callback) { 13 | if (!vnode.vtype) { 14 | throw new Error(`cannot render ${ vnode } to container`) 15 | } 16 | let id = container[COMPONENT_ID] || (container[COMPONENT_ID] = _.getUid()) 17 | let argsCache = pendingRendering[id] 18 | 19 | if (_.isFn(context)) { 20 | callback = context 21 | context = undefined 22 | } 23 | 24 | // component lify cycle method maybe call root rendering 25 | // should bundle them and render by only one time 26 | if (argsCache) { 27 | if (argsCache === true) { 28 | pendingRendering[id] = { 29 | vnode: vnode, 30 | context: context, 31 | callback: callback 32 | } 33 | } else { 34 | argsCache.vnode = vnode 35 | argsCache.context = context 36 | if (callback) { 37 | argsCache.callback = argsCache.callback ? _.pipe(argsCache.callback, callback) : callback 38 | } 39 | } 40 | return 41 | } 42 | 43 | pendingRendering[id] = true 44 | if (vnodeStore.hasOwnProperty(id)) { 45 | compareTwoVnodes(vnodeStore[id], vnode, container.firstChild, context) 46 | } else { 47 | var rootNode = initVnode(vnode, context, container.namespaceURI) 48 | var childNode = null 49 | while (childNode = container.lastChild) { 50 | container.removeChild(childNode) 51 | } 52 | container.appendChild(rootNode) 53 | } 54 | vnodeStore[id] = vnode 55 | clearPendingMount() 56 | 57 | argsCache = pendingRendering[id] 58 | pendingRendering[id] = null 59 | 60 | if (typeof argsCache === 'object') { 61 | render(argsCache.vnode, container, argsCache.context, argsCache.callback) 62 | } 63 | 64 | if (callback) { 65 | callback() 66 | } 67 | 68 | } 69 | 70 | export function destroy(container) { 71 | if (!container.nodeName) { 72 | throw new Error('expect node') 73 | } 74 | let id = container[COMPONENT_ID] 75 | let vnode = null 76 | if (vnode = vnodeStore[id]) { 77 | destroyVnode(vnode, container.firstChild) 78 | container.removeChild(container.firstChild) 79 | delete vnodeStore[id] 80 | delete pendingRendering[id] 81 | return true 82 | } 83 | return false 84 | } -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | // util 2 | 3 | export { attachProps, patchProps } from './directive' 4 | 5 | export function isFn(obj) { 6 | return typeof obj === 'function' 7 | } 8 | 9 | export let isArr = Array.isArray 10 | 11 | export function noop() {} 12 | export function identity(obj) { 13 | return obj 14 | } 15 | export function pipe(fn1, fn2) { 16 | return function() { 17 | fn1.apply(this, arguments) 18 | return fn2.apply(this, arguments) 19 | } 20 | } 21 | 22 | export function flatEach(list, iteratee, a) { 23 | let len = list.length 24 | let i = -1 25 | 26 | while (len--) { 27 | let item = list[++i] 28 | if (isArr(item)) { 29 | flatEach(item, iteratee, a) 30 | } else { 31 | iteratee(item, a) 32 | } 33 | } 34 | } 35 | 36 | export function addItem(list, item) { 37 | list[list.length] = item 38 | } 39 | 40 | 41 | export function extend(to, from) { 42 | if (!from) { 43 | return to 44 | } 45 | var keys = Object.keys(from) 46 | var i = keys.length 47 | while (i--) { 48 | to[keys[i]] = from[keys[i]] 49 | } 50 | return to 51 | } 52 | 53 | 54 | let uid = 0 55 | export function getUid() { 56 | return ++uid 57 | } 58 | 59 | if (!Object.freeze) { 60 | Object.freeze = identity 61 | } -------------------------------------------------------------------------------- /src/virtual-dom.js: -------------------------------------------------------------------------------- 1 | import { detachEvents } from './event-system' 2 | import * as _ from './util' 3 | import { 4 | SVGNamespaceURI, 5 | VELEMENT, 6 | VSTATELESS, 7 | VCOMMENT, 8 | HTML_KEY, 9 | HOOK_WILL_MOUNT, 10 | HOOK_DID_MOUNT, 11 | HOOK_WILL_UPDATE, 12 | HOOK_DID_UPDATE, 13 | HOOK_WILL_UNMOUNT 14 | } from './constant' 15 | 16 | export function initVnode(vnode, context, namespaceURI) { 17 | let { vtype } = vnode 18 | let node = null 19 | if (!vtype) { // init text 20 | node = document.createTextNode(vnode) 21 | } else if (vtype === VELEMENT) { // init element 22 | node = initVelem(vnode, context, namespaceURI) 23 | } else if (vtype === VSTATELESS) { // init stateless component 24 | node = initVstateless(vnode, context, namespaceURI) 25 | } else if (vtype === VCOMMENT) { // init comment 26 | node = document.createComment(`react-empty: ${ vnode.uid }`) 27 | } 28 | return node 29 | } 30 | 31 | function updateVnode(vnode, newVnode, node, context) { 32 | let { vtype } = vnode 33 | 34 | if (vtype === VSTATELESS) { 35 | return updateVstateless(vnode, newVnode, node, context) 36 | } 37 | 38 | // ignore VCOMMENT and other vtypes 39 | if (vtype !== VELEMENT) { 40 | return node 41 | } 42 | 43 | if (vnode.props[HTML_KEY] != null) { 44 | updateVelem(vnode, newVnode, node, context) 45 | initVchildren(newVnode, node, context) 46 | } else { 47 | updateVChildren(vnode, newVnode, node, context) 48 | updateVelem(vnode, newVnode, node, context) 49 | } 50 | return node 51 | } 52 | 53 | function updateVChildren(vnode, newVnode, node, context) { 54 | let patches = { 55 | removes: [], 56 | updates: [], 57 | creates: [], 58 | } 59 | // console.time('time') 60 | diffVchildren(patches, vnode, newVnode, node, context) 61 | 62 | _.flatEach(patches.removes, applyDestroy) 63 | 64 | _.flatEach(patches.updates, applyUpdate) 65 | 66 | _.flatEach(patches.creates, applyCreate) 67 | // console.timeEnd('time') 68 | } 69 | 70 | 71 | function applyUpdate(data) { 72 | if (!data) { 73 | return 74 | } 75 | let node = data.node 76 | 77 | // update 78 | if (data.shouldUpdate) { 79 | let { vnode, newVnode, context } = data 80 | if (!vnode.vtype) { 81 | node.nodeValue = newVnode 82 | } else if (vnode.vtype === VELEMENT) { 83 | updateVelem(vnode, newVnode, node, context) 84 | } else if (vnode.vtype === VSTATELESS) { 85 | node = updateVstateless(vnode, newVnode, node, context) 86 | } 87 | } 88 | 89 | // re-order 90 | if (data.index !== data.fromIndex) { 91 | let existNode = node.parentNode.childNodes[index] 92 | if (existNode !== node) { 93 | node.parentNode.insertBefore(node, existNode) 94 | } 95 | } 96 | } 97 | 98 | function applyDestroy(data) { 99 | destroyVnode(data.vnode, data.node) 100 | data.node.parentNode.removeChild(data.node) 101 | } 102 | 103 | function applyCreate(data) { 104 | let parentNode = data.parentNode 105 | let existNode = parentNode.childNodes[data.index] 106 | let node = initVnode(data.vnode, data.context, parentNode.namespaceURI) 107 | parentNode.insertBefore(node, existNode) 108 | } 109 | 110 | 111 | /** 112 | * Only vnode which has props.children need to call destroy function 113 | * to check whether subTree has component that need to call lify-cycle method and release cache. 114 | */ 115 | export function destroyVnode(vnode, node) { 116 | let { vtype } = vnode 117 | if (vtype === VELEMENT) { // destroy element 118 | destroyVelem(vnode, node) 119 | } else if (vtype === VSTATELESS) { // destroy stateless component 120 | destroyVstateless(vnode, node) 121 | } 122 | } 123 | 124 | function initVelem(velem, context, namespaceURI) { 125 | let { type, props } = velem 126 | let node = null 127 | 128 | if (type === 'svg' || namespaceURI === SVGNamespaceURI) { 129 | node = document.createElementNS(SVGNamespaceURI, type) 130 | namespaceURI = SVGNamespaceURI 131 | } else { 132 | node = document.createElement(type) 133 | } 134 | 135 | initVchildren(node, props.children, context) 136 | _.attachProps(node, props) 137 | 138 | if (props[HOOK_WILL_MOUNT]) { 139 | props[HOOK_WILL_MOUNT].call(null, node, props) 140 | } 141 | 142 | if (props[HOOK_DID_MOUNT]) { 143 | _.addItem(pendingHooks, { 144 | type: HOOK_DID_MOUNT, 145 | node: node, 146 | props: props, 147 | }) 148 | } 149 | 150 | return node 151 | } 152 | 153 | function initVchildren(node, vchildren, context) { 154 | let { namespaceURI } = node 155 | for (let i = 0, len = vchildren.length; i < len; i++) { 156 | node.appendChild(initVnode(vchildren[i], context, namespaceURI)) 157 | } 158 | } 159 | 160 | function diffVchildren(patches, vnode, newVnode, node, context) { 161 | let { childNodes } = node 162 | let vchildren = vnode.props.children 163 | let newVchildren = newVnode.props.children 164 | let vchildrenLen = vchildren.length 165 | let newVchildrenLen = newVchildren.length 166 | 167 | if (vchildrenLen === 0) { 168 | if (newVchildrenLen === 0) { 169 | return 170 | } 171 | for (let i = 0; i < newVchildrenLen; i++) { 172 | _.addItem(patches.creates, { 173 | vnode: newVchildren[i], 174 | parentNode: node, 175 | context: context, 176 | index: i, 177 | }) 178 | } 179 | return 180 | } else if (newVchildrenLen === 0) { 181 | for (let i = 0; i < vchildrenLen; i++) { 182 | _.addItem(patches.removes, { 183 | vnode: vchildren[i], 184 | node: childNodes[i], 185 | }) 186 | } 187 | return 188 | } 189 | 190 | let matches = {} 191 | let updates = Array(newVchildrenLen) 192 | let removes = null 193 | let creates = null 194 | 195 | // isEqual 196 | for (let i = 0; i < vchildrenLen; i++) { 197 | let vnode = vchildren[i] 198 | for (let j = 0; j < newVchildrenLen; j++) { 199 | if (updates[j]) { 200 | continue 201 | } 202 | let newVnode = newVchildren[j] 203 | if (vnode === newVnode) { 204 | let shouldUpdate = false 205 | if (context) { 206 | if (vnode.vtype === VSTATELESS) { 207 | /** 208 | * stateless component: (props, context) =>
209 | * if context argument is specified and context is exist, should re-render 210 | */ 211 | if (vnode.type.length > 1) { 212 | shouldUpdate = true 213 | } 214 | } 215 | } 216 | updates[j] = { 217 | shouldUpdate: shouldUpdate, 218 | vnode: vnode, 219 | newVnode: newVnode, 220 | node: childNodes[i], 221 | context: context, 222 | index: j, 223 | fromIndex: i, 224 | } 225 | matches[i] = true 226 | break 227 | } 228 | } 229 | } 230 | 231 | // isSimilar 232 | for (let i = 0; i < vchildrenLen; i++) { 233 | if (matches[i]) { 234 | continue 235 | } 236 | let vnode = vchildren[i] 237 | let shouldRemove = true 238 | for (let j = 0; j < newVchildrenLen; j++) { 239 | if (updates[j]) { 240 | continue 241 | } 242 | let newVnode = newVchildren[j] 243 | if ( 244 | newVnode.type === vnode.type && 245 | newVnode.key === vnode.key 246 | ) { 247 | updates[j] = { 248 | shouldUpdate: true, 249 | vnode: vnode, 250 | newVnode: newVnode, 251 | node: childNodes[i], 252 | context: context, 253 | index: j, 254 | fromIndex: i, 255 | } 256 | shouldRemove = false 257 | break 258 | } 259 | } 260 | if (shouldRemove) { 261 | if (!removes) { 262 | removes = [] 263 | } 264 | _.addItem(removes, { 265 | vnode: vnode, 266 | node: childNodes[i] 267 | }) 268 | } 269 | } 270 | 271 | for (let i = 0; i < newVchildrenLen; i++) { 272 | let item = updates[i] 273 | if (!item) { 274 | if (!creates) { 275 | creates = [] 276 | } 277 | _.addItem(creates, { 278 | vnode: newVchildren[i], 279 | parentNode: node, 280 | context: context, 281 | index: i, 282 | }) 283 | } else if (item.vnode.vtype === VELEMENT) { 284 | diffVchildren(patches, item.vnode, item.newVnode, item.node, item.context) 285 | } 286 | } 287 | 288 | if (removes) { 289 | _.addItem(patches.removes, removes) 290 | } 291 | if (creates) { 292 | _.addItem(patches.creates, creates) 293 | } 294 | _.addItem(patches.updates, updates) 295 | } 296 | 297 | function updateVelem(velem, newVelem, node) { 298 | let newProps = newVelem.props 299 | if (newProps[HOOK_WILL_UPDATE]) { 300 | newProps[HOOK_WILL_UPDATE].call(null, node, newProps) 301 | } 302 | _.patchProps(node, velem.props, newProps) 303 | if (newProps[HOOK_DID_UPDATE]) { 304 | newProps[HOOK_DID_UPDATE].call(null, node, newProps) 305 | } 306 | return node 307 | } 308 | 309 | function destroyVelem(velem, node) { 310 | let { props } = velem 311 | let vchildren = props.children 312 | let childNodes = node.childNodes 313 | 314 | for (let i = 0, len = vchildren.length; i < len; i++) { 315 | destroyVnode(vchildren[i], childNodes[i]) 316 | } 317 | 318 | if (_.isFn(props[HOOK_WILL_UNMOUNT])) { 319 | props[HOOK_WILL_UNMOUNT].call(null, node, props) 320 | } 321 | 322 | detachEvents(node, props) 323 | } 324 | 325 | function initVstateless(vstateless, context, namespaceURI) { 326 | let vnode = renderVstateless(vstateless, context) 327 | let node = initVnode(vnode, context, namespaceURI) 328 | node.cache = node.cache || {} 329 | node.cache[vstateless.uid] = vnode 330 | return node 331 | } 332 | 333 | function updateVstateless(vstateless, newVstateless, node, context) { 334 | let uid = vstateless.uid 335 | let vnode = node.cache[uid] 336 | delete node.cache[uid] 337 | let newVnode = renderVstateless(newVstateless, context) 338 | let newNode = compareTwoVnodes(vnode, newVnode, node, context) 339 | newNode.cache = newNode.cache || {} 340 | newNode.cache[newVstateless.uid] = newVnode 341 | if (newNode !== node) { 342 | _.extend(newNode.cache, node.cache) 343 | } 344 | return newNode 345 | } 346 | 347 | function destroyVstateless(vstateless, node) { 348 | let uid = vstateless.uid 349 | let vnode = node.cache[uid] 350 | delete node.cache[uid] 351 | destroyVnode(vnode, node) 352 | } 353 | 354 | function renderVstateless(vstateless, context) { 355 | let { type: factory, props } = vstateless 356 | let vnode = factory(props, context) 357 | if (vnode && vnode.render) { 358 | vnode = vnode.render() 359 | } 360 | if (vnode === null || vnode === false) { 361 | vnode = { 362 | vtype: VCOMMENT, 363 | uid: _.getUid(), 364 | } 365 | } else if (!vnode || !vnode.vtype) { 366 | throw new Error(`@${factory.name}#render:You may have returned undefined, an array or some other invalid object`) 367 | } 368 | return vnode 369 | } 370 | 371 | 372 | let pendingHooks = [] 373 | export let clearPendingMount = () => { 374 | let len = pendingHooks.length 375 | if (!len) { 376 | return 377 | } 378 | let list = pendingHooks 379 | let i = -1 380 | while (len--) { 381 | let item = list[++i] 382 | item.props[item.type].call(null, item.node, item.props) 383 | } 384 | pendingHooks.length = 0 385 | } 386 | 387 | export function compareTwoVnodes(vnode, newVnode, node, context) { 388 | let newNode = node 389 | if (newVnode == null) { 390 | // remove 391 | destroyVnode(vnode, node) 392 | node.parentNode.removeChild(node) 393 | } else if (vnode.type !== newVnode.type || vnode.key !== newVnode.key) { 394 | // replace 395 | destroyVnode(vnode, node) 396 | newNode = initVnode(newVnode, context, node.namespaceURI) 397 | node.parentNode.replaceChild(newNode, node) 398 | } else if (vnode !== newVnode || context) { 399 | // same type and same key -> update 400 | newNode = updateVnode(vnode, newVnode, node, context) 401 | } 402 | return newNode 403 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | watch: true, 6 | entry: { 7 | simple: './examples/simple/' 8 | }, 9 | output: { 10 | path: './examples/', 11 | filename: '[name]/app.js' 12 | }, 13 | module: { 14 | loaders: [{ 15 | test: /\.jsx?$/, 16 | loader: 'babel-loader', 17 | query: { 18 | stage: 0 19 | }, 20 | exclude: /node_modules/ 21 | }] 22 | }, 23 | resolve: { 24 | extensions: ['', '.js'], 25 | root: __dirname 26 | } 27 | }; 28 | --------------------------------------------------------------------------------