├── .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 | Increment if odd
11 | {' '}
12 | Increment async
13 | {' '}
14 | Increment by dblclick
15 | {' '}
16 | Decrement by mousemove
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 |
30 | {database.dbname}
31 |
32 |
33 |
34 | {database.lastSample.queries.length}
35 |
36 |
37 | {
38 | database.lastSample.topFiveQueries.map(function(query, index) {
39 | return (
40 |
41 | {query.formatElapsed}
42 |
43 |
{query.query}
44 |
45 |
46 |
47 | );
48 | })
49 | }
50 |
51 | );
52 | })
53 | }
54 |
55 |
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 |
15 | {database.dbname}
16 |
17 |
18 |
19 | {database.lastSample.queries.length}
20 |
21 |
22 | {
23 | database.lastSample.topFiveQueries.map(function(query, index) {
24 | return (
25 |
26 | {query.formatElapsed}
27 |
28 |
{query.query}
29 |
30 |
31 |
32 | );
33 | })
34 | }
35 |
36 | );
37 | })
38 | }
39 |
40 |
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 |
--------------------------------------------------------------------------------