├── .babelrc ├── .gitignore ├── webpack.config.js ├── docs └── index.html ├── package.json ├── README.md └── src └── index.jsx /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "react" 5 | ] 6 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | 3 | node_modules 4 | 5 | # Tern 6 | .tern-port 7 | 8 | # Mac 9 | .DS_Store 10 | 11 | .idea 12 | 13 | # gulp-gh-pages 14 | .publish 15 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | output: { 5 | filename: "bundle.js", 6 | path: path.resolve(__dirname, "docs"), 7 | }, 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.jsx?$/, 12 | exclude: /node_modules/, 13 | use: { 14 | loader: "babel-loader", 15 | }, 16 | }, 17 | ], 18 | }, 19 | resolve: { 20 | extensions: [".js", ".jsx"], 21 | }, 22 | 23 | devServer: { 24 | port: 9000, 25 | host: "localhost", 26 | historyApiFallback: true, 27 | noInfo: false, 28 | stats: "minimal", 29 | contentBase: path.join(__dirname, "docs"), 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | UI Benchmark 7 | 8 | 9 | 10 |
11 | 12 | 13 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "uibench", 4 | "version": "0.1.0", 5 | "description": "UI Benchmark", 6 | "homepage": "https://github.com/localvoid/uibench", 7 | "repository": "https://github.com/localvoid/uibench", 8 | "author": { 9 | "name": "Boris Kaul", 10 | "email": "localvoid@gmail.com", 11 | "url": "https://github.com/localvoid" 12 | }, 13 | "keywords": [ 14 | "uibench", 15 | "benchmark" 16 | ], 17 | "scripts": { 18 | "dist": "webpack --mode production", 19 | "serve": "webpack-dev-server" 20 | }, 21 | "dependencies": { 22 | "d3-array": "^1.2.1", 23 | "d3-scale": "^2.0.0", 24 | "inkdrop": "^0.2.2", 25 | "react": "^16.3.2", 26 | "react-dom": "^16.3.2", 27 | "urijs": "^1.19.1" 28 | }, 29 | "devDependencies": { 30 | "babel-core": "^6.26.3", 31 | "babel-loader": "^7.1.4", 32 | "babel-preset-env": "^1.6.1", 33 | "babel-preset-react": "^6.24.1", 34 | "webpack": "^4.8.0", 35 | "webpack-cli": "^2.1.3", 36 | "webpack-dev-server": "^3.1.4" 37 | } 38 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UI Benchmark 2 | 3 | [Open](https://localvoid.github.io/uibench/) 4 | 5 | ## Benchmark implementations query parameters 6 | 7 | ### i 8 | 9 | Number of iterations. Default: `10`. 10 | 11 | `http://localhost:8000/?i=5` 12 | 13 | ### name 14 | 15 | Override name. 16 | 17 | `http://localhost:8000/?name=Test` 18 | 19 | ### version 20 | 21 | Override version. 22 | 23 | `http://localhost:8000/?version=0.0.1-a` 24 | 25 | ### report 26 | 27 | Push report to the parent window. Default: `false`. 28 | 29 | `http://localhost:8000/?report=true` 30 | 31 | ### mobile 32 | 33 | Reduce number of DOM elements in tests. Default: `false`. 34 | 35 | `http://localhost:8000/?mobile=true` 36 | 37 | ### enableDOMRecycling 38 | 39 | Enable DOM recycling for implementations that support enabling/disabling DOM recycling. Default: `false`. 40 | 41 | `http://localhost:8000/?enableDOMRecycling=true` 42 | 43 | ### filter 44 | 45 | Filter tests by name (regexp). 46 | 47 | `http://localhost:8000/?filter=render` 48 | 49 | ### fullRenderTime 50 | 51 | Measure full render time (recalc style/layout/paint/composition/etc). Default: `false`. 52 | 53 | `http://localhost:8000/?fullRenderTime=true` 54 | 55 | ### timelineMarks 56 | 57 | Add marks to the Dev Tools timeline. Default: `false`. 58 | 59 | `http://localhost:8000/?timelineMarks=true` 60 | 61 | ### disableChecks 62 | 63 | Disable internal tests. Useful for experimenting with DOM structure changes. Default: `false`. 64 | 65 | `http://localhost:8000/?disableChecks=true` 66 | 67 | ### startDelay 68 | 69 | Add delay in ms before starting tests. Default: `0`. 70 | 71 | `http://localhost:8000/?startDelay=3000` 72 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import uri from 'urijs'; 4 | import { median as d3median, mean as d3mean, min as d3min, max as d3max } from 'd3-array'; 5 | import { scaleLinear } from 'd3-scale'; 6 | import { LchColor, lchToRgb, formatRgbToHex } from 'inkdrop'; 7 | 8 | function stdev(items) { 9 | const m = d3mean(items); 10 | 11 | const variance = d3mean(items.map((i) => { 12 | const diff = i - m; 13 | return diff * diff; 14 | })); 15 | 16 | return Math.sqrt(variance); 17 | } 18 | 19 | const GreenColor = formatRgbToHex(lchToRgb(new LchColor(0.9, 0.4, 140 / 360))); 20 | const RedColor = formatRgbToHex(lchToRgb(new LchColor(0.9, 0.4, 30 / 360))); 21 | 22 | function processResults(results) { 23 | const min = d3min(results); 24 | const max = d3max(results); 25 | const scale = scaleLinear().domain([min, max]); 26 | const diff = results.map((r) => r === min ? 0 : r / min); 27 | const colors = results.map((r) => r === min ? 28 | GreenColor : 29 | formatRgbToHex(lchToRgb(new LchColor(0.9, 0.4, (30 + 110 * (1 - scale(r))) / 360)))); 30 | 31 | return { 32 | min: min, 33 | max: max, 34 | scale: scale, 35 | values: results, 36 | diff: diff, 37 | colors: colors, 38 | } 39 | } 40 | 41 | function generateHtmlReport(results) { 42 | const header = ` 43 | 44 | 45 | UI Benchmark Report: ${navigator.userAgent} 46 | 47 | `; 48 | 49 | const footer = ``; 50 | 51 | const sampleNames = results.sampleNames; 52 | const reports = results.reports; 53 | 54 | let body = ''; 55 | 56 | const titles = reports.map((r) => `${r.name} ${r.version}`).join(''); 57 | const fullRenderFlags = reports.map((r) => ``).join(''); 58 | const preserveStateFlags = reports.map((r) => ``).join(''); 59 | const scuFlags = reports.map((r) => ``).join(''); 60 | const recyclingFlags = reports.map((r) => ``).join(''); 61 | const disableChecksFlags = reports.map((r) => ``).join(''); 62 | 63 | const jsInitTimes = processResults(reports.map((r) => r.timing.run - r.timing.start)); 64 | const firstRenderTimes = processResults(reports.map((r) => r.timing.firstRender)); 65 | const jsInitCols = []; 66 | const firstRenderCols = []; 67 | 68 | for (let i = 0; i < jsInitTimes.values.length; i++) { 69 | const value = jsInitTimes.values[i]; 70 | const diff = jsInitTimes.diff[i] ? ` (${(jsInitTimes.diff[i]).toFixed(2)})` : ''; 71 | const style = `background:${jsInitTimes.colors[i]}`; 72 | jsInitCols.push(`${Math.round(value * 1000)}${diff}`); 73 | } 74 | 75 | for (let i = 0; i < firstRenderTimes.values.length; i++) { 76 | const value = firstRenderTimes.values[i]; 77 | const diff = firstRenderTimes.diff[i] ? ` (${(firstRenderTimes.diff[i]).toFixed(2)})` : ''; 78 | const style = `background:${firstRenderTimes.colors[i]}`; 79 | firstRenderCols.push(`${Math.round(value * 1000)}${diff}`); 80 | } 81 | 82 | const rows = []; 83 | const overallTime = reports.map((r) => 0); 84 | 85 | for (let i = 0; i < sampleNames.length; i++) { 86 | const sampleName = sampleNames[i]; 87 | const cols = [`${sampleName}`]; 88 | 89 | const values = reports.map((r) => { 90 | const samples = r.samples[sampleName]; 91 | 92 | return { 93 | sampleCount: samples.length, 94 | median: d3median(samples), 95 | mean: d3mean(samples), 96 | stdev: stdev(samples), 97 | min: d3min(samples), 98 | max: d3max(samples), 99 | }; 100 | }); 101 | 102 | const medianValues = values.map((v) => v.median); 103 | const results = processResults(medianValues); 104 | 105 | for (let j = 0; j < reports.length; j++) { 106 | const report = reports[j]; 107 | const value = values[j]; 108 | const style = `background:${results.colors[j]}`; 109 | const title = `mean: ${Math.round(value.mean * 1000).toString()}\n` + 110 | `stdev: ${Math.round(value.stdev * 1000).toString()}\n` + 111 | `min: ${Math.round(value.min * 1000).toString()}\n` + 112 | `max: ${Math.round(value.max * 1000).toString()}`; 113 | 114 | const diff = results.diff[j] ? ` (${(results.diff[j]).toFixed(2)})` : ''; 115 | cols.push(`${Math.round(value.median * 1000)}${diff}`); 116 | 117 | overallTime[j] += Math.round(value.median * 1000); 118 | } 119 | 120 | rows.push(`${cols.join('\n')}`); 121 | } 122 | 123 | body += ` 124 |
125 |
UI Benchmark Report generated by https://localvoid.github.io/uibench/
126 |
127 |

User Agent: ${navigator.userAgent}

128 |

Flags:

129 | 137 |

Notes:

138 | 156 |

Tests:

157 | 180 | 181 | ${titles} 182 | 183 | 184 | ${fullRenderFlags} 185 | ${preserveStateFlags} 186 | ${scuFlags} 187 | ${recyclingFlags} 188 | ${disableChecksFlags} 189 | 190 | ${jsInitCols.join('')} 191 | ${firstRenderCols.join('')} 192 | ${overallTime.map((t) => ``).join('')} 193 | ${reports.map((r) => ``).join('')} 194 | ${rows.join('\n')} 195 | 196 |
Flags:
Measure Full Render Time
Preserve State
sCU Optimization
DOM Recycling
Spec Tests
Times:
JS Init Time
First Render Time
Overall Tests Time${t}
Iterations${r.iterations}
197 |
198 |
199 | `; 200 | 201 | return header + body + footer; 202 | } 203 | 204 | class Results { 205 | constructor() { 206 | this.reports = []; 207 | this.sampleNames = []; 208 | this.sampleNamesIndex = {}; 209 | } 210 | 211 | update(data) { 212 | this.reports.push(data); 213 | 214 | const keys = Object.keys(data.samples); 215 | for (let i = 0; i < keys.length; i++) { 216 | const sampleName = keys[i]; 217 | const v = this.sampleNamesIndex[sampleName]; 218 | if (v === undefined) { 219 | this.sampleNamesIndex[sampleName] = this.sampleNames.length; 220 | this.sampleNames.push(sampleName); 221 | } 222 | } 223 | } 224 | } 225 | 226 | class Header extends React.Component { 227 | shouldComponentUpdate() { 228 | return false; 229 | } 230 | 231 | render() { 232 | return ( 233 |
234 |
235 |

UI Benchmark

236 |

To start benchmarking, click on a button below library name that you want to test, it will 237 | open a new window, perform tests and send results back to the main window, results will be displayed 238 | at the bottom section "Results".

239 |

In the "Results" section there will be different test cases, for example test 240 | case table/[100,4]/render represents update from empty table to table with 100 rows and 4 241 | columns. Test case table/[100,4]/filter/32 is an update from table with 100 rows and 4 242 | columns to the same table where each 32th item is removed. Details about all test cases can be found inside 243 | the uibench.js file.

244 |

245 | Star 246 |

247 |
248 |
249 | ); 250 | } 251 | } 252 | 253 | function _createQuery(opts) { 254 | const q = { 255 | report: true, 256 | i: opts.iterations, 257 | }; 258 | if (opts.disableSCU) { 259 | q.disableSCU = true; 260 | } 261 | if (opts.enableDOMRecycling) { 262 | q.enableDOMRecycling = true; 263 | } 264 | if (opts.mobileMode) { 265 | q.mobile = true; 266 | } 267 | if (opts.testFilter) { 268 | q.filter = opts.testFilter; 269 | } 270 | if (opts.fullRenderTime) { 271 | q.fullRenderTime = true; 272 | } 273 | 274 | return q; 275 | } 276 | 277 | class Contestant extends React.Component { 278 | shouldComponentUpdate() { 279 | return false; 280 | } 281 | 282 | openWindow(e) { 283 | window.open(uri(this.props.benchmarkUrl).addQuery(_createQuery(this.props.opts)), '_blank'); 284 | } 285 | 286 | openVersionWindow(version) { 287 | window.open(uri(this.props.benchmarkUrl + version + '/' + this.props.page).addQuery(_createQuery(this.props.opts)), '_blank'); 288 | } 289 | 290 | render() { 291 | const buttons = this.props.versions === undefined ? 292 | : 293 | this.props.versions.map((v) => ); 294 | 295 | return ( 296 |
297 |

{this.props.name}

298 |

{this.props.comments}

299 |
{buttons}
300 |
301 | ); 302 | } 303 | } 304 | 305 | class CustomContestant extends React.Component { 306 | constructor(props) { 307 | super(props); 308 | let url = localStorage['customURL']; 309 | if (url === void 0) { 310 | url = ''; 311 | } 312 | this.state = { url: url }; 313 | 314 | this.changeUrl = this.changeUrl.bind(this); 315 | this.openWindow = this.openWindow.bind(this); 316 | } 317 | 318 | changeUrl(e) { 319 | const v = e.target.value; 320 | localStorage['customURL'] = v; 321 | this.setState({ url: v }); 322 | } 323 | 324 | openWindow(e) { 325 | window.open(uri(this.state.url).addQuery(_createQuery(this.props.opts)), '_blank'); 326 | } 327 | 328 | render() { 329 | return ( 330 |
331 |

Custom URL

332 |
333 | 334 | 335 | 336 | 337 |
338 |
339 | ); 340 | } 341 | } 342 | 343 | class Contestants extends React.Component { 344 | render() { 345 | const props = this.props; 346 | return ( 347 |
348 | {props.contestants.map((c) => )} 349 | 350 |
351 | ) 352 | } 353 | } 354 | 355 | class ResultsTable extends React.Component { 356 | constructor(props) { 357 | super(props); 358 | this.state = { 359 | filter: '' 360 | } 361 | 362 | this.handleFilterChange = this._handleFilterChange.bind(this); 363 | this.exportAsHtml = this._exportAsHtml.bind(this); 364 | this.exportAsJson = this._exportAsJson.bind(this); 365 | } 366 | 367 | _exportAsHtml(e) { 368 | e.currentTarget.href = encodeURI('data:text/html;charset=utf-8,' + generateHtmlReport(this.props.results)); 369 | } 370 | 371 | _exportAsJson(e) { 372 | e.currentTarget.href = encodeURI('data:text/json;charset=utf-8,' + JSON.stringify(this.props.results.reports, null, ' ')); 373 | } 374 | 375 | _handleFilterChange(e) { 376 | this.setState({ filter: e.target.value }); 377 | } 378 | 379 | render() { 380 | const filter = this.state.filter || ''; 381 | const results = this.props.results; 382 | const sampleNames = results.sampleNames; 383 | const reports = results.reports; 384 | 385 | if (reports.length === 0) { 386 | return ( 387 |
388 |
Results (lower is better)
389 |
Empty
390 |
391 | ); 392 | } 393 | 394 | const titles = reports.map((r) => {r.name} {r.version}); 395 | const fullRenderFlags = reports.map((r) => ); 396 | const preserveStateFlags = reports.map((r) => ); 397 | const scuFlags = reports.map((r) => ); 398 | const recyclingFlags = reports.map((r) => ); 399 | const disableChecksFlags = reports.map((r) => ); 400 | const iterations = reports.map((r) => {r.iterations}); 401 | 402 | const jsInitTimes = processResults(reports.map((r) => r.timing.run - r.timing.start)); 403 | const firstRenderTimes = processResults(reports.map((r) => r.timing.firstRender)); 404 | const jsInitCols = []; 405 | const firstRenderCols = []; 406 | 407 | for (let i = 0; i < jsInitTimes.values.length; i++) { 408 | const value = jsInitTimes.values[i]; 409 | const diff = jsInitTimes.diff[i] ? {`(${(jsInitTimes.diff[i]).toFixed(2)})`} : null; 410 | const style = { background: jsInitTimes.colors[i] }; 411 | jsInitCols.push({Math.round(value * 1000)} {diff}); 412 | } 413 | 414 | for (let i = 0; i < firstRenderTimes.values.length; i++) { 415 | const value = firstRenderTimes.values[i]; 416 | const diff = firstRenderTimes.diff[i] ? {`(${(firstRenderTimes.diff[i]).toFixed(2)})`} : null; 417 | const style = { background: firstRenderTimes.colors[i] }; 418 | firstRenderCols.push({Math.round(value * 1000)} {diff}); 419 | } 420 | 421 | const rows = []; 422 | const overallTime = reports.map((r) => 0); 423 | 424 | for (let i = 0; i < sampleNames.length; i++) { 425 | const sampleName = sampleNames[i]; 426 | if (sampleName.indexOf(filter) === -1) { 427 | continue; 428 | } 429 | 430 | const cols = [{sampleName}]; 431 | 432 | const values = reports.map((r) => { 433 | const samples = r.samples[sampleName]; 434 | 435 | return { 436 | sampleCount: samples.length, 437 | median: d3median(samples), 438 | mean: d3mean(samples), 439 | stdev: stdev(samples), 440 | min: d3min(samples), 441 | max: d3max(samples), 442 | }; 443 | }); 444 | 445 | const medianValues = values.map((v) => v.median); 446 | const results = processResults(medianValues); 447 | 448 | for (let j = 0; j < reports.length; j++) { 449 | const report = reports[j]; 450 | const value = values[j]; 451 | const style = { background: results.colors[j] }; 452 | const title = `mean: ${Math.round(value.mean * 1000).toString()}\n` + 453 | `stdev: ${Math.round(value.stdev * 1000).toString()}\n` + 454 | `min: ${Math.round(value.min * 1000).toString()}\n` + 455 | `max: ${Math.round(value.max * 1000).toString()}`; 456 | 457 | const diff = results.diff[j] ? {`(${(results.diff[j]).toFixed(2)})`} : null; 458 | cols.push({Math.round(value.median * 1000)} {diff}); 459 | 460 | overallTime[j] += Math.round(value.median * 1000); 461 | } 462 | 463 | rows.push({cols}); 464 | } 465 | 466 | return ( 467 |
468 |
Results (lower is better)
469 |
470 |
471 | Export as HTML Export as JSON 472 |
473 |

Flags:

474 | 482 |

Notes:

483 | 501 |

Tests:

502 | 525 |
526 | Filter 527 | 528 |
529 | 530 | {titles} 531 | 532 | 533 | {fullRenderFlags} 534 | {preserveStateFlags} 535 | {scuFlags} 536 | {recyclingFlags} 537 | {disableChecksFlags} 538 | 539 | {jsInitCols} 540 | {firstRenderCols} 541 | {overallTime.map((t) => )} 542 | {iterations} 543 | {rows} 544 | 545 |
Flags:
Measure Full Render Time
Preserve State
sCU Optimization
DOM Recycling
Spec Tests
Times:
JS Init Time
First Render Time
Overall Tests Time{t}
Iterations
546 |
547 |
548 | ); 549 | } 550 | } 551 | 552 | class Main extends React.Component { 553 | constructor(props) { 554 | super(props); 555 | this.state = { 556 | fullRenderTime: false, 557 | disableSCU: true, 558 | enableDOMRecycling: false, 559 | mobileMode: false, 560 | iterations: 5, 561 | filter: '', 562 | }; 563 | 564 | this.onFullRenderTimeChange = this.onFullRenderTimeChange.bind(this); 565 | this.onMobileModeChange = this.onMobileModeChange.bind(this); 566 | this.onDisableSCUChange = this.onDisableSCUChange.bind(this); 567 | this.onEnableDOMRecyclingChange = this.onEnableDOMRecyclingChange.bind(this); 568 | this.onIterationsChange = this.onIterationsChange.bind(this); 569 | this.onTestFilterChange = this.onTestFilterChange.bind(this); 570 | } 571 | 572 | onFullRenderTimeChange(e) { 573 | this.setState({ fullRenderTime: e.target.checked }); 574 | } 575 | 576 | onMobileModeChange(e) { 577 | this.setState({ mobileMode: e.target.checked }); 578 | } 579 | 580 | onDisableSCUChange(e) { 581 | this.setState({ disableSCU: e.target.checked }); 582 | } 583 | 584 | onEnableDOMRecyclingChange(e) { 585 | this.setState({ enableDOMRecycling: e.target.checked }); 586 | } 587 | 588 | onIterationsChange(e) { 589 | this.setState({ iterations: e.target.value }); 590 | } 591 | 592 | onTestFilterChange(e) { 593 | this.setState({ testFilter: e.target.value }); 594 | } 595 | 596 | render() { 597 | return ( 598 |
599 |
600 |
601 |
602 |
603 |
604 | 608 |
609 |
610 | 614 |
615 |
616 | 620 |
621 |
622 | 623 | 624 |
625 |
626 | 627 | 628 |
629 |
630 |
631 | 632 | 633 |
634 |
635 | ); 636 | } 637 | } 638 | 639 | const state = { 640 | contestants: [ 641 | { 642 | 'name': 'React', 643 | 'url': 'https://facebook.github.io/react/', 644 | 'benchmarkUrl': 'https://localvoid.github.io/uibench-react/', 645 | 'versions': ['14', '15', '16'], 646 | 'page': 'index.html', 647 | 'comments': 'Virtual DOM. Compiled with: transform-react-inline-elements.', 648 | }, 649 | { 650 | 'name': 'Bobril', 651 | 'url': 'https://github.com/Bobris/Bobril', 652 | 'benchmarkUrl': 'https://bobris.github.io/uibench-bobril/', 653 | 'comments': 'Virtual DOM. Benchmark is implemented as close as possible to React implementation, preserves internal state, all components are stateful, no explicit event delegation, etc.', 654 | }, 655 | { 656 | 'name': 'Preact', 657 | 'url': 'https://github.com/developit/preact', 658 | 'benchmarkUrl': 'https://developit.github.io/uibench-preact/', 659 | 'comments': 'Virtual DOM. Benchmark is implemented in exactly the same way as React implementation.', 660 | }, 661 | { 662 | 'name': 'Imba', 663 | 'url': 'https://github.com/somebee/imba', 664 | 'benchmarkUrl': 'https://somebee.github.io/uibench-imba/', 665 | 'comments': 'Programming language with UI library that has Virtual DOM like API. Using DOM Nodes recycling by default.', 666 | }, 667 | { 668 | 'name': 'Vidom', 669 | 'url': 'https://github.com/dfilatov/vidom', 670 | 'benchmarkUrl': 'https://dfilatov.github.io/uibench-vidom/', 671 | 'comments': 'Virtual DOM. Benchmark is implemented as close as possible to React implementation, preserves internal state, all components are stateful, no explicit event delegation, etc.', 672 | }, 673 | { 674 | 'name': 'DIO.js', 675 | 'url': 'https://github.com/thysultan/dio.js', 676 | 'benchmarkUrl': 'https://dio.js.org/examples/uibench.html', 677 | 'comments': 'Virtual DOM. Benchmark is implemented as close as possible to React implementation, preserves internal state, all components are stateful, no explicit event delegation, etc.', 678 | }, 679 | { 680 | 'name': 'Inferno [optimized]', 681 | 'url': 'https://github.com/infernojs/inferno', 682 | 'benchmarkUrl': 'https://infernojs.github.io/inferno/uibench/', 683 | 'page': 'index.html', 684 | 'comments': 'Virtual DOM. Optimized.', 685 | }, 686 | { 687 | 'name': 'Inferno', 688 | 'url': 'https://github.com/infernojs/inferno', 689 | 'benchmarkUrl': 'https://infernojs.github.io/inferno/uibench-reactlike/', 690 | 'page': 'index.html', 691 | 'comments': 'Virtual DOM. Benchmark is implemented in exactly the same way as React implementation.', 692 | }, 693 | { 694 | 'name': '$mol', 695 | 'url': 'https://github.com/eigenmethod/mol', 696 | 'benchmarkUrl': 'https://eigenmethod.github.io/mol/perf/uibench/', 697 | 'page': 'index.html', 698 | 'comments': 'Fine-grained data bindings. Components recycling.', 699 | }, 700 | { 701 | 'name': 'ivi', 702 | 'url': 'https://github.com/localvoid/ivi', 703 | 'benchmarkUrl': 'https://localvoid.github.io/ivi-examples/benchmarks/uibench/', 704 | 'page': 'index.html', 705 | 'comments': 'Virtual DOM. Preserves internal state, no explicit event delegation, etc.', 706 | }, 707 | { 708 | 'name': 'stage0', 709 | 'url': 'https://github.com/Freak613/stage0', 710 | 'benchmarkUrl': 'https://freak613.github.io/stage0/examples/uibench/', 711 | 'comments': 'Optimized "Vanilla" implementation that uses helper functions from stage0 library. Preserves internal state, doesn\'t support sCU optimization, doesn\'t have components overhead.', 712 | }, 713 | { 714 | 'name': 'Solid', 715 | 'url': 'https://github.com/ryansolid/solid', 716 | 'benchmarkUrl': 'https://ryansolid.github.io/solid-uibench/', 717 | 'page': 'index.html', 718 | 'comments': 'Fine-grained data bindings. Preserves internal state, no explicit event delegation, etc.', 719 | }, 720 | { 721 | 'name': 'Vanilla [innerHTML]', 722 | 'url': 'https://github.com/localvoid/uibench-vanilla', 723 | 'benchmarkUrl': 'https://localvoid.github.io/uibench-vanilla/innerhtml.html', 724 | 'comments': 'Benchmark implementation doesn\'t preserve internal state, doesn\'t support sCU optimization, doesn\'t have components overhead.', 725 | }, 726 | { 727 | 'name': 'Vanilla [WebComponent]', 728 | 'url': 'https://github.com/localvoid/uibench-vanilla-wc', 729 | 'benchmarkUrl': 'https://localvoid.github.io/uibench-vanilla-wc/', 730 | 'comments': 'Benchmark implementation doesn\'t preserve internal state, doesn\'t support sCU optimization.', 731 | }, 732 | ], 733 | results: new Results() 734 | }; 735 | 736 | document.addEventListener('DOMContentLoaded', function (e) { 737 | const container = document.querySelector('#App'); 738 | 739 | window.addEventListener('message', function (e) { 740 | const type = e.data.type; 741 | const data = e.data.data; 742 | 743 | if (type === 'report') { 744 | state.results.update(data); 745 | ReactDOM.render(
, container); 746 | } 747 | }); 748 | 749 | ReactDOM.render(
, container); 750 | }); 751 | --------------------------------------------------------------------------------