├── .babelrc
├── .eslintrc
├── .flowconfig
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── example
├── app.js
├── index.html
├── main.js
├── package.json
└── rollup.config.js
├── northbrook.json
├── package.json
├── perf
└── flow-dom
│ ├── .babelrc
│ ├── dist
│ ├── index.html
│ └── main.js
│ ├── package.json
│ ├── rollup.config.js
│ └── src
│ └── index.js
└── src
├── hyperscript
├── VNode.js
└── h.js
├── index.js
├── interfaces.js
├── module
└── properties.js
└── util.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "transform-flow-strip-types",
4 | "transform-object-rest-spread",
5 | ["module-resolver", {
6 | }]
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "standard",
3 | "parser": "babel-eslint",
4 | "plugins": [
5 | "flowtype",
6 | "import",
7 | "immutable"
8 | ],
9 | "rules": {
10 | "import/default": 2,
11 | "import/export": 2,
12 | "import/extensions": [2, {
13 | "js": "never",
14 | "json": "always"
15 | }],
16 | "import/imports-first": 2,
17 | "import/no-amd": 2,
18 | "import/no-deprecated": 1,
19 | "import/no-duplicates": 2,
20 | "import/no-extraneous-dependencies": [2, {"devDependencies": false, "optionalDependencies": false, "peerDependencies": false}],
21 | "import/no-mutable-exports": 2,
22 | "import/no-named-as-default-member": 2,
23 | "import/no-named-as-default": 2,
24 | "import/no-unresolved": 2,
25 | "flowtype/define-flow-type": 1,
26 | "flowtype/require-parameter-type": [1, { excludeArrowFunctions: true }],
27 | "flowtype/require-return-type": [
28 | 1,
29 | "always",
30 | {
31 | "annotateUndefined": "never"
32 | }
33 | ],
34 | "flowtype/space-after-type-colon": [
35 | 2,
36 | "always"
37 | ],
38 | "flowtype/space-before-type-colon": [
39 | 2,
40 | "never"
41 | ],
42 | "flowtype/use-flow-type": 1,
43 | "flowtype/valid-syntax": 2,
44 | "immutable/no-let": 2,
45 | },
46 | "settings": {
47 | "flowtype": {
48 | "onlyFilesWithFlowAnnotation": true
49 | },
50 | "import/resolver": {
51 | "babel-module": {
52 |
53 | }
54 | },
55 | "import/ignore": [
56 | 'node_modules',
57 | '.(scss|sass|css)$'
58 | ]
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | node_modules/
3 | lib/
4 | perf/
5 | example/
6 |
7 | [include]
8 | src/
9 |
10 | [libs]
11 |
12 | [options]
13 | unsafe.enable_getters_and_setters=true
14 | suppress_comment=\\(.\\|\n\\)*\\$flow-ignore-line
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | lib/
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TylorS/flow-dom/10116b69b8ff58b369d1c88c7b588537f6b210e1/.npmignore
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # v0.1.3 (2016-08-29)
2 | ---
3 |
4 |
5 | ## Bug Fixes
6 |
7 | - fix(flow-dom): fix all the things and make some perf test and an exampley [65206e9c](https://github.com/TylorS/stream-flow/commits/65206e9c03f51849adeb0f9bf1537b3a28ab4c90)
8 |
9 |
10 | # v0.1.2 (2016-08-28)
11 | ---
12 |
13 |
14 | ## Bug Fixes
15 |
16 | - fix(flow-dom): remove @most/prelude [7ffad2be](https://github.com/TylorS/stream-flow/commits/7ffad2beeff648ca03e7e1e8e331cf8b90546841)
17 |
18 |
19 | # v0.1.1 (2016-08-28)
20 | ---
21 |
22 |
23 | ## Bug Fixes
24 |
25 | - fix(flow-dom): add main field to package [01a46f26](https://github.com/TylorS/stream-flow/commits/01a46f26e93db12c476b0cb9308bf3f3d28c924b)
26 |
27 |
28 | # v0.1.0 (2016-08-28)
29 | ---
30 |
31 |
32 | ## Features
33 |
34 | - feat(flow-dom): add insert hook to types [4aff342d](https://github.com/TylorS/stream-flow/commits/4aff342d850635750aa016507753b67d22eb43df)
35 |
36 | ## Bug Fixes
37 |
38 | - fix(flow-dom): export h [2fd72665](https://github.com/TylorS/stream-flow/commits/2fd72665f0fd18894b7a57d219c8c3e2100c2344)
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2016 Tylor Steinberger
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7 |
8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9 |
10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flow DOM
2 |
3 | > An experiment to create an immutable, mutation-free, virtual-dom
4 |
5 | I succeed ;P
6 |
7 | Docs are a maybe lol
8 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Example app
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/example/main.js:
--------------------------------------------------------------------------------
1 | const { init, h } = require('../src/index')
2 | const { fromEvent } = require('most')
3 |
4 | const patch = init([])
5 |
6 | function view (clicks) {
7 | return h('div.flow', {}, [
8 | h('button.btn', {}, 'Click me'),
9 | h('h1', {}, 'Clicked ' + String(clicks) + ' times!')
10 | ])
11 | }
12 |
13 | const click$ = fromEvent('click', document.body)
14 | .tap((x) => console.log(x))
15 | .filter(ev => ev.target.matches('.btn'))
16 |
17 | const state$ = click$.scan((x) => x + 1, 0).tap(x => console.log(x))
18 |
19 | const view$ = state$.map(view)
20 |
21 | view$.scan(patch, document.querySelector('#app')).drain()
22 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "most.js",
6 | "scripts": {
7 | "build": "rm -rf app.js && rollup -c",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@most/dom-event": "^1.3.0",
14 | "most": "^1.0.1"
15 | },
16 | "devDependencies": {
17 | "babel-plugin-object-rest-spread": "0.0.0",
18 | "babel-plugin-transform-flow-strip-types": "^6.14.0",
19 | "babel-preset-es2015": "^6.14.0",
20 | "rollup": "^0.34.10",
21 | "rollup-plugin-babel": "^2.6.1",
22 | "rollup-plugin-buble": "^0.13.0",
23 | "rollup-plugin-commonjs": "^3.3.1",
24 | "rollup-plugin-node-resolve": "^2.0.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/example/rollup.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import buble from 'rollup-plugin-buble'
3 | import babel from 'rollup-plugin-babel'
4 | import nodeResolve from 'rollup-plugin-node-resolve'
5 | import commonjs from 'rollup-plugin-commonjs'
6 |
7 | export default {
8 | entry: 'main.js',
9 | dest: 'app.js',
10 | format: 'iife',
11 | moduleName: 'FlowDom',
12 | plugins: [
13 | babel(),
14 | buble(),
15 | nodeResolve({
16 | jsnext: false
17 | }),
18 | commonjs()
19 | ],
20 | acorn: {
21 | allowReserved: true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/northbrook.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": ["."],
3 | "plugins": ["eslint"]
4 | }
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flow-dom",
3 | "version": "0.1.3",
4 | "description": "An Immutable Virtual DOM",
5 | "main": "lib/index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/TylorS/stream-flow.git"
9 | },
10 | "author": "Tylor Steinberger (https://github.com/TylorS)",
11 | "license": "MIT",
12 | "bugs": {
13 | "url": "https://github.com/TylorS/stream-flow/issues"
14 | },
15 | "homepage": "https://github.com/TylorS/stream-flow#readme",
16 | "config": {
17 | "ghooks": {
18 | "commit-msg": "node ./node_modules/.bin/validate-commit-msg"
19 | },
20 | "validate-commit-msg": {
21 | "types": "@northbrook/commit-types"
22 | }
23 | },
24 | "scripts": {
25 | "lint": "northbrook eslint",
26 | "build": "buba -i src -o lib",
27 | "commit": "northbrook commit",
28 | "preversion": "npm run build",
29 | "release": "northbrook release"
30 | },
31 | "devDependencies": {
32 | "@northbrook/commit-types": "^1.1.0",
33 | "@northbrook/eslint": "^1.1.1",
34 | "babel-eslint": "^6.1.2",
35 | "babel-plugin-module-resolver": "^2.1.1",
36 | "babel-plugin-transform-flow-strip-types": "^6.14.0",
37 | "babel-plugin-transform-object-rest-spread": "^6.8.0",
38 | "babel-preset-es2015": "^6.14.0",
39 | "buba": "^2.0.3",
40 | "eslint": "^3.4.0",
41 | "eslint-import-resolver-babel-module": "^2.0.1",
42 | "eslint-plugin-flowtype": "^2.11.4",
43 | "eslint-plugin-immutable": "^1.0.0",
44 | "eslint-plugin-import": "^1.14.0",
45 | "flow-bin": "^0.31.1",
46 | "northbrook": "^2.2.6"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/perf/flow-dom/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "transform-flow-strip-types",
4 | "transform-object-rest-spread",
5 | ["module-resolver", {
6 | }]
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/perf/flow-dom/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Virtual DOM Benchmark: Flow-DOM
7 |
8 |
9 | Virtual DOM Benchmark: Flow-DOM
10 | Source code
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/perf/flow-dom/dist/main.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | function interopDefault(ex) {
5 | return ex && typeof ex === 'object' && 'default' in ex ? ex['default'] : ex;
6 | }
7 |
8 | function createCommonjsModule(fn, module) {
9 | return module = { exports: {} }, fn(module, module.exports), module.exports;
10 | }
11 |
12 | var executor = createCommonjsModule(function (module) {
13 | 'use strict';
14 |
15 | function render(nodes) {
16 | var children = [];
17 | var j;
18 | var c;
19 | var i;
20 | var e;
21 | var n;
22 |
23 | for (i = 0; i < nodes.length; i++) {
24 | n = nodes[i];
25 | if (n.children !== null) {
26 | e = document.createElement('div');
27 | c = render(n.children);
28 | for (j = 0; j < c.length; j++) {
29 | e.appendChild(c[j]);
30 | }
31 | children.push(e);
32 | } else {
33 | e = document.createElement('span');
34 | e.textContent = n.key.toString();
35 | children.push(e);
36 | }
37 | }
38 |
39 | return children;
40 | }
41 |
42 | function testInnerHtml(testName, nodes, container) {
43 | var c = document.createElement('div');
44 | var e = document.createElement('div');
45 | var children = render(nodes);
46 | for (var i = 0; i < children.length; i++) {
47 | e.appendChild(children[i]);
48 | }
49 | c.appendChild(e);
50 | if (c.innerHTML !== container.innerHTML) {
51 | console.log('error in test: ' + testName);
52 | console.log('container.innerHTML:');
53 | console.log(container.innerHTML);
54 | console.log('should be:');
55 | console.log(c.innerHTML);
56 | }
57 | }
58 |
59 | function Executor(impl, container, tests, iterations, cb, iterCb, enableTests) {
60 | if (iterCb === void 0) iterCb = null;
61 |
62 | this.impl = impl;
63 | this.container = container;
64 | this.tests = tests;
65 | this.iterations = iterations;
66 | this.cb = cb;
67 | this.iterCb = iterCb;
68 | this.enableTests = enableTests;
69 |
70 | this._currentTest = 0;
71 | this._currentIter = 0;
72 | this._renderSamples = [];
73 | this._updateSamples = [];
74 | this._result = [];
75 |
76 | this._tasksCount = tests.length * iterations;
77 |
78 | this._iter = this.iter.bind(this);
79 | }
80 |
81 | Executor.prototype.start = function () {
82 | this._iter();
83 | };
84 |
85 | Executor.prototype.finished = function () {
86 | this.cb(this._result);
87 | };
88 |
89 | Executor.prototype.progress = function () {
90 | if (this._currentTest === 0 && this._currentIter === 0) {
91 | return 0;
92 | }
93 |
94 | var tests = this.tests;
95 | return (this._currentTest * tests.length + this._currentIter) / (tests.length * this.iterataions);
96 | };
97 |
98 | Executor.prototype.iter = function () {
99 | if (this.iterCb != null) {
100 | this.iterCb(this);
101 | }
102 |
103 | var tests = this.tests;
104 |
105 | if (this._currentTest < tests.length) {
106 | var test = tests[this._currentTest];
107 |
108 | if (this._currentIter < this.iterations) {
109 | var e, t;
110 | var renderTime, updateTime;
111 |
112 | e = new this.impl(this.container, test.data.a, test.data.b);
113 | e.setUp();
114 |
115 | t = window.performance.now();
116 | e.render();
117 | renderTime = window.performance.now() - t;
118 |
119 | if (this.enableTests) {
120 | testInnerHtml(test.name + 'render()', test.data.a, this.container);
121 | }
122 |
123 | t = window.performance.now();
124 | e.update();
125 | updateTime = window.performance.now() - t;
126 |
127 | if (this.enableTests) {
128 | testInnerHtml(test.name + 'update()', test.data.b, this.container);
129 | }
130 |
131 | e.tearDown();
132 |
133 | this._renderSamples.push(renderTime);
134 | this._updateSamples.push(updateTime);
135 |
136 | this._currentIter++;
137 | } else {
138 | this._result.push({
139 | name: test.name + ' ' + 'render()',
140 | data: this._renderSamples.slice(0)
141 | });
142 |
143 | this._result.push({
144 | name: test.name + ' ' + 'update()',
145 | data: this._updateSamples.slice(0)
146 | });
147 |
148 | this._currentTest++;
149 |
150 | this._currentIter = 0;
151 | this._renderSamples = [];
152 | this._updateSamples = [];
153 | }
154 |
155 | setTimeout(this._iter, 0);
156 | } else {
157 | this.finished();
158 | }
159 | };
160 |
161 | module.exports = Executor;
162 | });
163 |
164 | var executor$1 = interopDefault(executor);
165 |
166 |
167 | var require$$0$1 = Object.freeze({
168 | default: executor$1
169 | });
170 |
171 | var benchmark$1 = createCommonjsModule(function (module) {
172 | 'use strict';
173 |
174 | var Executor = interopDefault(require$$0$1);
175 |
176 | function Benchmark() {
177 | this.running = false;
178 | this.impl = null;
179 | this.tests = null;
180 | this.reportCallback = null;
181 | this.enableTests = false;
182 |
183 | this.container = document.createElement('div');
184 |
185 | this._runButton = document.getElementById('RunButton');
186 | this._iterationsElement = document.getElementById('Iterations');
187 | this._reportElement = document.createElement('pre');
188 |
189 | document.body.appendChild(this.container);
190 | document.body.appendChild(this._reportElement);
191 |
192 | var self = this;
193 |
194 | this._runButton.addEventListener('click', function (e) {
195 | e.preventDefault();
196 |
197 | if (!self.running) {
198 | var iterations = parseInt(self._iterationsElement.value);
199 | if (iterations <= 0) {
200 | iterations = 10;
201 | }
202 |
203 | self.run(iterations);
204 | }
205 | }, false);
206 |
207 | this.ready(true);
208 | }
209 |
210 | Benchmark.prototype.ready = function (v) {
211 | if (v) {
212 | this._runButton.disabled = '';
213 | } else {
214 | this._runButton.disabled = 'true';
215 | }
216 | };
217 |
218 | Benchmark.prototype.run = function (iterations) {
219 | var self = this;
220 | this.running = true;
221 | this.ready(false);
222 |
223 | new Executor(self.impl, self.container, self.tests, 1, function () {
224 | // warmup
225 | new Executor(self.impl, self.container, self.tests, iterations, function (samples) {
226 | self._reportElement.textContent = JSON.stringify(samples, null, ' ');
227 | self.running = false;
228 | self.ready(true);
229 | if (self.reportCallback != null) {
230 | self.reportCallback(samples);
231 | }
232 | }, undefined, false).start();
233 | }, undefined, this.enableTests).start();
234 | };
235 |
236 | module.exports = Benchmark;
237 | });
238 |
239 | var benchmark$2 = interopDefault(benchmark$1);
240 |
241 |
242 | var require$$0 = Object.freeze({
243 | default: benchmark$2
244 | });
245 |
246 | var index = createCommonjsModule(function (module) {
247 | 'use strict';
248 |
249 | var Benchmark = interopDefault(require$$0);
250 | var benchmark = new Benchmark();
251 |
252 | function initFromScript(scriptUrl, impl) {
253 | var e = document.createElement('script');
254 | e.src = scriptUrl;
255 |
256 | e.onload = function () {
257 | benchmark.tests = window.generateBenchmarkData().units;
258 | benchmark.ready(true);
259 | };
260 |
261 | document.head.appendChild(e);
262 | }
263 |
264 | function initFromParentWindow(parent, name, version, id) {
265 | window.addEventListener('message', function (e) {
266 | var data = e.data;
267 | var type = data.type;
268 |
269 | if (type === 'tests') {
270 | benchmark.tests = data.data;
271 | benchmark.reportCallback = function (samples) {
272 | parent.postMessage({
273 | type: 'report',
274 | data: {
275 | name: name,
276 | version: version,
277 | samples: samples
278 | },
279 | id: id
280 | }, '*');
281 | };
282 | benchmark.ready(true);
283 |
284 | parent.postMessage({
285 | type: 'ready',
286 | data: null,
287 | id: id
288 | }, '*');
289 | } else if (type === 'run') {
290 | benchmark.run(data.data.iterations);
291 | }
292 | }, false);
293 |
294 | parent.postMessage({
295 | type: 'init',
296 | data: null,
297 | id: id
298 | }, '*');
299 | }
300 |
301 | function init(name, version, impl) {
302 | // Parse Query String.
303 | var qs = function (a) {
304 | if (a == "") return {};
305 | var b = {};
306 | for (var i = 0; i < a.length; ++i) {
307 | var p = a[i].split('=', 2);
308 | if (p.length == 1) {
309 | b[p[0]] = "";
310 | } else {
311 | b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
312 | }
313 | }
314 | return b;
315 | }(window.location.search.substr(1).split('&'));
316 |
317 | if (qs['name'] !== void 0) {
318 | name = qs['name'];
319 | }
320 |
321 | if (qs['version'] !== void 0) {
322 | version = qs['version'];
323 | }
324 |
325 | var type = qs['type'];
326 |
327 | if (qs['test'] !== void 0) {
328 | benchmark.enableTests = true;
329 | console.log('tests enabled');
330 | }
331 |
332 | var id;
333 | if (type === 'iframe') {
334 | id = qs['id'];
335 | if (id === void 0) id = null;
336 | initFromParentWindow(window.parent, name, version, id);
337 | } else if (type === 'window') {
338 | if (window.opener != null) {
339 | id = qs['id'];
340 | if (id === void 0) id = null;
341 | initFromParentWindow(window.opener, name, version, id);
342 | } else {
343 | console.log('Failed to initialize: opener window is NULL');
344 | }
345 | } else {
346 | var testsUrl = qs['data']; // url to the script generating test data
347 | if (testsUrl !== void 0) {
348 | initFromScript(testsUrl);
349 | } else {
350 | console.log('Failed to initialize: cannot load tests data');
351 | }
352 | }
353 |
354 | benchmark.impl = impl;
355 | }
356 |
357 | // performance.now() polyfill
358 | // https://gist.github.com/paulirish/5438650
359 | // prepare base perf object
360 | if (typeof window.performance === 'undefined') {
361 | window.performance = {};
362 | }
363 | if (!window.performance.now) {
364 | var nowOffset = Date.now();
365 | if (performance.timing && performance.timing.navigationStart) {
366 | nowOffset = performance.timing.navigationStart;
367 | }
368 | window.performance.now = function now() {
369 | return Date.now() - nowOffset;
370 | };
371 | }
372 |
373 | module.exports = init;
374 | });
375 |
376 | var benchmark = interopDefault(index);
377 |
378 | var FlowVNode = function FlowVNode(tagName, id, classList, data, children, text, element, key) {
379 | this._tagName = tagName;
380 | this._id = id;
381 | this._classList = classList;
382 | this._data = data;
383 | this._children = children;
384 | this._text = text;
385 | this._element = element;
386 | this._key = key;
387 | };
388 |
389 | var prototypeAccessors = { tagName: {},id: {},classList: {},selector: {},data: {},children: {},text: {},element: {},key: {} };
390 |
391 | prototypeAccessors.tagName.get = function () {
392 | return this._tagName;
393 | };
394 |
395 | prototypeAccessors.id.get = function () {
396 | return this._id;
397 | };
398 |
399 | prototypeAccessors.classList.get = function () {
400 | return this._classList;
401 | };
402 |
403 | prototypeAccessors.selector.get = function () {
404 | return "" + (this._tagName) + (this._id && ("#" + (this._id)) || '') + "" + (this._classList && this._classList.length > 0 ? '.' + this._classList.sort().join('') : '');
405 | };
406 |
407 | prototypeAccessors.data.get = function () {
408 | return this._data;
409 | };
410 |
411 | prototypeAccessors.children.get = function () {
412 | return this._children;
413 | };
414 |
415 | prototypeAccessors.text.get = function () {
416 | return this._text;
417 | };
418 |
419 | prototypeAccessors.element.get = function () {
420 | return this._element;
421 | };
422 |
423 | prototypeAccessors.key.get = function () {
424 | return this._key;
425 | };
426 |
427 | FlowVNode.prototype.setTagName = function setTagName (tagName) {
428 | return new FlowVNode(tagName, this._id, this._classList, this._data, this._children, this._text, this._element, this._key);
429 | };
430 |
431 | FlowVNode.prototype.setId = function setId (id) {
432 | return new FlowVNode(this._tagName, id, this._classList, this._data, this._children, this._text, this._element, this._key);
433 | };
434 |
435 | FlowVNode.prototype.setClassList = function setClassList (classList) {
436 | return new FlowVNode(this._tagName, this._id, classList, this._data, this._children, this._text, this._element, this._key);
437 | };
438 |
439 | FlowVNode.prototype.setData = function setData (data) {
440 | return new FlowVNode(this._tagName, this._id, this._classList, data, this._children, this._text, this._element, this._key);
441 | };
442 |
443 | FlowVNode.prototype.setChildren = function setChildren (children) {
444 | return new FlowVNode(this._tagName, this._id, this._classList, this._data, children, this._text, this._element, this._key);
445 | };
446 |
447 | FlowVNode.prototype.setText = function setText (text) {
448 | return new FlowVNode(this._tagName, this._id, this._classList, this._data, this._children, text, this._element, this._key);
449 | };
450 |
451 | FlowVNode.prototype.setElement = function setElement (element) {
452 | return new FlowVNode(this._tagName, this._id, this._classList, this._data, this._children, this._text, element, this._key);
453 | };
454 |
455 | FlowVNode.prototype.setKey = function setKey (key) {
456 | return new FlowVNode(this._tagName, this._id, this._classList, this._data, this._children, this._text, this._element, key);
457 | };
458 |
459 | Object.defineProperties( FlowVNode.prototype, prototypeAccessors );
460 |
461 | // eslint-disable-line
462 |
463 | function isUndef(x) {
464 | return x === void 0;
465 | }
466 |
467 | function emptyVNodeAt(node) {
468 | return new FlowVNode(node.tagName.toLowerCase(), node.id || '', copy(node.classList), {}, [], '', node, null);
469 | }
470 |
471 | function sameVNode(a, b) {
472 | return a.selector === b.selector && a.key === b.key;
473 | }
474 |
475 | function forEach(f, arr) {
476 | var length = arr.length;
477 | for (var i = 0; i < length; ++i) {
478 | // eslint-disable-line immutable/no-let
479 | f(arr[i], i);
480 | }
481 | }
482 |
483 | // copy :: [a] -> [a]
484 | // duplicate a (shallow duplication)
485 | function copy(a) {
486 | var l = a.length;
487 | var b = new Array(l);
488 | for (var i = 0; i < l; ++i) {
489 | // eslint-disable-line immutable/no-let
490 | b[i] = a[i];
491 | }
492 | return b;
493 | }
494 |
495 | // map :: (a -> b) -> [a] -> [b]
496 | // transform each element with f
497 | function map$1(f, a) {
498 | var l = a.length;
499 | var b = new Array(l);
500 | for (var i = 0; i < l; ++i) {
501 | // eslint-disable-line immutable/no-let
502 | b[i] = f(a[i], i);
503 | }
504 | return b;
505 | }
506 |
507 | // reduce :: (a -> b -> a) -> a -> [b] -> a
508 | // accumulate via left-fold
509 | function reduce(f, z, a) {
510 | var r = z; // eslint-disable-line immutable/no-let
511 | for (var i = 0, l = a.length; i < l; ++i) {
512 | // eslint-disable-line immutable/no-let
513 | r = f(r, a[i], i);
514 | }
515 | return r;
516 | }
517 |
518 | // replace :: a -> Int -> [a]
519 | // replace element at index
520 | function replace(x, i, a) {
521 | if (i < 0) {
522 | throw new TypeError('i must be >= 0');
523 | }
524 |
525 | var l = a.length;
526 | var b = new Array(l);
527 | for (var j = 0; j < l; ++j) {
528 | // eslint-disable-line immutable/no-let
529 | b[j] = i === j ? x : a[j];
530 | }
531 | return b;
532 | }
533 |
534 | // eslint-disable-line
535 |
536 | var assign = function (x, y) { return Object.assign({}, x, y); };
537 |
538 | function setSVGNamespace(vNode) {
539 | var newVNode = vNode.setData(assign(vNode.data, { ns: 'http://www.w3.org/2000/svg' }));
540 | if (newVNode.children.length > 0 && newVNode.tagName !== 'foreignObject') {
541 | return newVNode.setChildren(addNS(newVNode.children));
542 | }
543 | return newVNode;
544 | }
545 |
546 | function addNS(children) {
547 | return map$1(setSVGNamespace, children);
548 | }
549 |
550 | function convertText(children) {
551 | return map$1(function (child) {
552 | return typeof child === 'string' ? new FlowVNode('', '', [], {}, [], child, null, null) : child;
553 | }, children);
554 | }
555 |
556 | function h(selector, data, childrenOrText) {
557 | var ref = parseSelector(selector);
558 | var tagName = ref.tagName;
559 | var id = ref.id;
560 | var classList = ref.classList;
561 |
562 | var text = typeof childrenOrText === 'string' ? childrenOrText : '';
563 |
564 | var children = Array.isArray(childrenOrText) ? childrenOrText : [];
565 |
566 | return new FlowVNode(tagName, id, classList, data, tagName === 'svg' ? addNS(children) : convertText(children), text, null, data && data.key || null);
567 | }
568 |
569 | var classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/;
570 | var notClassId = /^\.|#/;
571 |
572 | function parseSelector(selector) {
573 | var tagParts = selector.split(classIdSplit);
574 |
575 | if (selector === '') {
576 | return {
577 | tagName: 'div',
578 | id: '',
579 | classList: []
580 | };
581 | }
582 |
583 | var seed = notClassId.test(tagParts[1]) ? { tagName: 'div', id: '', classList: [] } : { tagName: '', id: '', classList: [] };
584 |
585 | return reduce(function (output, part) {
586 | if (!part) return output;
587 |
588 | var type = part.charAt(0);
589 |
590 | if (!output.tagName) {
591 | output.tagName = part.trim();
592 | } else if (type === '.') {
593 | output.classList.push(part.substring(1, part.length).trim());
594 | } else if (type === '#') {
595 | output.id = part.substring(1, part.length).trim();
596 | }
597 |
598 | return output;
599 | }, seed, tagParts);
600 | }
601 |
602 | var _extends = Object.assign || function (target) {
603 | var arguments$1 = arguments;
604 | for (var i = 1; i < arguments.length; i++) { var source = arguments$1[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
605 |
606 | // eslint-disable-line
607 | var emptyVNode = new FlowVNode('', '', [], {}, [], '', null, null);
608 |
609 | var id = function (x, y) { return y; };
610 |
611 | function setTextContent(node, text) {
612 | node.textContent = text;
613 | }
614 |
615 | function insertBefore(parent, node, before) {
616 | parent.insertBefore(node, before);
617 | }
618 |
619 | function removeChild(parent, child) {
620 | parent.removeChild(child);
621 | }
622 |
623 | function nextSibling(element) {
624 | return element.nextSibling;
625 | }
626 |
627 | var appendChild = function (parent) { return function (vNode) {
628 | parent.appendChild(vNode.element);
629 | }; };
630 |
631 | function createKeyToIndex(children, beginIdx, endIdx) {
632 | return reduce(function (map, child, i) {
633 | if (child.key) {
634 | map[child.key] = i;
635 | }
636 | return map;
637 | }, {}, children);
638 | }
639 |
640 | function callInsert(vNode) {
641 | var data = vNode.data;
642 | var children = vNode.children;
643 |
644 | var insertedChildren = children.length > 0 ? map$1(callInsert, children) : children;
645 |
646 | if (data.insert) {
647 | return data.insert(vNode);
648 | }
649 |
650 | return vNode.setChildren(insertedChildren);
651 | }
652 |
653 | function init(modules) {
654 | if ( modules === void 0 ) modules = [];
655 |
656 | // calls all update hooks
657 | function callUpdateHooks(oldVNode, vNode) {
658 | var ref = reduce(function (ref, module) {
659 | var oldVNode = ref[0];
660 | var vNode = ref[1];
661 |
662 | return [vNode, module.update(oldVNode, vNode)];
663 | }, [oldVNode, vNode], modules);
664 | var updatedOldVNode = ref[0];
665 | var updatedVNode = ref[1];
666 |
667 | var update = !isUndef(updatedVNode.data) && updatedVNode.data.update || id;
668 |
669 | return update(updatedOldVNode, updatedVNode);
670 | }
671 |
672 | // calls all create hooks
673 | function callCreateHooks(vNode) {
674 | var updatedVNode = reduce(function (vNode, module) {
675 | return module.create(emptyVNode, vNode);
676 | }, vNode, modules);
677 |
678 | var create = vNode.data.insert || id;
679 |
680 | return create(emptyVNode, updatedVNode);
681 | }
682 |
683 | // recursively calls destroy hook on a VNode and all it's children
684 | function callDestroyHooks(vNode) {
685 | var data = vNode.data;
686 | var children = vNode.children;
687 | if (data.destroy) data.destroy(vNode);
688 |
689 | forEach(function (module) {
690 | module.destroy(vNode);
691 | }, modules);
692 |
693 | if (children.length > 0) {
694 | forEach(callDestroyHooks, children);
695 | }
696 | }
697 |
698 | // calls remove hooks
699 | function callRemoveHooks(vNode, remove) {
700 | var data = vNode.data;
701 |
702 | forEach(function (module) {
703 | module.remove(vNode, remove);
704 | }, modules);
705 |
706 | if (data.remove) {
707 | data.remove(vNode, remove);
708 | } else {
709 | remove();
710 | }
711 | }
712 |
713 | // create a new VNode which contains an element
714 | function createElement(vNode) {
715 | var tagName = vNode.tagName;
716 | var id = vNode.id;
717 | var classList = vNode.classList;
718 | var VNodeChildren = vNode.children;
719 | var text = vNode.text;
720 | var data = vNode.data;
721 |
722 | var element = data.ns ? document.createElementNS(data.ns, tagName) : document.createElement(tagName);
723 |
724 | if (id) element.id = id;
725 | if (classList.length > 0) element.className = classList.join(' ');
726 |
727 | var children = map$1(createElement, VNodeChildren);
728 |
729 | if (children.length > 0) {
730 | forEach(appendChild(element), children);
731 | } else if (text && text.length > 0) {
732 | setTextContent(element, text);
733 | }
734 |
735 | var updatedVNode = vNode.setChildren(children).setElement(element);
736 |
737 | return callCreateHooks(updatedVNode);
738 | }
739 |
740 | function createRemoveCallback(childElement, listeners) {
741 | return function () {
742 | if (--listeners === 0) {
743 | var parent = childElement.parentNode;
744 | removeChild(parent, childElement);
745 | }
746 | };
747 | }
748 |
749 | function removeVNodes(parent, vNodes, startIndex, endIndex) {
750 | for (; startIndex < endIndex; ++startIndex) {
751 | var listeners = modules.length + 1;
752 | var child = vNodes[startIndex];
753 | if (child) {
754 | if (child.tagName !== '') {
755 | callDestroyHooks(child);
756 | var rm = createRemoveCallback(child.element, listeners);
757 | callRemoveHooks(child, rm);
758 | } else {
759 | // Text Node
760 | removeChild(parent, child.element);
761 | }
762 | }
763 | }
764 | }
765 |
766 | function addVNodes(parent, vNode, before, startIndex, endIndex) {
767 | var children = vNode.children;
768 |
769 | if (startIndex >= children.length) {
770 | return vNode;
771 | }
772 |
773 | var childrenWithElements = map$1(function (child, index) {
774 | if (index >= startIndex && index <= endIndex) {
775 | insertBefore(parent, child.element, before);
776 | return callInsert(child);
777 | }
778 | return child;
779 | }, map$1(createElement, children));
780 |
781 | return vNode.setChildren(childrenWithElements);
782 | }
783 |
784 | // update children when they have changed
785 | function updateChildren(ref) {
786 | var parent = ref.parent;
787 | var oldChildren = ref.oldChildren;
788 | var vNode = ref.vNode;
789 | var oldKeyToIndex = ref.oldKeyToIndex;
790 | var oldStartIndex = ref.oldStartIndex;
791 | var oldEndIndex = ref.oldEndIndex;
792 | var newStartIndex = ref.newStartIndex;
793 | var newEndIndex = ref.newEndIndex;
794 | var oldStartVNode = ref.oldStartVNode;
795 | var oldEndVNode = ref.oldEndVNode;
796 | var newStartVNode = ref.newStartVNode;
797 | var newEndVNode = ref.newEndVNode;
798 |
799 | var previousInput = arguments[0];
800 | var children = vNode.children;
801 |
802 | if (oldStartIndex > oldEndIndex) {
803 | var before = isUndef(children[newEndIndex + 1]) ? null : children[newEndIndex + 1].element;
804 |
805 | return addVNodes(parent, vNode, before, newStartIndex, newEndIndex);
806 | }
807 |
808 | if (newStartIndex > newEndIndex) {
809 | removeVNodes(parent, oldChildren, oldStartIndex, oldEndIndex);
810 | return vNode;
811 | }
812 |
813 | // VNode has moved left in child array
814 | if (isUndef(oldStartVNode)) {
815 | var _oldStartIndex = oldStartIndex + 1;
816 | return updateChildren(_extends({}, previousInput, {
817 | oldStartVNode: oldChildren[_oldStartIndex],
818 | oldStartIndex: _oldStartIndex
819 | }));
820 | }
821 |
822 | if (isUndef(oldEndVNode)) {
823 | var _oldEndIndex = oldEndIndex - 1;
824 | return updateChildren(_extends({}, previousInput, {
825 | oldEndVNode: oldChildren[_oldEndIndex],
826 | oldEndIndex: _oldEndIndex
827 | }));
828 | }
829 |
830 | if (sameVNode(oldStartVNode, newStartVNode)) {
831 | var _updatedVNode = patchVNode(oldStartVNode, newStartVNode);
832 | var newChildren$1 = replace(_updatedVNode, newStartIndex, children);
833 |
834 | var _oldStartIndex$1 = oldStartIndex + 1;
835 | var _newStartIndex$1 = newStartIndex + 1;
836 |
837 | return updateChildren(_extends({}, previousInput, {
838 | vNode: vNode.setChildren(newChildren$1),
839 | oldStartIndex: _oldStartIndex$1,
840 | newStartIndex: _newStartIndex$1,
841 | oldStartVNode: oldChildren[_oldStartIndex$1],
842 | newStartVNode: children[_newStartIndex$1]
843 | }));
844 | }
845 |
846 | if (sameVNode(oldEndVNode, newEndVNode)) {
847 | var updatedVNode$1 = patchVNode(oldEndVNode, newEndVNode);
848 | var newChildren$2 = replace(updatedVNode$1, newEndIndex, children);
849 |
850 | var _oldEndIndex$1 = oldEndIndex - 1;
851 | var _newEndIndex = newEndIndex - 1;
852 |
853 | return updateChildren(_extends({}, previousInput, {
854 | vNode: vNode.setChildren(newChildren$2),
855 | oldEndIndex: _oldEndIndex$1,
856 | newEndIndex: _newEndIndex,
857 | oldEndVNode: oldChildren[_oldEndIndex$1],
858 | newEndVNode: children[_newEndIndex]
859 | }));
860 | }
861 |
862 | // vNode has moved right in the array
863 | if (sameVNode(oldStartVNode, newEndVNode)) {
864 | var updatedVNode$2 = patchVNode(oldStartVNode, newEndVNode);
865 | var newChildren$3 = replace(updatedVNode$2, newEndIndex, children);
866 |
867 | insertBefore(parent, oldStartVNode.element, nextSibling(oldEndVNode.element));
868 |
869 | var _oldStartIndex$2 = oldStartIndex + 1;
870 | var _newEndIndex$1 = newEndIndex - 1;
871 |
872 | return updateChildren(_extends({}, previousInput, {
873 | vNode: vNode.setChildren(newChildren$3),
874 | oldStartIndex: _oldStartIndex$2,
875 | newEndIndex: _newEndIndex$1,
876 | oldStartVNode: oldChildren[_oldStartIndex$2],
877 | newEndVNode: children[_newEndIndex$1]
878 | }));
879 | }
880 |
881 | // vNode moved left
882 | if (sameVNode(oldEndVNode, newStartVNode)) {
883 | var updatedVNode$3 = patchVNode(oldEndVNode, newStartVNode);
884 | var newChildren$4 = replace(updatedVNode$3, newStartIndex, children);
885 |
886 | insertBefore(parent, oldEndVNode.element, oldStartVNode.element);
887 |
888 | var _oldEndIndex$2 = oldEndIndex - 1;
889 | var _newStartIndex$2 = newStartIndex + 1;
890 |
891 | return updateChildren(_extends({}, previousInput, {
892 | vNode: vNode.setChildren(newChildren$4),
893 | oldEndIndex: _oldEndIndex$2,
894 | newStartIndex: _newStartIndex$2,
895 | oldEndVNode: oldChildren[_oldEndIndex$2],
896 | newStartVNode: children[_newStartIndex$2]
897 | }));
898 | }
899 |
900 | var _oldKeyToIndex = oldKeyToIndex === null ? createKeyToIndex(oldChildren, oldStartIndex, oldEndIndex) : oldKeyToIndex;
901 |
902 | var indexInOld = _oldKeyToIndex[newStartVNode.key];
903 |
904 | if (isUndef(indexInOld)) {
905 | // new element
906 | var _vNode$1 = createElement(newStartVNode);
907 | insertBefore(parent, _vNode$1.element, oldStartVNode.element);
908 |
909 | var _newStartIndex$3 = newStartIndex + 1;
910 | var _newStartVNode$1 = children[_newStartIndex$3];
911 |
912 | return updateChildren(_extends({}, previousInput, {
913 | oldKeyToIndex: _oldKeyToIndex,
914 | vNode: callInsert(_vNode$1),
915 | newStartIndex: _newStartIndex$3,
916 | newStartVNode: _newStartVNode$1
917 | }));
918 | }
919 |
920 | var elementToMove = oldChildren[indexInOld];
921 | var updatedVNode = patchVNode(elementToMove, newStartVNode);
922 | var newChildren = replace(updatedVNode, newStartIndex, children);
923 | var _vNode = vNode.setChildren(newChildren);
924 | // $flow-ignore-line
925 | oldChildren[indexInOld] = undefined;
926 | insertBefore(parent, elementToMove.element, oldStartVNode.element);
927 | var _newStartIndex = newStartIndex + 1;
928 | var _newStartVNode = children[_newStartIndex];
929 | return updateChildren(_extends({}, previousInput, {
930 | oldKeyToIndex: _oldKeyToIndex,
931 | vNode: _vNode,
932 | newStartIndex: _newStartIndex,
933 | newStartVNode: _newStartVNode
934 | }));
935 | }
936 |
937 | // updates the DOM and VNode with the current information it should have
938 | function patchVNode(oldVNode, _vNode) {
939 | // $flow-ignore-line
940 | var vNode = _vNode.setElement(oldVNode.element);
941 | // if the previous and current are equal do nothing
942 | if (oldVNode === vNode) return vNode;
943 |
944 | // if the vNode has changed drastically create a new one an replace the old
945 | if (!sameVNode(oldVNode, vNode)) {
946 | var parent = oldVNode.element && oldVNode.element.parentNode;
947 | var newVNode = createElement(vNode);
948 | insertBefore(parent, newVNode.element, oldVNode.element);
949 | var updatedVNode$1 = callInsert(newVNode);
950 | if (parent !== null && parent !== undefined) {
951 | removeVNodes(parent, [oldVNode], 0, 0);
952 | }
953 | return updatedVNode$1;
954 | }
955 |
956 | var updatedVNode = callUpdateHooks(oldVNode, vNode);
957 |
958 | var element = updatedVNode.element;
959 | var children = updatedVNode.children;
960 |
961 | // lets update the DOM
962 | if (updatedVNode.text === '') {
963 | if (oldVNode.children.length > 0 && children.length > 0) {
964 | // children have changed somehow
965 | if (oldVNode.children === updatedVNode.children) return updatedVNode;
966 |
967 | var oldEndIndex = oldVNode.children.length - 1;
968 | var newEndIndex = children.length - 1;
969 |
970 | var oldStartVNode = oldVNode.children[0];
971 | var oldEndVNode = oldVNode.children[oldEndIndex];
972 | var newStartVNode = children[0];
973 | var newEndVNode = children[newEndIndex];
974 |
975 | var input = {
976 | parent: oldVNode.element,
977 | oldChildren: oldVNode.children,
978 | vNode: updatedVNode,
979 | oldKeyToIndex: null,
980 | oldStartIndex: 0,
981 | oldEndIndex: oldEndIndex,
982 | newStartIndex: 0,
983 | newEndIndex: newEndIndex,
984 | oldStartVNode: oldStartVNode,
985 | oldEndVNode: oldEndVNode,
986 | newStartVNode: newStartVNode,
987 | newEndVNode: newEndVNode
988 | };
989 |
990 | return updateChildren(input);
991 | } else if (children.length > 0) {
992 | // children have been added when there were none
993 | if (oldVNode.text !== '') setTextContent(element, '');
994 | return addVNodes(element, updatedVNode, null, 0, updatedVNode.children.length - 1);
995 | } else if (oldVNode.children.length > 0) {
996 | // children have been completely removed
997 | if (updatedVNode.element) {
998 | removeVNodes(updatedVNode.element, oldVNode.children, 0, oldVNode.children.length - 1);
999 | }
1000 | return updatedVNode;
1001 | } else if (oldVNode.text !== '') {
1002 | // text has been removed
1003 | setTextContent(updatedVNode.element, '');
1004 | return updatedVNode;
1005 | }
1006 | } else if (oldVNode.text !== updatedVNode.text) {
1007 | // update text if needed
1008 | setTextContent(element, updatedVNode.text);
1009 | }
1010 |
1011 | return updatedVNode;
1012 | }
1013 |
1014 | return function patch(oldVNode, vNode) {
1015 | if (oldVNode === vNode) return vNode;
1016 |
1017 | var previousVNode = oldVNode instanceof HTMLElement ? emptyVNodeAt(oldVNode) : oldVNode;
1018 |
1019 | if (sameVNode(previousVNode, vNode)) {
1020 | return patchVNode(previousVNode, vNode);
1021 | }
1022 |
1023 | var element = previousVNode.element;
1024 | var parent = element && element.parentNode;
1025 |
1026 | var newVNode = createElement(vNode);
1027 |
1028 | if (parent) {
1029 | insertBefore(parent, newVNode.element, element && element.nextSibling);
1030 | return callInsert(newVNode);
1031 | }
1032 |
1033 | return newVNode;
1034 | };
1035 | }
1036 |
1037 | var patch = init([]);
1038 |
1039 | var NAME = 'flow-dom';
1040 | var VERSION = '1.0.0';
1041 |
1042 | function map(arr, f) {
1043 | var l = arr.length;
1044 | var x = new Array(l);
1045 | for (var i = 0; i < l; ++i) {
1046 | // eslint-disable-line immutable/no-let
1047 | x[i] = f(arr[i]);
1048 | }
1049 | return x;
1050 | }
1051 |
1052 | function convertToVnodes(nodes) {
1053 | return map(nodes, function (n) {
1054 | if (n.children !== null) {
1055 | return h('div', { key: n.key }, convertToVnodes(n.children));
1056 | }
1057 | return h('div', { key: n.key }, n.key);
1058 | });
1059 | }
1060 |
1061 | function BenchmarkImpl(container, a, b) {
1062 | this.container = container;
1063 | this.a = a;
1064 | this.b = b;
1065 | this.vnode = null;
1066 | }
1067 |
1068 | BenchmarkImpl.prototype.setUp = function () {};
1069 |
1070 | BenchmarkImpl.prototype.tearDown = function () {
1071 | patch(this.vnode, h('div'));
1072 | };
1073 |
1074 | BenchmarkImpl.prototype.render = function () {
1075 | var elm = document.createElement('div');
1076 | this.vnode = patch(elm, h('div', convertToVnodes(this.a)));
1077 | this.container.appendChild(elm);
1078 | };
1079 |
1080 | BenchmarkImpl.prototype.update = function () {
1081 | this.vnode = patch(this.vnode, h('div', convertToVnodes(this.b)));
1082 | };
1083 |
1084 | document.addEventListener('DOMContentLoaded', function (e) {
1085 | benchmark(NAME, VERSION, BenchmarkImpl);
1086 | }, false);
1087 |
1088 | }());
--------------------------------------------------------------------------------
/perf/flow-dom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vdom-benchmark-flow-dom",
3 | "version": "1.0.0",
4 | "description": "Virtual DOM Benchmark: Flow-DOM",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "rollup -c",
8 | "serve": "pushstate-server dist/ 3000 /index.html",
9 | "start": "npm run build && npm run serve",
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "author": "Tylor Steinberger ",
13 | "license": "MIT",
14 | "dependencies": {
15 | "vdom-benchmark-base": "^0.2.4"
16 | },
17 | "devDependencies": {
18 | "babel-plugin-module-resolver": "^2.2.0",
19 | "babel-plugin-transform-flow-strip-types": "^6.14.0",
20 | "babel-plugin-transform-object-rest-spread": "^6.8.0",
21 | "pushstate-server": "^1.11.0",
22 | "rollup": "^0.34.10",
23 | "rollup-plugin-babel": "^2.6.1",
24 | "rollup-plugin-buble": "^0.13.0",
25 | "rollup-plugin-commonjs": "^3.3.1",
26 | "rollup-plugin-node-resolve": "^2.0.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/perf/flow-dom/rollup.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import buble from 'rollup-plugin-buble'
3 | import babel from 'rollup-plugin-babel'
4 | import nodeResolve from 'rollup-plugin-node-resolve'
5 | import commonjs from 'rollup-plugin-commonjs'
6 |
7 | export default {
8 | entry: 'src/index.js',
9 | dest: 'dist/main.js',
10 | format: 'iife',
11 | moduleName: 'VDOMBenchmarkFlowDom',
12 | plugins: [
13 | babel(),
14 | buble(),
15 | nodeResolve({
16 | jsnext: true
17 | }),
18 | commonjs()
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/perf/flow-dom/src/index.js:
--------------------------------------------------------------------------------
1 | import benchmark from 'vdom-benchmark-base'
2 | import { init } from '../../../src/index'
3 | import { h } from '../../../src/hyperscript/h'
4 | const patch = init([])
5 |
6 | var NAME = 'flow-dom'
7 | var VERSION = '1.0.0'
8 |
9 | function map (arr, f) {
10 | const l = arr.length
11 | const x = new Array(l)
12 | for (let i = 0; i < l; ++i) { // eslint-disable-line immutable/no-let
13 | x[i] = f(arr[i])
14 | }
15 | return x
16 | }
17 |
18 | function convertToVnodes (nodes) {
19 | return map(nodes, function (n) {
20 | if (n.children !== null) {
21 | return h('div', {key: n.key}, convertToVnodes(n.children))
22 | }
23 | return h('div', {key: n.key}, n.key)
24 | })
25 | }
26 |
27 | function BenchmarkImpl (container, a, b) {
28 | this.container = container
29 | this.a = a
30 | this.b = b
31 | this.vnode = null
32 | }
33 |
34 | BenchmarkImpl.prototype.setUp = function () {
35 | }
36 |
37 | BenchmarkImpl.prototype.tearDown = function () {
38 | patch(this.vnode, h('div'))
39 | }
40 |
41 | BenchmarkImpl.prototype.render = function () {
42 | const elm = document.createElement('div')
43 | this.vnode = patch(elm, h('div', convertToVnodes(this.a)))
44 | this.container.appendChild(elm)
45 | }
46 |
47 | BenchmarkImpl.prototype.update = function () {
48 | this.vnode = patch(this.vnode, h('div', convertToVnodes(this.b)))
49 | }
50 |
51 | document.addEventListener('DOMContentLoaded', function (e) {
52 | benchmark(NAME, VERSION, BenchmarkImpl)
53 | }, false)
54 |
--------------------------------------------------------------------------------
/src/hyperscript/VNode.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | export type VNode = {
4 | tagName: string,
5 | id: string,
6 | classList: string[],
7 | selector: string,
8 | data: Object,
9 | children: Array,
10 | text: string | null,
11 | element: Element | Text | null,
12 | key: string | number | null,
13 |
14 | setTagName(tagName: string): VNode,
15 | setId(id: string): VNode,
16 | setClassList(classList: string[]): VNode,
17 | setData(data: Object): VNode,
18 | setChildren(children: Array): VNode,
19 | setText(text: string): VNode,
20 | setElement(element: Element | Text): VNode,
21 | setKey(key: string | number): VNode
22 | }
23 |
24 | export class FlowVNode {
25 | _tagName: string
26 | _id: string
27 | _classList: string[]
28 | _data: Object
29 | _children: Array
30 | _text: string
31 | _element: Element | Text | null
32 | _key: string | number | null
33 |
34 | constructor (tagName: string, id: string, classList: string[], data: Object, children: Array,
35 | text: string, element: Element | Text | null, key: string | number | null) {
36 | this._tagName = tagName
37 | this._id = id
38 | this._classList = classList
39 | this._data = data
40 | this._children = children
41 | this._text = text
42 | this._element = element
43 | this._key = key
44 | }
45 |
46 | get tagName (): string {
47 | return this._tagName
48 | }
49 |
50 | get id (): string {
51 | return this._id
52 | }
53 |
54 | get classList (): string[] {
55 | return this._classList
56 | }
57 |
58 | get selector (): string {
59 | return `${this._tagName}${this._id && `#${this._id}` || ''}` +
60 | `${this._classList && this._classList.length > 0 ? '.' + this._classList.sort().join('') : ''}`
61 | }
62 |
63 | get data (): Object {
64 | return this._data
65 | }
66 |
67 | get children (): Array {
68 | return this._children
69 | }
70 |
71 | get text (): string | null {
72 | return this._text
73 | }
74 |
75 | get element (): Element | Text | null {
76 | return this._element
77 | }
78 |
79 | get key (): string | number | null {
80 | return this._key
81 | }
82 |
83 | setTagName (tagName: string): VNode {
84 | return new FlowVNode(tagName, this._id, this._classList, this._data,
85 | this._children, this._text, this._element, this._key)
86 | }
87 |
88 | setId (id: string): VNode {
89 | return new FlowVNode(this._tagName, id, this._classList, this._data,
90 | this._children, this._text, this._element, this._key)
91 | }
92 |
93 | setClassList (classList: Array): VNode {
94 | return new FlowVNode(this._tagName, this._id, classList, this._data,
95 | this._children, this._text, this._element, this._key)
96 | }
97 |
98 | setData (data: Object): VNode {
99 | return new FlowVNode(this._tagName, this._id, this._classList, data,
100 | this._children, this._text, this._element, this._key)
101 | }
102 |
103 | setChildren (children: Array): VNode {
104 | return new FlowVNode(this._tagName, this._id, this._classList, this._data,
105 | children, this._text, this._element, this._key)
106 | }
107 |
108 | setText (text: string): VNode {
109 | return new FlowVNode(this._tagName, this._id, this._classList, this._data,
110 | this._children, text, this._element, this._key)
111 | }
112 |
113 | setElement (element: Element | Text): VNode {
114 | return new FlowVNode(this._tagName, this._id, this._classList, this._data,
115 | this._children, this._text, element, this._key)
116 | }
117 |
118 | setKey (key: string | number): VNode {
119 | return new FlowVNode(this._tagName, this._id, this._classList, this._data,
120 | this._children, this._text, this._element, key)
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/hyperscript/h.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | import type { VNode } from './VNode'
3 | import { map, reduce } from '../util'
4 | import { FlowVNode } from './VNode' // eslint-disable-line
5 |
6 | const assign = (x: Object, y: Object): Object => Object.assign({}, x, y)
7 |
8 | function setSVGNamespace (vNode: VNode): VNode {
9 | const newVNode = vNode.setData(assign(vNode.data, { ns: 'http://www.w3.org/2000/svg' }))
10 | if (newVNode.children.length > 0 && newVNode.tagName !== 'foreignObject') {
11 | return newVNode.setChildren(addNS(newVNode.children))
12 | }
13 | return newVNode
14 | }
15 |
16 | function addNS (children: any[]): VNode[] {
17 | return map(setSVGNamespace, children)
18 | }
19 |
20 | function convertText (children: any[]): VNode[] {
21 | return map(function (child: VNode | string): VNode {
22 | return typeof child === 'string'
23 | ? new FlowVNode('', '', [], {}, [], child, null, null)
24 | : (child: VNode)
25 | }, children)
26 | }
27 |
28 | export function h (selector: string, data: Object, childrenOrText: string | Array): VNode {
29 | const { tagName, id, classList } = parseSelector(selector)
30 |
31 | const text = typeof childrenOrText === 'string'
32 | ? childrenOrText
33 | : ''
34 |
35 | const children = Array.isArray(childrenOrText)
36 | ? childrenOrText
37 | : []
38 |
39 | return new FlowVNode(tagName, id, classList, data,
40 | tagName === 'svg' ? addNS(children) : convertText(children),
41 | text, null, data && data.key || null)
42 | }
43 |
44 | const classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/
45 | const notClassId = /^\.|#/
46 |
47 | type selectorOuput = { tagName: string, id: string, classList: string[] }
48 |
49 | function parseSelector (selector: string): selectorOuput {
50 | const tagParts = selector.split(classIdSplit)
51 |
52 | if (selector === '') {
53 | return {
54 | tagName: 'div',
55 | id: '',
56 | classList: []
57 | }
58 | }
59 |
60 | const seed = notClassId.test(tagParts[1])
61 | ? { tagName: 'div', id: '', classList: [] }
62 | : { tagName: '', id: '', classList: [] }
63 |
64 | return reduce(function (output: selectorOuput, part: string): selectorOuput {
65 | if (!part) return output
66 |
67 | const type = part.charAt(0)
68 |
69 | if (!output.tagName) {
70 | output.tagName = part.trim()
71 | } else if (type === '.') {
72 | output.classList.push(part.substring(1, part.length).trim())
73 | } else if (type === '#') {
74 | output.id = part.substring(1, part.length).trim()
75 | }
76 |
77 | return output
78 | }, seed, tagParts)
79 | }
80 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | import type { Module } from './interfaces'
3 | import type { VNode } from './hyperscript/VNode'
4 | import { FlowVNode } from './hyperscript/VNode' // eslint-disable-line
5 | import { isUndef, emptyVNodeAt, sameVNode, forEach, reduce, map, replace } from './util'
6 |
7 | export { h } from './hyperscript/h'
8 |
9 | const emptyVNode = new FlowVNode('', '', [], {}, [], '', null, null)
10 |
11 | const id = (x, y): VNode => y
12 |
13 | function setTextContent (node: any, text: any) {
14 | node.textContent = text
15 | }
16 |
17 | function insertBefore (parent: any, node: any, before: any) {
18 | parent.insertBefore(node, before)
19 | }
20 |
21 | function removeChild (parent: any, child: any) {
22 | parent.removeChild(child)
23 | }
24 |
25 | function nextSibling (element: any): Element {
26 | return element.nextSibling
27 | }
28 |
29 | const appendChild = (parent: any): Function => (vNode: any) => {
30 | parent.appendChild(vNode.element)
31 | }
32 |
33 | function createKeyToIndex (children: VNode[], beginIdx: number, endIdx: number): Object {
34 | return reduce(function (map: Object, child: VNode, i: number): Object {
35 | if (child.key) {
36 | map[child.key] = i
37 | }
38 | return map
39 | }, {}, children)
40 | }
41 |
42 | function callInsert (vNode: VNode): VNode {
43 | const { data, children } = vNode
44 |
45 | const insertedChildren = children.length > 0
46 | ? map(callInsert, children)
47 | : children
48 |
49 | if (data.insert) {
50 | return data.insert(vNode)
51 | }
52 |
53 | return vNode.setChildren(insertedChildren)
54 | }
55 |
56 | export function init (modules: Module[] = []): (oldVNode: VNode | HTMLElement, vNode: VNode) => VNode {
57 | // calls all update hooks
58 | function callUpdateHooks (oldVNode: VNode, vNode: VNode): VNode {
59 | const [updatedOldVNode, updatedVNode] = reduce(function ([oldVNode, vNode]: [VNode, VNode], module: Module): [VNode, VNode] {
60 | return [vNode, module.update(oldVNode, vNode)]
61 | }, [oldVNode, vNode], modules)
62 |
63 | const update = !isUndef(updatedVNode.data) && updatedVNode.data.update || id
64 |
65 | return update(updatedOldVNode, updatedVNode)
66 | }
67 |
68 | // calls all create hooks
69 | function callCreateHooks (vNode: VNode): VNode {
70 | const updatedVNode = reduce(function (vNode: VNode, module: Module): VNode {
71 | return module.create(emptyVNode, vNode)
72 | }, vNode, modules)
73 |
74 | const create = vNode.data.insert || id
75 |
76 | return create(emptyVNode, updatedVNode)
77 | }
78 |
79 | // recursively calls destroy hook on a VNode and all it's children
80 | function callDestroyHooks (vNode: VNode) {
81 | const { data, children } = vNode
82 | if (data.destroy) data.destroy(vNode)
83 |
84 | forEach(function (module: Module) {
85 | module.destroy(vNode)
86 | }, modules)
87 |
88 | if (children.length > 0) {
89 | forEach(callDestroyHooks, children)
90 | }
91 | }
92 |
93 | // calls remove hooks
94 | function callRemoveHooks (vNode: VNode, remove: Function) {
95 | const { data } = vNode
96 |
97 | forEach(function (module: Module) {
98 | module.remove(vNode, remove)
99 | }, modules)
100 |
101 | if (data.remove) {
102 | data.remove(vNode, remove)
103 | } else {
104 | remove()
105 | }
106 | }
107 |
108 | // create a new VNode which contains an element
109 | function createElement (vNode: VNode): VNode {
110 | const { tagName, id, classList, children: VNodeChildren, text, data } = vNode
111 |
112 | const element = data.ns
113 | ? document.createElementNS(data.ns, tagName)
114 | : document.createElement(tagName)
115 |
116 | if (id) element.id = id
117 | if (classList.length > 0) element.className = classList.join(' ')
118 |
119 | const children = map(createElement, VNodeChildren)
120 |
121 | if (children.length > 0) {
122 | forEach(appendChild(element), children)
123 | } else if (text && text.length > 0) {
124 | setTextContent(element, text)
125 | }
126 |
127 | const updatedVNode = vNode.setChildren(children).setElement(element)
128 |
129 | return callCreateHooks(updatedVNode)
130 | }
131 |
132 | function createRemoveCallback (childElement: any, listeners: number): Function {
133 | return function () {
134 | if (--listeners === 0) {
135 | const parent = childElement.parentNode
136 | removeChild(parent, childElement)
137 | }
138 | }
139 | }
140 |
141 | function removeVNodes (parent: Node, vNodes: VNode[], startIndex: number, endIndex: number) {
142 | for (; startIndex < endIndex; ++startIndex) {
143 | const listeners = modules.length + 1
144 | const child = vNodes[startIndex]
145 | if (child) {
146 | if (child.tagName !== '') {
147 | callDestroyHooks(child)
148 | const rm = createRemoveCallback(child.element, listeners)
149 | callRemoveHooks(child, rm)
150 | } else { // Text Node
151 | removeChild(parent, child.element)
152 | }
153 | }
154 | }
155 | }
156 |
157 | function addVNodes (parent: any, vNode: VNode, before: any, startIndex: number, endIndex: number): VNode {
158 | const { children } = vNode
159 |
160 | if (startIndex >= children.length) {
161 | return vNode
162 | }
163 |
164 | const childrenWithElements = map(function (child: VNode, index: number): VNode {
165 | if (index >= startIndex && index <= endIndex) {
166 | insertBefore(parent, child.element, before)
167 | return callInsert(child)
168 | }
169 | return child
170 | }, map(createElement, children))
171 |
172 | return vNode.setChildren(childrenWithElements)
173 | }
174 |
175 | type UpdateChildren = {
176 | parent: any,
177 | // $flow-ignore-line
178 | oldChildren: Array,
179 | vNode: VNode,
180 | oldKeyToIndex: Object | null,
181 | oldStartIndex: number,
182 | oldEndIndex: number,
183 | newStartIndex: number,
184 | newEndIndex: number,
185 | oldStartVNode: VNode,
186 | oldEndVNode: VNode,
187 | newStartVNode: VNode,
188 | newEndVNode: VNode
189 | }
190 |
191 | // update children when they have changed
192 | function updateChildren ({parent, oldChildren, vNode, oldKeyToIndex,
193 | oldStartIndex, oldEndIndex, newStartIndex, newEndIndex,
194 | oldStartVNode, oldEndVNode, newStartVNode, newEndVNode }: UpdateChildren): VNode {
195 | const previousInput = arguments[0]
196 | const { children } = vNode
197 |
198 | if (oldStartIndex > oldEndIndex) {
199 | const before = isUndef(children[newEndIndex + 1]) ? null : children[newEndIndex + 1].element
200 |
201 | return addVNodes(parent, vNode, before, newStartIndex, newEndIndex)
202 | }
203 |
204 | if (newStartIndex > newEndIndex) {
205 | removeVNodes(parent, oldChildren, oldStartIndex, oldEndIndex)
206 | return vNode
207 | }
208 |
209 | // VNode has moved left in child array
210 | if (isUndef(oldStartVNode)) {
211 | const _oldStartIndex = oldStartIndex + 1
212 | return updateChildren({ ...previousInput,
213 | oldStartVNode: oldChildren[_oldStartIndex],
214 | oldStartIndex: _oldStartIndex
215 | })
216 | }
217 |
218 | if (isUndef(oldEndVNode)) {
219 | const _oldEndIndex = oldEndIndex - 1
220 | return updateChildren({...previousInput,
221 | oldEndVNode: oldChildren[_oldEndIndex],
222 | oldEndIndex: _oldEndIndex
223 | })
224 | }
225 |
226 | if (sameVNode(oldStartVNode, newStartVNode)) {
227 | const _updatedVNode = patchVNode(oldStartVNode, newStartVNode)
228 | const newChildren = replace(_updatedVNode, newStartIndex, children)
229 |
230 | const _oldStartIndex = oldStartIndex + 1
231 | const _newStartIndex = newStartIndex + 1
232 |
233 | return updateChildren({...previousInput,
234 | vNode: vNode.setChildren(newChildren),
235 | oldStartIndex: _oldStartIndex,
236 | newStartIndex: _newStartIndex,
237 | oldStartVNode: oldChildren[_oldStartIndex],
238 | newStartVNode: children[_newStartIndex]
239 | })
240 | }
241 |
242 | if (sameVNode(oldEndVNode, newEndVNode)) {
243 | const updatedVNode = patchVNode(oldEndVNode, newEndVNode)
244 | const newChildren = replace(updatedVNode, newEndIndex, children)
245 |
246 | const _oldEndIndex = oldEndIndex - 1
247 | const _newEndIndex = newEndIndex - 1
248 |
249 | return updateChildren({...previousInput,
250 | vNode: vNode.setChildren(newChildren),
251 | oldEndIndex: _oldEndIndex,
252 | newEndIndex: _newEndIndex,
253 | oldEndVNode: oldChildren[_oldEndIndex],
254 | newEndVNode: children[_newEndIndex]
255 | })
256 | }
257 |
258 | // vNode has moved right in the array
259 | if (sameVNode(oldStartVNode, newEndVNode)) {
260 | const updatedVNode = patchVNode(oldStartVNode, newEndVNode)
261 | const newChildren = replace(updatedVNode, newEndIndex, children)
262 |
263 | insertBefore(parent, oldStartVNode.element, nextSibling(oldEndVNode.element))
264 |
265 | const _oldStartIndex = oldStartIndex + 1
266 | const _newEndIndex = newEndIndex - 1
267 |
268 | return updateChildren({...previousInput,
269 | vNode: vNode.setChildren(newChildren),
270 | oldStartIndex: _oldStartIndex,
271 | newEndIndex: _newEndIndex,
272 | oldStartVNode: oldChildren[_oldStartIndex],
273 | newEndVNode: children[_newEndIndex]
274 | })
275 | }
276 |
277 | // vNode moved left
278 | if (sameVNode(oldEndVNode, newStartVNode)) {
279 | const updatedVNode = patchVNode(oldEndVNode, newStartVNode)
280 | const newChildren = replace(updatedVNode, newStartIndex, children)
281 |
282 | insertBefore(parent, oldEndVNode.element, oldStartVNode.element)
283 |
284 | const _oldEndIndex = oldEndIndex - 1
285 | const _newStartIndex = newStartIndex + 1
286 |
287 | return updateChildren({...previousInput,
288 | vNode: vNode.setChildren(newChildren),
289 | oldEndIndex: _oldEndIndex,
290 | newStartIndex: _newStartIndex,
291 | oldEndVNode: oldChildren[_oldEndIndex],
292 | newStartVNode: children[_newStartIndex]
293 | })
294 | }
295 |
296 | const _oldKeyToIndex = oldKeyToIndex === null
297 | ? createKeyToIndex(oldChildren, oldStartIndex, oldEndIndex)
298 | : oldKeyToIndex
299 |
300 | const indexInOld = _oldKeyToIndex[newStartVNode.key]
301 |
302 | if (isUndef(indexInOld)) {
303 | // new element
304 | const _vNode = createElement(newStartVNode)
305 | insertBefore(parent, _vNode.element, oldStartVNode.element)
306 |
307 | const _newStartIndex = newStartIndex + 1
308 | const _newStartVNode = children[_newStartIndex]
309 |
310 | return updateChildren({...previousInput,
311 | oldKeyToIndex: _oldKeyToIndex,
312 | vNode: callInsert(_vNode),
313 | newStartIndex: _newStartIndex,
314 | newStartVNode: _newStartVNode
315 | })
316 | }
317 |
318 | const elementToMove = oldChildren[indexInOld]
319 | const updatedVNode = patchVNode(elementToMove, newStartVNode)
320 | const newChildren = replace(updatedVNode, newStartIndex, children)
321 | const _vNode = vNode.setChildren(newChildren)
322 | // $flow-ignore-line
323 | oldChildren[indexInOld] = undefined
324 | insertBefore(parent, elementToMove.element, oldStartVNode.element)
325 | const _newStartIndex = newStartIndex + 1
326 | const _newStartVNode = children[_newStartIndex]
327 | return updateChildren({...previousInput,
328 | oldKeyToIndex: _oldKeyToIndex,
329 | vNode: _vNode,
330 | newStartIndex: _newStartIndex,
331 | newStartVNode: _newStartVNode
332 | })
333 | }
334 |
335 | // updates the DOM and VNode with the current information it should have
336 | function patchVNode (oldVNode: VNode, _vNode: VNode): VNode {
337 | // $flow-ignore-line
338 | const vNode = _vNode.setElement(oldVNode.element)
339 | // if the previous and current are equal do nothing
340 | if (oldVNode === vNode) return vNode
341 |
342 | // if the vNode has changed drastically create a new one an replace the old
343 | if (!sameVNode(oldVNode, vNode)) {
344 | const parent = oldVNode.element && oldVNode.element.parentNode
345 | const newVNode = createElement(vNode)
346 | insertBefore(parent, newVNode.element, oldVNode.element)
347 | const updatedVNode = callInsert(newVNode)
348 | if (parent !== null && parent !== undefined) {
349 | removeVNodes(parent, [oldVNode], 0, 0)
350 | }
351 | return updatedVNode
352 | }
353 |
354 | const updatedVNode = callUpdateHooks(oldVNode, vNode)
355 |
356 | const { element, children } = updatedVNode
357 |
358 | // lets update the DOM
359 | if (updatedVNode.text === '') {
360 | if (oldVNode.children.length > 0 && children.length > 0) { // children have changed somehow
361 | if (oldVNode.children === updatedVNode.children) return updatedVNode
362 |
363 | const oldEndIndex = oldVNode.children.length - 1
364 | const newEndIndex = children.length - 1
365 |
366 | const oldStartVNode = oldVNode.children[0]
367 | const oldEndVNode = oldVNode.children[oldEndIndex]
368 | const newStartVNode = children[0]
369 | const newEndVNode = children[newEndIndex]
370 |
371 | const input: UpdateChildren = {
372 | parent: oldVNode.element,
373 | oldChildren: oldVNode.children,
374 | vNode: updatedVNode,
375 | oldKeyToIndex: null,
376 | oldStartIndex: 0,
377 | oldEndIndex,
378 | newStartIndex: 0,
379 | newEndIndex,
380 | oldStartVNode,
381 | oldEndVNode,
382 | newStartVNode,
383 | newEndVNode
384 | }
385 |
386 | return updateChildren(input)
387 | } else if (children.length > 0) { // children have been added when there were none
388 | if (oldVNode.text !== '') setTextContent(element, '')
389 | return addVNodes(element, updatedVNode, null, 0, updatedVNode.children.length - 1)
390 | } else if (oldVNode.children.length > 0) { // children have been completely removed
391 | if (updatedVNode.element) {
392 | removeVNodes(updatedVNode.element, oldVNode.children, 0, oldVNode.children.length - 1)
393 | }
394 | return updatedVNode
395 | } else if (oldVNode.text !== '') { // text has been removed
396 | setTextContent(updatedVNode.element, '')
397 | return updatedVNode
398 | }
399 | } else if (oldVNode.text !== updatedVNode.text) { // update text if needed
400 | setTextContent(element, updatedVNode.text)
401 | }
402 |
403 | return updatedVNode
404 | }
405 |
406 | return function patch (oldVNode: VNode | HTMLElement, vNode: VNode): VNode {
407 | if (oldVNode === vNode) return vNode
408 |
409 | const previousVNode = oldVNode instanceof HTMLElement
410 | ? emptyVNodeAt(oldVNode)
411 | : oldVNode
412 |
413 | if (sameVNode(previousVNode, vNode)) {
414 | return patchVNode(previousVNode, vNode)
415 | }
416 |
417 | const { element } = previousVNode
418 | const parent = element && element.parentNode
419 |
420 | const newVNode = createElement(vNode)
421 |
422 | if (parent) {
423 | insertBefore(parent, newVNode.element, element && element.nextSibling)
424 | return callInsert(newVNode)
425 | }
426 |
427 | return newVNode
428 | }
429 | }
430 |
--------------------------------------------------------------------------------
/src/interfaces.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 |
3 | import { VNode } from './hyperscript/VNode'
4 |
5 | export type CreateHook = (oldVNode: VNode, vNode: VNode) => VNode;
6 | export type UpdateHook = (oldVNode: VNode, vNode: VNode) => VNode;
7 | export type InsertHook = (vNode: VNode) => VNode;
8 | export type RemoveHook = (vNode: VNode, removeCallback: () => void) => void;
9 | export type DestroyHook = (vNode: VNode) => void;
10 |
11 | export type Module = {
12 | create: CreateHook,
13 | update: UpdateHook,
14 | insert: InsertHook,
15 | remove: RemoveHook,
16 | destroy: DestroyHook
17 | }
18 |
--------------------------------------------------------------------------------
/src/module/properties.js:
--------------------------------------------------------------------------------
1 | import { reduce } from '../util'
2 | import { VNode } from '../hyperscript/VNode'
3 |
4 | export function update (oldVNode: VNode, vNode: VNode): VNode {
5 | const oldProps = oldVNode.data.props || {}
6 | const props = vNode.data.props || {}
7 |
8 | if (!oldProps && !props) return vNode
9 |
10 | const keys = Object.keys(props)
11 |
12 | const element = reduce(function (element, key) {
13 | if (!props[key]) {
14 | delete element[key]
15 | }
16 | return element
17 | }, vNode.data.element, keys)
18 |
19 | const newElement = reduce(function (element, key) {
20 | const cur = props[key]
21 | const old = oldProps[key]
22 |
23 | if (old !== cur && key !== 'value' || element[key] !== cur) {
24 | element[key] = cur
25 | }
26 |
27 | return element
28 | }, element, keys)
29 |
30 | return vNode.setElement(newElement)
31 | }
32 |
33 | export const create = update
34 |
35 | export function remove (vNode: VNode, rm: Function) {
36 | rm()
37 | }
38 |
39 | export function destroy (vNode: VNode) {}
40 |
41 | export function insert (vNode: VNode) {
42 | return vNode
43 | }
44 |
45 | export const module = {
46 | update,
47 | create,
48 | insert,
49 | remove,
50 | destroy
51 | }
52 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | /* @flow */
2 | import type { VNode } from './hyperscript/VNode'
3 | import { FlowVNode } from './hyperscript/VNode' // eslint-disable-line
4 |
5 | export function isUndef (x: any): boolean {
6 | return x === void 0
7 | }
8 |
9 | export function emptyVNodeAt (node: Element): VNode {
10 | return new FlowVNode(node.tagName.toLowerCase(), node.id || '', copy(node.classList), {}, [], '', node, null)
11 | }
12 |
13 | export function sameVNode (a: VNode, b: VNode): boolean {
14 | return a.selector === b.selector && a.key === b.key
15 | }
16 |
17 | export function forEach (f: Function, arr: Array) {
18 | const length = arr.length
19 | for (let i = 0; i < length; ++i) { // eslint-disable-line immutable/no-let
20 | f(arr[i], i)
21 | }
22 | }
23 |
24 | // copy :: [a] -> [a]
25 | // duplicate a (shallow duplication)
26 | export function copy (a: any): any[] {
27 | const l = a.length
28 | const b = new Array(l)
29 | for (let i = 0; i < l; ++i) { // eslint-disable-line immutable/no-let
30 | b[i] = a[i]
31 | }
32 | return b
33 | }
34 |
35 | // map :: (a -> b) -> [a] -> [b]
36 | // transform each element with f
37 | export function map (f: Function, a: any): any[] {
38 | const l = a.length
39 | const b = new Array(l)
40 | for (let i = 0; i < l; ++i) { // eslint-disable-line immutable/no-let
41 | b[i] = f(a[i], i)
42 | }
43 | return b
44 | }
45 |
46 | // reduce :: (a -> b -> a) -> a -> [b] -> a
47 | // accumulate via left-fold
48 | export function reduce (f: Function, z: any, a: any): any {
49 | let r = z // eslint-disable-line immutable/no-let
50 | for (let i = 0, l = a.length; i < l; ++i) { // eslint-disable-line immutable/no-let
51 | r = f(r, a[i], i)
52 | }
53 | return r
54 | }
55 |
56 | // replace :: a -> Int -> [a]
57 | // replace element at index
58 | export function replace (x: any, i: number, a: any): any[] {
59 | if (i < 0) {
60 | throw new TypeError('i must be >= 0')
61 | }
62 |
63 | const l = a.length
64 | const b = new Array(l)
65 | for (let j = 0; j < l; ++j) { // eslint-disable-line immutable/no-let
66 | b[j] = i === j ? x : a[j]
67 | }
68 | return b
69 | }
70 |
--------------------------------------------------------------------------------