From fd6dac4d21cad1bfa89ba302cc90f12efd68db04 Mon Sep 17 00:00:00 2001 From: Thomas Wilkerling Date: Wed, 27 Dec 2023 13:20:03 +0100 Subject: [PATCH] github pages --- README.md | 393 +++++++++------ bench.js | 264 ++++++----- data.js | 2 +- index.html | 193 +++++--- main.js | 171 ++++--- package.json | 2 +- test/domc/strict.html | 38 ++ test/doohtml/doo.html.min.js | 2 + test/doohtml/index.html | 50 ++ test/doohtml/internal.html | 50 ++ test/doohtml/keyed.html | 50 ++ test/inferno/strict.html | 61 +++ test/innerhtml/strict.html | 48 ++ test/ivi/dist/main.js | 1 + test/ivi/index.html | 24 + test/ivi/internal.html | 38 ++ test/ivi/keyed.html | 38 ++ test/ivi/package.json | 19 + test/ivi/rollup.config.js | 38 ++ test/ivi/src/main.js | 15 + test/ivi/strict.html | 24 + test/jquery/strict.html | 49 ++ test/knockout/strict.html | 58 +++ .../dist/lib/default-template-processor.js | 51 ++ test/lit-html/dist/lib/directive.js | 62 +++ test/lit-html/dist/lib/dom.js | 42 ++ test/lit-html/dist/lib/part.js | 22 + test/lit-html/dist/lib/parts.js | 447 ++++++++++++++++++ test/lit-html/dist/lib/render.js | 45 ++ test/lit-html/dist/lib/template-factory.js | 47 ++ test/lit-html/dist/lib/template-instance.js | 135 ++++++ test/lit-html/dist/lib/template-result.js | 111 +++++ test/lit-html/dist/lib/template.js | 212 +++++++++ test/lit-html/dist/lit-html.js | 57 +++ test/lit-html/index.html | 2 +- test/lit-html/internal.html | 2 +- test/lit-html/keyed.html | 2 +- test/lit-html/strict.html | 38 ++ test/mikado-proxy/dist/mikado.bundle.js | 47 ++ test/mikado-proxy/dist/mikado.light.js | 25 + test/mikado-proxy/index.html | 19 +- test/mikado-proxy/internal.html | 29 ++ test/mikado-proxy/keyed.html | 32 ++ test/mikado-proxy/template/keyed.es5.js | 1 + test/mikado-proxy/template/keyed.es6.js | 44 ++ test/mikado-proxy/template/keyed.html | 7 + test/mikado-proxy/template/keyed.js | 1 + test/mikado-proxy/template/keyed.json | 44 ++ test/mikado-proxy/template/proxy.es5.js | 1 + test/mikado-proxy/template/proxy.html | 7 + test/mikado-proxy/template/proxy.js | 1 + test/mikado-proxy/template/proxy.json | 51 ++ test/mikado-proxy/template/template.es5.js | 1 + test/mikado-proxy/template/template.es6.js | 43 ++ test/mikado-proxy/template/template.html | 7 + test/mikado-proxy/template/template.js | 1 + test/mikado-proxy/template/template.json | 43 ++ test/mikado/dist/mikado.bundle.js | 47 ++ test/mikado/dist/mikado.light.js | 40 +- test/mikado/dist/mikado.min.js | 56 --- test/mikado/index.html | 17 +- test/mikado/internal.html | 20 +- test/mikado/keyed.html | 16 +- test/mikado/template/keyed.html | 7 + test/mikado/template/keyed.js | 1 + test/mikado/template/proxy.html | 7 + test/mikado/template/proxy.js | 1 + test/mikado/template/template.html | 7 + test/mikado/template/template.js | 1 + test/mithril/strict.html | 54 +++ test/mustache/index.html | 33 ++ test/mustache/internal.html | 33 ++ test/mustache/keyed.html | 33 ++ test/mustache/mustache.min.js | 1 + test/mustache/strict.html | 33 ++ test/redom/internal.html | 6 - test/redom/keyed.html | 8 +- test/redom/strict.html | 85 ++++ test/sinuous/index.html | 6 +- test/sinuous/internal.html | 10 +- test/sinuous/keyed.html | 6 +- test/sinuous/strict.html | 46 ++ test/solid/dist/main-keyed.js | 1 + test/solid/dist/main-non-keyed.js | 1 + test/solid/dist/main.js | 1 + test/solid/index.html | 22 + test/solid/internal.html | 23 + test/solid/keyed.html | 22 + test/solid/package.json | 19 + test/solid/rollup.config.js | 38 ++ test/solid/src/main-keyed.jsx | 36 ++ test/solid/src/main-non-keyed.jsx | 34 ++ test/solid/src/main.jsx | 35 ++ test/solid/strict.html | 22 + test/stage0/dist/index.min.js | 1 + test/stage0/dist/keyed.min.js | 1 + test/stage0/dist/reconcile.min.js | 1 + test/stage0/dist/reuseNodes.min.js | 1 + test/stage0/index.html | 90 ++++ test/stage0/internal.html | 90 ++++ test/stage0/keyed.html | 91 ++++ test/stage0/package.json | 6 + test/stage0/strict.html | 91 ++++ test/surplus/index.html | 2 +- test/surplus/internal.html | 4 +- test/surplus/keyed.html | 2 +- test/surplus/strict.html | 57 +++ test/vue/index.html | 60 +++ tpl/lib.es5.js | 1 + tpl/lib.html | 3 + tpl/lib.js | 17 - tpl/row.es5.js | 1 + tpl/row.html | 16 + tpl/row.js | 137 ------ 114 files changed, 4080 insertions(+), 727 deletions(-) create mode 100644 test/domc/strict.html create mode 100644 test/doohtml/doo.html.min.js create mode 100644 test/doohtml/index.html create mode 100644 test/doohtml/internal.html create mode 100644 test/doohtml/keyed.html create mode 100644 test/inferno/strict.html create mode 100644 test/innerhtml/strict.html create mode 100644 test/ivi/dist/main.js create mode 100644 test/ivi/index.html create mode 100644 test/ivi/internal.html create mode 100644 test/ivi/keyed.html create mode 100644 test/ivi/package.json create mode 100644 test/ivi/rollup.config.js create mode 100644 test/ivi/src/main.js create mode 100644 test/ivi/strict.html create mode 100644 test/jquery/strict.html create mode 100644 test/knockout/strict.html create mode 100644 test/lit-html/dist/lib/default-template-processor.js create mode 100644 test/lit-html/dist/lib/directive.js create mode 100644 test/lit-html/dist/lib/dom.js create mode 100644 test/lit-html/dist/lib/part.js create mode 100644 test/lit-html/dist/lib/parts.js create mode 100644 test/lit-html/dist/lib/render.js create mode 100644 test/lit-html/dist/lib/template-factory.js create mode 100644 test/lit-html/dist/lib/template-instance.js create mode 100644 test/lit-html/dist/lib/template-result.js create mode 100644 test/lit-html/dist/lib/template.js create mode 100644 test/lit-html/dist/lit-html.js create mode 100644 test/lit-html/strict.html create mode 100644 test/mikado-proxy/dist/mikado.bundle.js create mode 100644 test/mikado-proxy/dist/mikado.light.js create mode 100644 test/mikado-proxy/internal.html create mode 100644 test/mikado-proxy/keyed.html create mode 100644 test/mikado-proxy/template/keyed.es5.js create mode 100644 test/mikado-proxy/template/keyed.es6.js create mode 100644 test/mikado-proxy/template/keyed.html create mode 100644 test/mikado-proxy/template/keyed.js create mode 100644 test/mikado-proxy/template/keyed.json create mode 100644 test/mikado-proxy/template/proxy.es5.js create mode 100644 test/mikado-proxy/template/proxy.html create mode 100644 test/mikado-proxy/template/proxy.js create mode 100644 test/mikado-proxy/template/proxy.json create mode 100644 test/mikado-proxy/template/template.es5.js create mode 100644 test/mikado-proxy/template/template.es6.js create mode 100644 test/mikado-proxy/template/template.html create mode 100644 test/mikado-proxy/template/template.js create mode 100644 test/mikado-proxy/template/template.json create mode 100644 test/mikado/dist/mikado.bundle.js delete mode 100644 test/mikado/dist/mikado.min.js create mode 100644 test/mikado/template/keyed.html create mode 100644 test/mikado/template/keyed.js create mode 100644 test/mikado/template/proxy.html create mode 100644 test/mikado/template/proxy.js create mode 100644 test/mikado/template/template.html create mode 100644 test/mikado/template/template.js create mode 100644 test/mithril/strict.html create mode 100644 test/mustache/index.html create mode 100644 test/mustache/internal.html create mode 100644 test/mustache/keyed.html create mode 100644 test/mustache/mustache.min.js create mode 100644 test/mustache/strict.html create mode 100644 test/redom/strict.html create mode 100644 test/sinuous/strict.html create mode 100644 test/solid/dist/main-keyed.js create mode 100644 test/solid/dist/main-non-keyed.js create mode 100644 test/solid/dist/main.js create mode 100644 test/solid/index.html create mode 100644 test/solid/internal.html create mode 100644 test/solid/keyed.html create mode 100644 test/solid/package.json create mode 100644 test/solid/rollup.config.js create mode 100644 test/solid/src/main-keyed.jsx create mode 100644 test/solid/src/main-non-keyed.jsx create mode 100644 test/solid/src/main.jsx create mode 100644 test/solid/strict.html create mode 100644 test/stage0/dist/index.min.js create mode 100644 test/stage0/dist/keyed.min.js create mode 100644 test/stage0/dist/reconcile.min.js create mode 100644 test/stage0/dist/reuseNodes.min.js create mode 100644 test/stage0/index.html create mode 100644 test/stage0/internal.html create mode 100644 test/stage0/keyed.html create mode 100644 test/stage0/package.json create mode 100644 test/stage0/strict.html create mode 100644 test/surplus/strict.html create mode 100644 test/vue/index.html create mode 100644 tpl/lib.es5.js create mode 100644 tpl/lib.html delete mode 100644 tpl/lib.js create mode 100644 tpl/row.es5.js create mode 100644 tpl/row.html delete mode 100644 tpl/row.js diff --git a/README.md b/README.md index 689d5f1..83b093e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ There are 3 kinds of test scenarios: -Weather a library provides optimizations to one of these modes or not, it is fair to compare each of them in a different scenario. +Whether a library provides optimizations to one of these modes or not, it is fair to compare each of them in a different scenario. When the option "keep best run" is enabled it will replace the better result with the old one (separately for each test). When disabled, it will summarize the results for each test. @@ -45,7 +45,7 @@ When the option "keep best run" is enabled it will replace the better result wit Update Order Repaint - Append + Add Remove Toggle Clear @@ -54,19 +54,53 @@ When the option "keep best run" is enabled it will replace the better result wit mikado - 2.8 - 27 - 18745 - 8124 - 92669 - 49554 - 299140 - 35254 - 27563 + 3 + 22 + 19301 + 8535 + 206747 + 51470 + 220010 + 35346 + 27945 31265 - 25276 - 994 - 60020 + 26378 + 996 + 54089 + + + + mikado-proxy + 8.3 + 30 + 10288 + 5901 + 27129 + 18648 + 28194 + 14912 + 19278 + 16526 + 26216 + 537 + 12803 + + + + solid + 0 + 339 + 737 + 665 + 7291 + 4029 + 13279 + 1391 + 7487 + 2470 + 15227 + 149 + 3587 @@ -82,8 +116,8 @@ When the option "keep best run" is enabled it will replace the better result wit 7443 2302 15982 - 196 - 3099 + 191 + 2647 @@ -99,8 +133,25 @@ When the option "keep best run" is enabled it will replace the better result wit 6614 2004 12622 - 175 - 2639 + 170 + 2256 + + + + redom + 2.9 + 522 + 421 + 411 + 4146 + 3719 + 4215 + 761 + 5750 + 1380 + 11744 + 190 + 1954 @@ -116,8 +167,8 @@ When the option "keep best run" is enabled it will replace the better result wit 2049 1464 24931 - 214 - 1385 + 211 + 1250 innerhtml @@ -132,8 +183,8 @@ When the option "keep best run" is enabled it will replace the better result wit 1769 1186 27131 - 157 - 1232 + 154 + 1107 surplus @@ -148,8 +199,8 @@ When the option "keep best run" is enabled it will replace the better result wit 1560 1187 23713 - 165 - 1093 + 162 + 987 @@ -165,8 +216,8 @@ When the option "keep best run" is enabled it will replace the better result wit 1577 1096 18047 - 161 - 1043 + 159 + 941 @@ -182,25 +233,8 @@ When the option "keep best run" is enabled it will replace the better result wit 1129 860 5520 - 86 - 784 - - - - redom - 2.9 - 1090 - 449 - 441 - 450 - 451 - 452 - 468 - 864 - 603 - 11765 - 158 - 715 + 84 + 708 @@ -216,8 +250,8 @@ When the option "keep best run" is enabled it will replace the better result wit 761 550 4964 - 80 - 538 + 79 + 487 @@ -233,8 +267,8 @@ When the option "keep best run" is enabled it will replace the better result wit 298 212 1944 - 37 - 223 + 36 + 202 @@ -250,12 +284,12 @@ When the option "keep best run" is enabled it will replace the better result wit 130 103 1162 - 46 - 175 + 45 + 161 -#### Recycle (Non-Keyed) +#### Non-Keyed (Recycle) @@ -268,7 +302,7 @@ When the option "keep best run" is enabled it will replace the better result wit - + @@ -277,19 +311,36 @@ When the option "keep best run" is enabled it will replace the better result wit - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -305,8 +356,8 @@ When the option "keep best run" is enabled it will replace the better result wit - - + + @@ -322,8 +373,25 @@ When the option "keep best run" is enabled it will replace the better result wit - - + + + + + + + + + + + + + + + + + + + @@ -339,8 +407,8 @@ When the option "keep best run" is enabled it will replace the better result wit - - + + @@ -356,8 +424,8 @@ When the option "keep best run" is enabled it will replace the better result wit - - + + @@ -373,8 +441,8 @@ When the option "keep best run" is enabled it will replace the better result wit - - + + @@ -389,8 +457,8 @@ When the option "keep best run" is enabled it will replace the better result wit - - + + @@ -405,8 +473,8 @@ When the option "keep best run" is enabled it will replace the better result wit - - + + @@ -422,8 +490,8 @@ When the option "keep best run" is enabled it will replace the better result wit - - + + @@ -439,8 +507,8 @@ When the option "keep best run" is enabled it will replace the better result wit - - + + @@ -456,8 +524,8 @@ When the option "keep best run" is enabled it will replace the better result wit - - + + @@ -473,8 +541,8 @@ When the option "keep best run" is enabled it will replace the better result wit - - + +
Update Order RepaintAppendAdd Remove Toggle Clear
mikado2.82518432782290335487612849693424527452307042567499418232312193448611205552524972438743440527858318472648399518276
mikado-proxy8.343100415782242151820124873143021912616517257784945927
22497 4617 2516747852714314510
8317 1645 502122426071972220
solid026774121966302322313560134683972452149641621320
7366 2345 1615119812721851122
7191 2011 1260419312001791034
6620 1530 1234822611672141005
innerhtml1636 1186 27371157791147710
surplus1540 1155 25324169740162671
2970 616 17688271673608
1609 1075 16384155678148614
1136 872 55318650379454
130 103 11814614043130
@@ -491,28 +559,62 @@ When the option "keep best run" is enabled it will replace the better result wit Update Order Repaint - Append + Add Remove Toggle Clear Index Score + + mikado-proxy + 8.3 + 18 + 7350 + 2600 + 18467 + 11819 + 5601305 + 10254 + 23402 + 14499 + 18986 + 551 + 109601 + + mikado - 2.8 + 3 15 - 18055 - 7789 - 71626 - 45370 - 294877 - 33575 - 27901 - 30799 - 25028 - 1000 - 48211 + 18980 + 8374 + 78140 + 49034 + 260674 + 33723 + 27425 + 30768 + 25797 + 911 + 17362 + + + + sinuous + 7.5 + 830 + 372 + 377 + 36533 + 38554 + 327320 + 757 + 13058 + 1529 + 12815 + 289 + 8431 @@ -528,25 +630,8 @@ When the option "keep best run" is enabled it will replace the better result wit 14914 3703 22609 - 405 - 13764 - - - - sinuous - 7.5 - 247 - 839 - 816 - -failed- - 39032 - -failed- - 1604 - 18119 - 3034 - 15289 - 364 - 3311 + 373 + 4248 @@ -562,8 +647,8 @@ When the option "keep best run" is enabled it will replace the better result wit 13561 3480 20871 - 317 - 2564 + 311 + 2063 @@ -579,8 +664,25 @@ When the option "keep best run" is enabled it will replace the better result wit 6100 2363 14419 - 188 - 2173 + 184 + 1200 + + + + redom + 2.9 + 1126 + 439 + 2425 + 4139 + 3386 + 4666 + 840 + 6389 + 1482 + 12790 + 214 + 1170 @@ -596,8 +698,8 @@ When the option "keep best run" is enabled it will replace the better result wit 6664 1964 12248 - 171 - 1870 + 168 + 1046 innerHTML @@ -612,8 +714,8 @@ When the option "keep best run" is enabled it will replace the better result wit 1675 1167 20810 - 135 - 957 + 131 + 794 jquery @@ -628,25 +730,8 @@ When the option "keep best run" is enabled it will replace the better result wit 1112 856 5553 - 85 - 636 - - - - redom - 2.9 - 1126 - 446 - 442 - 443 - 438 - 442 - 456 - 865 - 566 - 11737 - 158 - 604 + 84 + 530 @@ -663,7 +748,7 @@ When the option "keep best run" is enabled it will replace the better result wit 554 5027 79 - 444 + 378 @@ -679,8 +764,8 @@ When the option "keep best run" is enabled it will replace the better result wit 320 191 1056 - 56 - 195 + 57 + 196 @@ -696,8 +781,8 @@ When the option "keep best run" is enabled it will replace the better result wit 243 187 1434 - 34 - 168 + 33 + 146 @@ -707,6 +792,10 @@ This stress test focuses a real life use case, where new data is coming from a s This test measures the raw rendering performance. If you look for a benchmark which covers more aspects goto here:
https://krausest.github.io/js-framework-benchmark/current.html +#### Angular and React? + +There are some reasons why you did not find Angular or React on this list. Both aren't a library nor a framework, they are full integrated ecosystems which is quite unfair to compare. Also they runs completely async. If there was a way to force running them in sync I'm pretty sure, they would fill the bottom lines of this benchmark test. + #### Local Installation Go to the folder _bench/_ and install dependencies: diff --git a/bench.js b/bench.js index ec8d4de..fdfd254 100644 --- a/bench.js +++ b/bench.js @@ -12,12 +12,12 @@ export function store(data){ return (data && (items = data)) || items; } let pos = 0; export const queue = []; const pathname = window.location.pathname; -const keyed = pathname.indexOf("/keyed.html") !== -1; -const strict = pathname.indexOf("/strict.html") !== -1; -const internal = (pathname.indexOf("/internal.html") !== -1) || - (pathname.indexOf("mikado-observer") !== -1) || - (pathname.indexOf("mikado-proxy") !== -1) || - (pathname.indexOf("mikado-observer-proxy") !== -1); +const keyed = pathname.includes("keyed.html"); +const strict = pathname.includes("strict.html"); +const internal = (pathname.includes("internal.html")) || + (pathname.includes("mode-array")) || + (pathname.includes("mode-proxy")) || + (pathname.includes("mode-array-proxy")); const params = (function(){ @@ -32,31 +32,15 @@ const params = (function(){ return obj; }()); -// const rnd = (function(){ -// +// console.log((function(){ // const array = new Array(DATA_SIZE_HALF); // for(let i = 0; i < DATA_SIZE_HALF; i++) array[i] = i; // for(let i = 0; i < 3; i++) shuffle(array); -// // return array; -// }()); -// -// console.log(rnd); +// }())); const rnd = [27, 36, 31, 21, 13, 5, 11, 0, 16, 8, 9, 28, 25, 49, 6, 26, 12, 29, 38, 1, 40, 35, 20, 23, 2, 7, 18, 48, 45, 17, 22, 39, 32, 37, 24, 4, 41, 44, 14, 34, 19, 47, 46, 10, 30, 15, 33, 3, 42, 43]; - -let runs; -let duration; - -if(params["duration"] && (params["duration"].indexOf("run-") !== -1)){ - - duration = 86400000; - runs = parseInt(params["duration"].replace("run-", ""), 10); -} -else{ - - duration = parseFloat(params["duration"] || "5") * 1000; -} +const duration = parseFloat(params["duration"] || 5) * 1000; const hidden = params["hidden"] !== "false"; const factory = strict ? null : [ @@ -68,23 +52,30 @@ const factory = strict ? null : [ ]; let clone; -root.hidden = hidden; -function next(){ +if(hidden){ - if(strict){ + root.hidden = hidden; + root.style.position = "absolute"; + root.style.display = "none"; + root.style.overflow = "hidden"; + root.style.contain = "strict"; +} - return items = generate(DATA_SIZE); - } +function next(){ if(internal){ copy(factory[pos], items); } - else{ + else if(keyed){ items = enforce(factory[pos]); } + else /*if(strict || recycle)*/{ + + items = generate(DATA_SIZE); + } if(++pos === factory.length){ @@ -122,20 +113,19 @@ function enforce(data){ queue.push({ name: "create", init: function(){ - items.splice(0); - this.fn(items); + //items.splice(0); + //this.fn(items); + //items = next(); }, test: null, - start: function(){ - shuffle_rnd(); - }, + start: null, prepare: function(){ clone = next(); }, fn: null, end: function(){ - items.splice(0); - this.fn(items); + clone.splice(0); + this.fn(clone); }, complete: null }); @@ -146,56 +136,50 @@ queue.push({ this.fn(next()); }, test: null, - start: function(){ - shuffle_rnd(); - }, + start: null, prepare: function(){ clone = next(); }, fn: null, end: null, complete: function(){ - items.splice(0); - this.fn(items); + clone.splice(0); + this.fn(clone); } }); queue.push({ name: "update", init: function(){ - this.fn(next()); + this.fn(clone = next()); }, test: null, start: null, prepare: function(index){ - clone = enforce(update(items, index)); + clone = update(enforce(clone), index); }, fn: null, end: null, complete: function(){ - items.splice(0); - this.fn(items); + clone.splice(0); + this.fn(clone); } }); queue.push({ name: "arrange", init: function(){ - this.fn(next()); + this.fn(clone = next()); }, test: null, start: null, prepare: function(index){ index %= 3; - // a too short path comes to close to the repaint test - // if(index === 3){ // swap (shortest path: 2) - // swap(items, 5, items.length - 6); - // } if(index === 2){ // re-order (shortest path: 10) const div = (DATA_SIZE / 20) | 0; for(let i = 0; i < div; i++){ - items.splice(div * 4 + i, 0, items.splice(div * 16 + i, 1)[0]); - items.splice(div * 12 + i, 0, items.splice(div * 8 + i, 1)[0]); + clone.splice(div * 4 + i, 0, clone.splice(div * 16 + i, 1)[0]); + clone.splice(div * 12 + i, 0, clone.splice(div * 8 + i, 1)[0]); // items[div * 9 + i].id = "" + (Math.random() * 999999999 | 0); // items[div * 5 + i].id = "" + (Math.random() * 999999999 | 0); } @@ -203,45 +187,45 @@ queue.push({ else if(index){ // re-order (shortest path: 10) const div = (DATA_SIZE / 10) | 0; for(let i = 0; i < div; i += 2){ - items.splice(div * 2 + i, 0, items.splice(div * 8 + i, 1)[0]); - items.splice(div * 6 + i, 0, items.splice(div * 4 + i, 1)[0]); + clone.splice(div * 2 + i, 0, clone.splice(div * 8 + i, 1)[0]); + clone.splice(div * 6 + i, 0, clone.splice(div * 4 + i, 1)[0]); } } else{ // re-order (shortest path: 10) const div = (DATA_SIZE / 5) | 0; for(let i = 0; i < div; i += 4){ - swap(items, div * 1 + i, div * 3 + i); + swap(clone, div * 1 + i, div * 3 + i); } } // a full shuffle is theoretically a full replace, those test already exist // else{ // shuffle_rnd(items); // re-order (shortest path: 100) // } - clone = enforce(items); + clone = enforce(clone); }, fn: null, end: null, complete: function(){ - items.splice(0); - this.fn(items); + clone.splice(0); + this.fn(clone); } }); queue.push({ name: "repaint", init: function(){ - this.fn(next()); + this.fn(clone = next()); }, test: null, start: function(){ - clone = enforce(items); + clone = enforce(clone); }, prepare: null, fn: null, end: null, complete: function(){ - items.splice(0); - this.fn(items); + clone.splice(0); + this.fn(clone); } }); @@ -252,18 +236,18 @@ queue.push({ init: null, test: null, start: function(){ - shuffle_rnd(); - tmp = next().splice(DATA_SIZE_HALF); - this.fn(enforce(items)); + clone = next(); + tmp = clone.splice(DATA_SIZE_HALF); + this.fn(clone); }, prepare: function(){ - clone = enforce(items.concat(tmp)); + clone = enforce(clone.concat(tmp)); }, fn: null, end: null, complete: function(){ - items.splice(0); - this.fn(items); + clone.splice(0); + this.fn(clone); } }); @@ -272,43 +256,37 @@ queue.push({ init: null, test: null, start: function(){ - shuffle_rnd(); - this.fn(next()); + this.fn(clone = next()); }, prepare: function(){ - items.splice(DATA_SIZE_HALF); - clone = enforce(items); + clone.splice(DATA_SIZE_HALF); + clone = enforce(clone); }, fn: null, end: null, complete: function(){ - items.splice(0); - this.fn(items); + clone.splice(0); + this.fn(clone); } }); queue.push({ name: "toggle", init: function(){ - shuffle_rnd(); this.fn(clone = next()); }, test: null, start: null, prepare: function(index){ - if(index % 2){ - clone = enforce(clone.concat(tmp)); - } - else{ - tmp = clone.splice(DATA_SIZE_HALF); - clone = enforce(clone); - } + if(index % 2) clone = clone.concat(tmp); + else tmp = clone.splice(DATA_SIZE_HALF); + clone = enforce(clone); }, fn: null, end: null, complete: function(){ - items.splice(0); - this.fn(items); + clone.splice(0); + this.fn(clone); } }); @@ -317,18 +295,16 @@ queue.push({ init: null, test: null, start: function(){ - shuffle_rnd(); - this.fn(next()); + this.fn(clone = next()); }, prepare: function(){ - items.splice(0); - clone = items; + clone.splice(0); }, fn: null, end: null, complete: function(){ - items.splice(0); - this.fn(items); + clone.splice(0); + this.fn(clone); } }); @@ -416,22 +392,32 @@ function check(fn){ fn(enforce(items)); if(!validate(data[0])) return false; - // checks if libs updates contents on same id + // checks if libs updates contents on same key const tmp = enforce(items); tmp[0].title = "test"; fn(tmp); if(!validate(tmp[0])) return false; + let target = (root.shadow || root); + + if(target.firstElementChild && target.firstElementChild.tagName.toLowerCase() !== "section"){ + target = target.firstElementChild; + } + + if(target.firstElementChild && target.firstElementChild.tagName.toLowerCase() !== "section"){ + target = target.firstElementChild; + } + if(keyed){ - const node = root.firstElementChild.firstElementChild.firstElementChild; + const node = target.firstElementChild.firstElementChild.firstElementChild; node._test = true; items.pop(); items.push(data[1]); fn(enforce(items)); - if(root.firstElementChild.firstElementChild.firstElementChild._test){ - msg("lib does not run in keyed mode."); + if(target.firstElementChild.firstElementChild.firstElementChild._test){ + msg("lib '" + Object.keys(suite)[0] + "' does not run in keyed mode."); return false; } node._test = false; @@ -441,11 +427,11 @@ function check(fn){ items.pop(); items.push(data[0]); fn(enforce(items)); - const node = root.firstElementChild.firstElementChild.firstElementChild; + const node = target.firstElementChild.firstElementChild.firstElementChild; node._test = true; fn(enforce(items)); - if(root.firstElementChild.firstElementChild.firstElementChild._test){ - msg("lib does not run in strict mode."); + if(target.firstElementChild.firstElementChild.firstElementChild._test){ + msg("lib '" + Object.keys(suite)[0] + "' does not run in strict mode."); return false; } node._test = false; @@ -453,7 +439,7 @@ function check(fn){ items.pop(); fn(items); - return (root.children.length === 0) || (root.firstElementChild.children.length === 0); + return target.children.length === 0; } function check_test(test){ @@ -471,12 +457,26 @@ function check_test(test){ if(test.complete) test.complete(); - return (root.children.length === 0) || (root.firstElementChild.children.length === 0); + let target = (root.shadow || root); + + if(target.firstElementChild && target.firstElementChild.tagName.toLowerCase() !== "section"){ + target = target.firstElementChild; + } + + if(target.firstElementChild && target.firstElementChild.tagName.toLowerCase() !== "section"){ + target = target.firstElementChild; + } + + return target.children.length === 0; } function check_loop(name){ - let target = root; + let target = (root.shadow || root); + + if(target.firstElementChild && target.firstElementChild.tagName.toLowerCase() !== "section"){ + target = target.firstElementChild; + } if(target.firstElementChild && target.firstElementChild.tagName.toLowerCase() !== "section"){ target = target.firstElementChild; @@ -497,7 +497,7 @@ function check_loop(name){ function validate(item, index){ - let section = root.firstElementChild; + let section = (root.shadow || root).firstElementChild; if(!section) return msg("root.firstElementChild"); (section.tagName.toLowerCase() === "section") || (section = section.firstElementChild); (section.tagName.toLowerCase() === "section") || (section = section.firstElementChild); @@ -535,8 +535,9 @@ function msg(message, a){ // ##################################################################################### let str_results = ""; -const perf = window.performance; +const perf = window.performance || {}; perf.memory || (perf.memory = { usedJSHeapSize: 0 }); + perf.now || (perf.now = function(){ return Date.now() }); let current = 0; let update_failed; @@ -546,8 +547,8 @@ function perform(){ const test = queue[current]; let elapsed = 0, memory = 0; - if(current === 0 && test.test) check(test.test) || msg("Main test failed"); - let status = check_test(test) || msg("Test failed: " + test.name); + if(current === 0 && test.test) check(test.test) || msg("Main test failed: " + Object.keys(suite)[0]); + let status = check_test(test) || msg("Test failed: " + test.name + ", " + Object.keys(suite)[0]); // Not allowed when update test fails if(update_failed && (test.name === "repaint")){ @@ -569,20 +570,17 @@ function perform(){ const end = perf.now() + duration; - for(let start, mem_start, mem; now < end; loops++){ - - if(runs && (runs === loops)){ - - break; - } + for(let start, mem_start, mem, tmp; now < end; loops++){ if(test.start) test.start(loops); if(!internal && test.prepare) test.prepare(loops); + if(!hidden) tmp = root.clientHeight; mem_start = perf.memory.usedJSHeapSize; start = perf.now(); if(internal && test.prepare) test.prepare(loops); test.fn(clone); + if(!hidden) tmp = root.clientHeight; now = perf.now(); mem = perf.memory.usedJSHeapSize - mem_start; elapsed += (now - start); @@ -611,7 +609,11 @@ function perform(){ if(current < queue.length){ - setTimeout(perform, 200); + requestAnimationFrame(() => { + requestAnimationFrame(() => { + setTimeout(perform, 100); + }); + }); } else{ @@ -619,17 +621,17 @@ function perform(){ } } -function shuffle_rnd(items){ - - items || (items = factory[pos]); - - for(let i = 0; i < DATA_SIZE_HALF; i++) { - - swap(items, i, DATA_SIZE_HALF + rnd[i]); - } - - return items; -} +// function shuffle_rnd(items){ +// +// items || (items = factory[pos]); +// +// for(let i = 0; i < DATA_SIZE_HALF; i++) { +// +// swap(items, i, DATA_SIZE_HALF + rnd[i]); +// } +// +// return items; +// } // function shuffle(items){ // @@ -657,13 +659,13 @@ function update(items, index){ current = i + index; - (current % 29) || swap_value(items, current, "date"); - (current % 23) || swap_value(items, current, "classname"); - (current % 19) || swap_value(items, current, "months"); - (current % 17) || swap_value(items, current, "content"); - (current % 13) || swap_value(items, current, "title"); - (current % 11) || swap_value(items, current, "days"); - (current % 7 ) || swap_value(items, current, "footer"); + (current % 29) || swap_value(items, current, "date"); + (current % 23) || swap_value(items, current, "classname"); + (current % 19) || swap_value(items, current, "months"); + (current % 17) || swap_value(items, current, "content"); + (current % 13) || swap_value(items, current, "title"); + (current % 11) || swap_value(items, current, "days"); + (current % 7 ) || swap_value(items, current, "footer"); } return items; diff --git a/data.js b/data.js index 3367217..12f4aed 100644 --- a/data.js +++ b/data.js @@ -65,7 +65,7 @@ const len_continents = continents.length; export function generate(count){ - const data = new Array(count); + const data = []; for(let i = 0; i < count; i++){ diff --git a/index.html b/index.html index ddd9e9d..c093c37 100644 --- a/index.html +++ b/index.html @@ -1,15 +1,22 @@ + - Benchmark of Web Templating Engines (Non-Keyed) + Benchmark of Web Template Rendering Engines (Recycle, Keyed, Data-Driven, Strict) -

Benchmark of Web Templating Engines (Stress Test)

+Back to Mikado Github +

Benchmark: Template Rendering Engines

+ +
- + - Mode: - + - Duration: - + - - + + + + - Repeat: - + - keep best run +
@@ -91,32 +133,65 @@

Benchmark of Web Templating Engines (Stress Test)

- - - - - - - - - - - - - - + + + + + + + + + + + + + +
LibrarySizeMemoryCreateReplaceUpdateOrderRepaintAppendRemoveToggleClearIndexScoreLibraryMemoryCreateReplaceUpdateOrderRepaintAppendRemoveToggleClearScoreIndex
-
-To measure memory you have to run in Chrome browser.

+
+Operations per second, higher values are better except the memory test.
+Hover through the table header or the forms to get more information.
+To measure memory you need to run in Chrome browser.
+The test "innerHTML" just uses the browsers native feature element.innerHTML = "..." with no library. +

Single Tests: - - - - +
+Test Modes: + +* The test applies de-referencing to mimic real incoming data from a server. No fake allowed. +

+Test Environment: +

+Every test will run in its own isolated/dedicated browser instance (iframe) and post back results via message channel. +The benchmark creates a sequence of fixed randomness which will apply for every test. So every test has to solve exactly the same task. +This is a real world benchmark running in your browser, no synthetics. It applies de-referencing on "recycle" und "keyed" tests to mimic realistic data which is coming from an extern authority like a server. +To bo honest, everything else would be a fake. The data-driven test covers the feature of internal data processing explicitly. You will find a lot of synthetic benchmarks out there which didn't cover this specific real world behavior. Keep this in mind. +

+The test by default just measure the time it takes to construct and transfer the whole render task to the browser. A library is able to optimize just this specific part. When enabling the option "Force reflow" the benchmark will also measure the time taken by the recalculation by forcing reflow at the end of each test cycle. Since this part couldn't be optimized by a library it adds way too much unrelated workload to the test. +


+Test Data: +

+Before every test is running 5 slots each including 100 unique data items are created. Imagine a paginated table on which you move 5 steps forward, every page has 100 rows. +The test data isn't infinite, because the benchmark has to cover the library optimizations capabilities. Some important capabilities are getting lost when using infinite random data. Let's take the "keyed" recycle strategy as an example. When a key will never match, the whole strategy will negate every improvement and becomes useless. +Also, it doesn't match real world scenario when a test runs 5 seconds and is applying more than 2 million of unique data entries. + +

+ + + + \ No newline at end of file diff --git a/main.js b/main.js index 951f495..051edf0 100644 --- a/main.js +++ b/main.js @@ -2,10 +2,13 @@ "use strict"; + const Mikado = window.Mikado; const iframe = document.getElementById("iframe"); - const options = { cache: false, store: false, pool: false }; - const mikado = Mikado(document.getElementById("result"), "row", options); - const list = Mikado(document.getElementById("lib"), "lib", options); + const mikado_table = Mikado("row").mount(document.getElementById("result")); + const mikado_list = Mikado("lib").mount(document.getElementById("lib")); + + Mikado.eventCache = true; + //Mikado.unregister("row").unregister("lib"); const modes = window.location.hash.indexOf("modes") !== -1; let strict; @@ -17,135 +20,130 @@ let lib = shuffle(modes ? [ - "mikado-cross-shared", "mikado-exclusive", "mikado-keyed", - "mikado-keyed-shared", "mikado-non-keyed", "mikado-proxy", - "mikado-observer", "mikado-observer-proxy" + "mode-cross-shared", "mode-exclusive", "mode-keyed-noop", + "mode-keyed-shared", "mode-non-keyed", "mode-proxy", + "mode-array", "mode-array-proxy" ]:[ - "mikado", "domc", "inferno", + "mikado", "mikado-proxy", //"mikado-0.7.6", "mikado-proxy-0.7.6", "mikado-0.7.5", "mikado-0.7.4", + "stage0", "solid", "domc", "inferno", "redom", "sinuous", "surplus", "innerHTML", "jquery", "mithril", - "knockout", "lit-html", "ractive" + "knockout", "lit-html", "ractive", + "doohtml" ]); function init(hash){ - strict = hash.indexOf("strict") !== -1; - internal = hash.indexOf("internal") !== -1; - keyed = hash.indexOf("keyed") !== -1; + strict = hash.includes("strict"); + internal = hash.includes("internal"); + keyed = hash.includes("keyed"); - document.getElementsByTagName("h1")[0].firstChild.nodeValue = "Benchmark of Web Templating Engines (" + (keyed ? "Keyed" : strict ? "Non-Reusing" : modes ? "Modes" : internal ? "Data-Driven" : "Non-Keyed") + ")"; + document.getElementsByTagName("h1")[0].firstChild.nodeValue = "Benchmark: Template Rendering Engines (" + (keyed ? "Keyed" : strict ? "Non-Recycle" : modes ? "Modes" : internal ? "Data-Driven" : "Recycle") + ")"; if(modes){ document.body.className = "modes"; } - list.render(lib, {"mode": (keyed ? "keyed.html" : strict ? "strict.html" : internal ? "internal.html" : "")}); + mikado_list.render(lib, {"mode": (keyed ? "keyed.html" : strict ? "strict.html" : internal ? "internal.html" : "")}); } - init(window.location.hash); + init(window.location.hash || window.location.search); - document.getElementById("mode").options[keyed ? 1 : internal ? 2 : 0].selected = true; + document.getElementById("mode").options[keyed ? 2 : internal ? 3 : strict ? 0 : 1].selected = true; + //document.getElementById("mode").options[keyed ? 1 : internal ? 2 : 0].selected = true; if(modes){ document.getElementById("mode").hidden = true; } - Mikado.route("start", function(target){ + const test = [ + + /*"size",*/ "memory", + "create", "replace", "update", + "arrange", "repaint", "append", + "remove", "toggle", "clear" + ]; + + const current = []; + + Mikado.route("start", function(event){ - if(target.value === "Start"){ + if(this.value === "Start"){ index = -1; repeat = document.getElementById("repeat").value; - target.value = "Stop"; + this.value = "Stop"; setTimeout(runner, 200); + + const reflow = document.getElementById("reflow").checked; + + iframe.hidden = !reflow; + iframe.style.position = "absolute"; + iframe.style.display = reflow ? "": "none"; + iframe.style.overflow = reflow ? "": "hidden"; + iframe.style.contain = reflow ? "": "strict"; } else{ current[index][test[2]] = ""; - target.value = "Start"; + this.value = "Start"; iframe.src = ""; index = lib.length; } + }) + .route("mode", function(event){ - }).route("mode", function(target){ - - init(window.location.hash = "#" + target.value); + init(window.location.hash = "#" + this.value); + reset(); + }) + .listen("click") + .listen("change"); - }).listen("click").listen("change"); + function reset(){ - const test = [ - - "size", "memory", - "create", "replace", "update", - "arrange", "repaint", "append", - "remove", "toggle", "clear" - ]; - - const current = new Array(lib.length); - - let size = { - - "mikado": 2.8, - "domc": 4.46, - "inferno": 8.4, - "redom": 2.88, - "sinuous": 7.48, - "surplus": 15.79, - "innerHTML": 0, - "jquery": 31.26, - "mithril": 9.64, - "knockout": 24.8, - "lit-html": 17.31, - "ractive": 68.2, - - "mikado-cross-shared": 2.8, - "mikado-exclusive": 2.8, - "mikado-keyed": 2.8, - "mikado-keyed-shared": 2.8, - "mikado-non-keyed": 2.8, - "mikado-proxy": 7.1, - "mikado-observer": 7.1, - "mikado-observer-proxy": 7.1 - }; - - for(let x = 0; x < lib.length; x++){ + for(let x = 0; x < lib.length; x++){ - current[x] = { + current[x] = { - "name": lib[x], - "size": size[lib[x]], - "memory": 0, - "score": "", - "index": "" - }; + "name": lib[x], + //"size": size[lib[x]], + "memory": "", + "score": "", + "index": "" + }; - for(let y = 2; y < test.length + 1; y++){ + for(let y = 1; y < test.length + 1; y++){ - current[x][test[y]] = ""; - current[x]["color_" + test[y]] = "transparent"; + current[x][test[y]] = ""; + current[x]["color_" + test[y]] = "transparent"; + } } + + document.body.className = ""; + mikado_table.render(current); } - mikado.render(current); + reset(); function runner(){ + const reflow = document.getElementById("reflow").checked; const duration = document.getElementById("duration").value; keep = document.getElementById("keep").checked; index++; const tmp = Object.assign({}, current[index]); - tmp[test[2]] = "run..."; - mikado.update(mikado.node(index), tmp); - iframe.src = "test/" + lib[index].toLowerCase() + "/" + (keyed ? "keyed.html" : strict ? "strict.html" : internal ? "internal.html" : "") + ("?duration=" + duration); + tmp[test[1]] = "run..."; + mikado_table.update(mikado_table.node(index), tmp); + iframe.src = "test/" + lib[index].toLowerCase() + "/" + (keyed ? "keyed.html" : strict ? "strict.html" : internal ? "internal.html" : "") + ("?duration=" + duration) + (reflow ? "&hidden=false" : ""); } function get_score(){ - let max = new Array(test.length); - let val = new Array(test.length); + let max = []; + let val = []; for(let y = 0; y < test.length; y++){ @@ -161,7 +159,7 @@ val[y].push(current[x][test[y]]); } - if((test[y] === "size") || (test[y] === "memory")){ + if(/*(test[y] === "size") ||*/ (test[y] === "memory")){ if((current[x][test[y]] < max[y]) || !max[y]){ @@ -179,9 +177,7 @@ } } - let score = new Array(lib.length); - let index = new Array(lib.length); - let length = new Array(lib.length); + let score = [], index = [],length = []; let max_score = 0, max_index = 0; for(let x = 0; x < lib.length; x++){ @@ -196,7 +192,7 @@ length[x]++; - if((test[y] === "size") || (test[y] === "memory")){ + if(/*(test[y] === "size") ||*/ (test[y] === "memory")){ score[x] += Math.sqrt(median(val[y]) / current[x][test[y]]); index[x] += Math.sqrt(max[y] / current[x][test[y]]); @@ -204,8 +200,8 @@ } else{ - score[x] += current[x][test[y]] / median(val[y]); - index[x] += current[x][test[y]] / max[y]; + score[x] += (current[x][test[y]] / median(val[y])); + index[x] += (current[x][test[y]] / max[y]); current[x]["color_" + test[y]] = color(current[x][test[y]], max[y]); } } @@ -216,7 +212,7 @@ } current[x]["score"] = (score[x] / length[x] * 1000 + 0.5) | 0; - current[x]["index"] = (index[x] / length[x] * 1000 + 0.5) | 0; + current[x]["index"] = (index[x] / length[x] * 100 + 0.5) | 0; if(max_score < current[x]["score"]) max_score = current[x]["score"]; if(max_index < current[x]["index"]) max_index = current[x]["index"]; } @@ -288,7 +284,7 @@ if(index < lib.length - 1){ - mikado.update(index, current[index]); + mikado_table.update(index, current[index]); setTimeout(runner, 50); } else{ @@ -297,7 +293,7 @@ current.sort(function(a, b){ - return b["score"] - a["score"]; + return b["index"] - a["index"]; }); for(let i = 0; i < lib.length; i++){ @@ -305,7 +301,8 @@ lib[i] = current[i]["name"]; } - mikado.render(current); + mikado_table.render(current); + document.body.className = "finished"; if(--repeat > 0){ @@ -322,7 +319,7 @@ const tmp = Object.assign({}, current[index]); tmp[test[test.indexOf(parts[0]) + 1]] = "run..."; - mikado.update(index, tmp); + mikado_table.update(index, tmp); } } } diff --git a/package.json b/package.json index 224d0a3..99f8930 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "mikado": "^0.7.21" }, "devDependencies": { - "mikado-compile": "^0.7.2", + "mikado-compile": "^0.7.44", "web-servo": "^0.5.1" } } diff --git a/test/domc/strict.html b/test/domc/strict.html new file mode 100644 index 0000000..25ed8c2 --- /dev/null +++ b/test/domc/strict.html @@ -0,0 +1,38 @@ + + + domc + + +

Benchmark: domc-0.0.12 (keyed)


+
+
+
+
+
{{item.title}}
+
{{item.content}}
+ +
+
+
+ + + \ No newline at end of file diff --git a/test/doohtml/doo.html.min.js b/test/doohtml/doo.html.min.js new file mode 100644 index 0000000..d4ff7d9 --- /dev/null +++ b/test/doohtml/doo.html.min.js @@ -0,0 +1,2 @@ +/*! For license information please see doo.html.min.js.LICENSE.txt */ +(()=>{"use strict";const e="bind",t="{{",a="}}",l="shadow";class i extends HTMLElement{static get version(){return"v0.92.1-beta"}static define(e,t=null){let a=t||e.name.toLowerCase();customElements.define("doo-"+a,e)}constructor(e=12){super(),this.PAGE_SIZE=e,this.data={},this.place=[],this.template=void 0,this.visibleColumns=[],this.dataHasHtml=!1,this.dataHydrate=!1}static get observedAttributes(){return["data-hydrate","data-has-html","data-key","data-template","bind","data-bind","data-page-size","page-size"]}async attributeChangedCallback(e,t,a){a.length>0&&t!==a&&("page-size"===e?this.PAGE_SIZE=a:"data-key"===e?this.dataKey=a:"data-has-html"===e?this.dataHasHtml=a:"data-hydrate"===e?this.dataHydrate=a:"debug"===e&&(Doo.debug=!0))}dooParse(t){let a=t.cloneNode(!0);a.removeAttribute(e),a.removeAttribute("data-key");let l=a.outerHTML.replace(/\t/g,"").replace(/\n/g,""),i=l;["src","selected","checked","disabled","readonly"].forEach((e=>{l=l.replace(new RegExp(" "+e+'="{{(.+)}}"',"g")," doo-"+e+'="{{$1}}"')}));let s=i===l,n=document.createElement("template");n.innerHTML=l;let o=[];const r=(e,t,a)=>{let l=[],i=e;for(;i!==n.firstElementChild;){let e=i.previousSibling,t=0;for(;e;)t++,e=e.previousSibling;i=i.parentNode,i&&l.unshift(t)}o.push([t,l.slice(1),a])},d=document.createTreeWalker(n.content,NodeFilter.SHOW_TEXT,{acceptNode:()=>NodeFilter.FILTER_ACCEPT});let h=d.nextNode(),c=[];for(;h;){let e=h.wholeText.trim();if(0===e.indexOf("{{")&&e.lastIndexOf("}}")===e.length-2);else{let t=e.replace(/{{/g,"{{").replace(/}}/g,"}}");c.push({node:h.parentNode,oldText:e,newText:t})}h=d.nextNode()}for(let e=0,t=c.length;eNodeFilter.FILTER_ACCEPT});let u=m.nextNode();for(;u;)0===u.textContent.indexOf("{{")&&r(u,u.textContent.replace("{{","").replace("}}",""),"textContent"),u=m.nextNode();const g=document.createTreeWalker(p,NodeFilter.SHOW_ELEMENT,{acceptNode:()=>NodeFilter.FILTER_ACCEPT});for(u=g.nextNode();u;){for(const e of[...u.attributes])e.nodeValue.includes("{{")&&r(u,e.nodeValue.replace("{{","").replace("}}",""),e.name);u=g.nextNode()}let f=p.firstElementChild.outerHTML;return o.forEach((e=>{let t="{{"+e[0]+"}}";f=f.replace(new RegExp(t,"g"),"")})),p.outerHTML=f,{processNode:p.firstElementChild,xHtml:s,dataSlots:o}}initReactiveDataNodes(l){const i=e=>{let t=0;for(;e.parentElement;)e=e.parentElement,t++;return t};this.dataMap=null,this.flex=null,this.hasAttribute("data-map")&&(0===this.getAttribute("data-map").indexOf("Doo.reflect")||(this.dataMap=this.getAttribute("data-map").split("|"))),this.hasAttribute("flex")&&(this.flex=this.getAttribute("flex").replace("[","").replace("]","").split("|"));let s=l.content.querySelectorAll('[data-src="${data-map}"]');s.length>0&&[...s].forEach((e=>{let t=[];this.dataMap.forEach(((e,a)=>{let l=void 0===this.flex[a]?1:this.flex[a],i=e.split(":"),s=i.length>1?i[1]:i[0],n="";l.includes(":")&&(n=l.split(":").length>2?"doo-center":"doo-end",l=l.replace(/:/g,"")),t.push({label:s,fieldName:this.dynamicField(i[0]),flex:l,flexJustify:n})}));let a=this.htmlParse(e,t),l=e.parentElement;l.removeChild(e),l.innerHTML=a+l.innerHTML})),l.innerHTML=l.innerHTML.replace(/\$\{(.+?)\}/gm,((e,i)=>isNaN(i)?this.hasAttribute(i)?this.getAttribute(i):l.hasAttribute(i)?l.getAttribute(i):"":this.dataMap.length>0?t+this.dataMap[i-1]+a:void 0)),[...l.content.querySelectorAll("[bind]")].forEach((e=>{if(!e.hasAttribute("data-src")){let t=this.hasAttribute("doo-dispatch")?"DooX":"this.data";e.setAttribute("data-src",t)}}));let n,o,r=l.content.querySelectorAll("[data-src]"),d=r.length,h=0,c=[];for(h=0;he.level===t.level?0:e.level>t.level?1:-1)).reverse(),h=0;h-1?c[h]:c[h].parentElement&&("void"===r[h].getAttribute(e)||"|DL|UL|TBODY|THEAD|TFOOT|TR|SELECT|SECTION|".indexOf(`|${c[h].parentElement.tagName}|`)>-1)?c[h].parentElement:document.createElement("data"),n.dataKey=c[h].getAttribute(e);let t=this.dooParse(c[h],this,this.dataMap);n.processNode=t.processNode,n.xHtml=t.xHtml,n.dataSlots=t.dataSlots,n.name=h,n.level=i(c[h]),n.useParent=c[h].useParent,n.noRepeat=c[h].hasAttribute("data-norepeat"),"DATA"!==n.tagName&&"STYLE"!==n.tagName&&"LINK"!==n.tagName||(c[h].parentElement?c[h].parentElement.replaceChild(n,c[h]):(console.log("Warning: Templates should only have one child node"),c[h].appendChild(n))),this.place.push(n)}return this.place}async setContext(){this.childNodes.length>0&&this.getElementsByTagName("template")&&this.getElementsByTagName("template").length>0&&this.removeChild(this.getElementsByTagName("template").item(0));let e=this.getAttribute("context")||l;if(e===l){this.componentContainer=this.shadow.host,this.showComponentContainer(!1);let e=this.templateElem.firstElementChild.hasAttribute("class")?" "+this.templateElem.firstElementChild.getAttribute("class"):"",t=this.hasAttribute("class")?this.getAttribute("class")+" ":"";t+e!==""&&this.templateElem.firstElementChild&&this.templateElem.firstElementChild.setAttribute("class",`${t}${e}`),this.shadow.appendChild(this.templateElem)}else"document"===e&&(this.componentContainer=this.parentElement,this.showComponentContainer(!1),this.componentContainer.replaceChild(this.templateElem,this));this.render([],this.place[0])}getItemValue(e,t){return e[t]?e[t]:""}renderHTML(e,t,a=0,l=this.PAGE_SIZE){let i,s,n=t.length,o=a+l;o>n&&(o=n);let r=e.dataSlots.length;const d=(e,t,a)=>{let l=t[a];return e.childNodes[l]?d(e.childNodes[l],t,++a):e},h=(a,l)=>{for(let i=0;il&&(t.textContent=""),this.renderHTML(t,e,a,l-a)):t.textContent=""}append(e=this.data[this.defaultDataSet],t=this.place[0],a=0){this.renderHTML(t,e,a,e.length-a,!0)}getIndex(e,t,a=this.data[this.defaultDataSet]){return a.findIndex(((a,l)=>{if(a[t]===e.key)return l}))}render(t=null,a=0,l=null){if(!this.template)return console.error(this.name+" has no template defined"),console.log("You need to set a data-template attribute on the component"),console.log("You can referense you data-template by a template id"),console.log('Example: '),console.log("Templates can also be external and you can use reletive path to access it"),void console.log('Example: ');this.place||console.log("No target set on the component or inside the template. USAGE: set the data-bind=[your data pointer]");for(let i=0,s=this.place.length;i{const l=new XMLHttpRequest;l.open("GET",e),l.onload=()=>t(l.responseText),l.onerror=()=>a(l.statusText),l.send()}))}async createDooTemplate(e){let t="";t=0===e.indexOf("#")?document.querySelector(e).outerHTML:await this.fetchTemplate(e);let a=document.createElement("div");a.innerHTML=t,a.querySelector("template")&&(a.innerHTML=a.querySelector("template")?t:``);let l=a.querySelector("template").cloneNode(!0);l.removeAttribute("id");let i=document.createElement("template");i.innerHTML=l.innerHTML;let s=i.content,n=s.querySelectorAll("style");return n&&n.length>0&&s.appendChild(n[0]),i}}window.DooHTML||(window.Doo=i,window.DooHTML=i),i.define(i,"html")})(); diff --git a/test/doohtml/index.html b/test/doohtml/index.html new file mode 100644 index 0000000..3ceaee0 --- /dev/null +++ b/test/doohtml/index.html @@ -0,0 +1,50 @@ + + + doohtml + + +

Benchmark: doohtml (recycle)


+
+ + + + + + \ No newline at end of file diff --git a/test/doohtml/internal.html b/test/doohtml/internal.html new file mode 100644 index 0000000..2407938 --- /dev/null +++ b/test/doohtml/internal.html @@ -0,0 +1,50 @@ + + + doohtml + + +

Benchmark: doohtml (recycle)


+
+ + + + + + \ No newline at end of file diff --git a/test/doohtml/keyed.html b/test/doohtml/keyed.html new file mode 100644 index 0000000..ea58456 --- /dev/null +++ b/test/doohtml/keyed.html @@ -0,0 +1,50 @@ + + + doohtml + + +

Benchmark: doohtml (recycle)


+
+ + + + + + \ No newline at end of file diff --git a/test/inferno/strict.html b/test/inferno/strict.html new file mode 100644 index 0000000..2b21058 --- /dev/null +++ b/test/inferno/strict.html @@ -0,0 +1,61 @@ + + + inferno + + +

Benchmark: inferno-7.3.1 (keyed)


+
+
+ + + + + \ No newline at end of file diff --git a/test/innerhtml/strict.html b/test/innerhtml/strict.html new file mode 100644 index 0000000..a8e75c6 --- /dev/null +++ b/test/innerhtml/strict.html @@ -0,0 +1,48 @@ + + + innerHTML + + +

Benchmark: innerHTML (native)


+
+
+ + + \ No newline at end of file diff --git a/test/ivi/dist/main.js b/test/ivi/dist/main.js new file mode 100644 index 0000000..573b07c --- /dev/null +++ b/test/ivi/dist/main.js @@ -0,0 +1 @@ +window._ivi=t=>setState({data:t.length?t:[]}); diff --git a/test/ivi/index.html b/test/ivi/index.html new file mode 100644 index 0000000..eb5c17a --- /dev/null +++ b/test/ivi/index.html @@ -0,0 +1,24 @@ + + + lit-html + + +

Benchmark: lit-html-1.1.2 (recycle)


+
+
+ + + + \ No newline at end of file diff --git a/test/ivi/internal.html b/test/ivi/internal.html new file mode 100644 index 0000000..ec4941e --- /dev/null +++ b/test/ivi/internal.html @@ -0,0 +1,38 @@ + + + lit-html + + +

Benchmark: lit-html-1.1.2 (data-driven)


+
+
+ + + \ No newline at end of file diff --git a/test/ivi/keyed.html b/test/ivi/keyed.html new file mode 100644 index 0000000..5c4cd6b --- /dev/null +++ b/test/ivi/keyed.html @@ -0,0 +1,38 @@ + + + lit-html + + +

Benchmark: lit-html-1.1.2 (keyed)


+
+
+ + + \ No newline at end of file diff --git a/test/ivi/package.json b/test/ivi/package.json new file mode 100644 index 0000000..f162029 --- /dev/null +++ b/test/ivi/package.json @@ -0,0 +1,19 @@ +{ + "private": true, + "description": "", + "main": "index.html", + "scripts": { + "build-dev": "rollup -c -w", + "build-prod": "rollup -c rollup.config.js" + }, + "dependencies": { + "ivi-html": "0.27.0", + "ivi": "0.27.1" + }, + "devDependencies": { + "rollup": "1.15.6", + "rollup-plugin-node-resolve": "5.0.3", + "rollup-plugin-replace": "2.2.0", + "rollup-plugin-terser": "5.0.0" + } +} diff --git a/test/ivi/rollup.config.js b/test/ivi/rollup.config.js new file mode 100644 index 0000000..e91512f --- /dev/null +++ b/test/ivi/rollup.config.js @@ -0,0 +1,38 @@ +import replace from 'rollup-plugin-replace'; +import nodeResolve from 'rollup-plugin-node-resolve'; +import { terser } from 'rollup-plugin-terser'; + +export default { + input: 'src/main.js', + output: { + file: 'dist/main.js', + format: 'es', + }, + plugins: [ + replace({ + values: { + "process.env.NODE_ENV": '"production"', + "process.env.IVI_TARGET": '"evergreen"', + }, + }), + nodeResolve(), + terser({ + parse: { + ecma: 8, + }, + compress: { + ecma: 5, + inline: true, + reduce_funcs: false, + passes: 5, + comparisons: false, + }, + output: { + ecma: 5, + comments: false, + }, + toplevel: true, + module: true, + }) + ], +}; diff --git a/test/ivi/src/main.js b/test/ivi/src/main.js new file mode 100644 index 0000000..da097ae --- /dev/null +++ b/test/ivi/src/main.js @@ -0,0 +1,15 @@ +import { _, render, Events, onClick, withNextFrame, requestDirtyCheck, elementProto, component, useSelect, selector, TrackByKey, key } from "ivi"; +import { h1, div, span, table, tbody, tr, td, a, button } from "ivi-html"; + +window._ivi = (items) => setState({ data: items.length ? items : [] }); + +const template = (items) => items.map((item) => html` +
+
+
${item.title}
+
${item.content}
+ +
+
+`); + diff --git a/test/ivi/strict.html b/test/ivi/strict.html new file mode 100644 index 0000000..eb5c17a --- /dev/null +++ b/test/ivi/strict.html @@ -0,0 +1,24 @@ + + + lit-html + + +

Benchmark: lit-html-1.1.2 (recycle)


+
+
+ + + + \ No newline at end of file diff --git a/test/jquery/strict.html b/test/jquery/strict.html new file mode 100644 index 0000000..1a94df1 --- /dev/null +++ b/test/jquery/strict.html @@ -0,0 +1,49 @@ + + + jquery + + +

Benchmark: jquery-3.4.1 (recycle)


+
+
+ + + + \ No newline at end of file diff --git a/test/knockout/strict.html b/test/knockout/strict.html new file mode 100644 index 0000000..d5e2b79 --- /dev/null +++ b/test/knockout/strict.html @@ -0,0 +1,58 @@ + + + Knockout + + +

Benchmark: knockout-3.5.0 (keyed)


+
+
+
+
+
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/test/lit-html/dist/lib/default-template-processor.js b/test/lit-html/dist/lib/default-template-processor.js new file mode 100644 index 0000000..60bbe1f --- /dev/null +++ b/test/lit-html/dist/lib/default-template-processor.js @@ -0,0 +1,51 @@ +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +import { AttributeCommitter, BooleanAttributePart, EventPart, NodePart, PropertyCommitter } from "./parts.js?module"; +/** + * Creates Parts when a template is instantiated. + */ +export class DefaultTemplateProcessor { + /** + * Create parts for an attribute-position binding, given the event, attribute + * name, and string literals. + * + * @param element The element containing the binding + * @param name The attribute name + * @param strings The string literals. There are always at least two strings, + * event for fully-controlled bindings with a single expression. + */ + handleAttributeExpressions(element, name, strings, options) { + const prefix = name[0]; + if (prefix === '.') { + const committer = new PropertyCommitter(element, name.slice(1), strings); + return committer.parts; + } + if (prefix === '@') { + return [new EventPart(element, name.slice(1), options.eventContext)]; + } + if (prefix === '?') { + return [new BooleanAttributePart(element, name.slice(1), strings)]; + } + const committer = new AttributeCommitter(element, name, strings); + return committer.parts; + } + /** + * Create parts for a text-position binding. + * @param templateFactory + */ + handleTextExpression(options) { + return new NodePart(options); + }} + +export const defaultTemplateProcessor = new DefaultTemplateProcessor(); \ No newline at end of file diff --git a/test/lit-html/dist/lib/directive.js b/test/lit-html/dist/lib/directive.js new file mode 100644 index 0000000..81ff7be --- /dev/null +++ b/test/lit-html/dist/lib/directive.js @@ -0,0 +1,62 @@ +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +const directives = new WeakMap(); +/** + * Brands a function as a directive factory function so that lit-html will call + * the function during template rendering, rather than passing as a value. + * + * A _directive_ is a function that takes a Part as an argument. It has the + * signature: `(part: Part) => void`. + * + * A directive _factory_ is a function that takes arguments for data and + * configuration and returns a directive. Users of directive usually refer to + * the directive factory as the directive. For example, "The repeat directive". + * + * Usually a template author will invoke a directive factory in their template + * with relevant arguments, which will then return a directive function. + * + * Here's an example of using the `repeat()` directive factory that takes an + * array and a function to render an item: + * + * ```js + * html`
    <${repeat(items, (item) => html`
  • ${item}
  • `)}
` + * ``` + * + * When `repeat` is invoked, it returns a directive function that closes over + * `items` and the template function. When the outer template is rendered, the + * return directive function is called with the Part for the expression. + * `repeat` then performs it's custom logic to render multiple items. + * + * @param f The directive factory function. Must be a function that returns a + * function of the signature `(part: Part) => void`. The returned function will + * be called with the part object. + * + * @example + * + * import {directive, html} from 'lit-html'; + * + * const immutable = directive((v) => (part) => { + * if (part.value !== v) { + * part.setValue(v) + * } + * }); + */ +export const directive = f => (...args) => { + const d = f(...args); + directives.set(d, true); + return d; +}; +export const isDirective = o => { + return typeof o === 'function' && directives.has(o); +}; \ No newline at end of file diff --git a/test/lit-html/dist/lib/dom.js b/test/lit-html/dist/lib/dom.js new file mode 100644 index 0000000..317e580 --- /dev/null +++ b/test/lit-html/dist/lib/dom.js @@ -0,0 +1,42 @@ +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +/** + * True if the custom elements polyfill is in use. + */ +export const isCEPolyfill = window.customElements !== undefined && +window.customElements.polyfillWrapFlushCallback !== +undefined; +/** + * Reparents nodes, starting from `start` (inclusive) to `end` (exclusive), + * into another container (could be the same container), before `before`. If + * `before` is null, it appends the nodes to the container. + */ +export const reparentNodes = (container, start, end = null, before = null) => { + while (start !== end) { + const n = start.nextSibling; + container.insertBefore(start, before); + start = n; + } +}; +/** + * Removes nodes, starting from `start` (inclusive) to `end` (exclusive), from + * `container`. + */ +export const removeNodes = (container, start, end = null) => { + while (start !== end) { + const n = start.nextSibling; + container.removeChild(start); + start = n; + } +}; \ No newline at end of file diff --git a/test/lit-html/dist/lib/part.js b/test/lit-html/dist/lib/part.js new file mode 100644 index 0000000..15bbeb6 --- /dev/null +++ b/test/lit-html/dist/lib/part.js @@ -0,0 +1,22 @@ +/** + * @license + * Copyright (c) 2018 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +/** + * A sentinel value that signals that a value was handled by a directive and + * should not be written to the DOM. + */ +export const noChange = {}; +/** + * A sentinel value that signals a NodePart to fully clear its content. + */ +export const nothing = {}; \ No newline at end of file diff --git a/test/lit-html/dist/lib/parts.js b/test/lit-html/dist/lib/parts.js new file mode 100644 index 0000000..94f8063 --- /dev/null +++ b/test/lit-html/dist/lib/parts.js @@ -0,0 +1,447 @@ +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +/** + * @module lit-html + */ +import { isDirective } from "./directive.js?module"; +import { removeNodes } from "./dom.js?module"; +import { noChange, nothing } from "./part.js?module"; +import { TemplateInstance } from "./template-instance.js?module"; +import { TemplateResult } from "./template-result.js?module"; +import { createMarker } from "./template.js?module"; +export const isPrimitive = value => { + return value === null || + !(typeof value === 'object' || typeof value === 'function'); +}; +export const isIterable = value => { + return Array.isArray(value) || + // tslint:disable-next-line:no-any + !!(value && value[Symbol.iterator]); +}; +/** + * Writes attribute values to the DOM for a group of AttributeParts bound to a + * single attibute. The value is only set once even if there are multiple parts + * for an attribute. + */ +export class AttributeCommitter { + constructor(element, name, strings) { + this.dirty = true; + this.element = element; + this.name = name; + this.strings = strings; + this.parts = []; + for (let i = 0; i < strings.length - 1; i++) { + this.parts[i] = this._createPart(); + } + } + /** + * Creates a single part. Override this to create a differnt type of part. + */ + _createPart() { + return new AttributePart(this); + } + _getValue() { + const strings = this.strings; + const l = strings.length - 1; + let text = ''; + for (let i = 0; i < l; i++) { + text += strings[i]; + const part = this.parts[i]; + if (part !== undefined) { + const v = part.value; + if (isPrimitive(v) || !isIterable(v)) { + text += typeof v === 'string' ? v : String(v); + } else + { + for (const t of v) { + text += typeof t === 'string' ? t : String(t); + } + } + } + } + text += strings[l]; + return text; + } + commit() { + if (this.dirty) { + this.dirty = false; + this.element.setAttribute(this.name, this._getValue()); + } + }} + +/** + * A Part that controls all or part of an attribute value. + */ +export class AttributePart { + constructor(committer) { + this.value = undefined; + this.committer = committer; + } + setValue(value) { + if (value !== noChange && (!isPrimitive(value) || value !== this.value)) { + this.value = value; + // If the value is a not a directive, dirty the committer so that it'll + // call setAttribute. If the value is a directive, it'll dirty the + // committer if it calls setValue(). + if (!isDirective(value)) { + this.committer.dirty = true; + } + } + } + commit() { + while (isDirective(this.value)) { + const directive = this.value; + this.value = noChange; + directive(this); + } + if (this.value === noChange) { + return; + } + this.committer.commit(); + }} + +/** + * A Part that controls a location within a Node tree. Like a Range, NodePart + * has start and end locations and can set and update the Nodes between those + * locations. + * + * NodeParts support several value types: primitives, Nodes, TemplateResults, + * as well as arrays and iterables of those types. + */ +export class NodePart { + constructor(options) { + this.value = undefined; + this.__pendingValue = undefined; + this.options = options; + } + /** + * Appends this part into a container. + * + * This part must be empty, as its contents are not automatically moved. + */ + appendInto(container) { + this.startNode = container.appendChild(createMarker()); + this.endNode = container.appendChild(createMarker()); + } + /** + * Inserts this part after the `ref` node (between `ref` and `ref`'s next + * sibling). Both `ref` and its next sibling must be static, unchanging nodes + * such as those that appear in a literal section of a template. + * + * This part must be empty, as its contents are not automatically moved. + */ + insertAfterNode(ref) { + this.startNode = ref; + this.endNode = ref.nextSibling; + } + /** + * Appends this part into a parent part. + * + * This part must be empty, as its contents are not automatically moved. + */ + appendIntoPart(part) { + part.__insert(this.startNode = createMarker()); + part.__insert(this.endNode = createMarker()); + } + /** + * Inserts this part after the `ref` part. + * + * This part must be empty, as its contents are not automatically moved. + */ + insertAfterPart(ref) { + ref.__insert(this.startNode = createMarker()); + this.endNode = ref.endNode; + ref.endNode = this.startNode; + } + setValue(value) { + this.__pendingValue = value; + } + commit() { + while (isDirective(this.__pendingValue)) { + const directive = this.__pendingValue; + this.__pendingValue = noChange; + directive(this); + } + const value = this.__pendingValue; + if (value === noChange) { + return; + } + if (isPrimitive(value)) { + if (value !== this.value) { + this.__commitText(value); + } + } else + if (value instanceof TemplateResult) { + this.__commitTemplateResult(value); + } else + if (value instanceof Node) { + this.__commitNode(value); + } else + if (isIterable(value)) { + this.__commitIterable(value); + } else + if (value === nothing) { + this.value = nothing; + this.clear(); + } else + { + // Fallback, will render the string representation + this.__commitText(value); + } + } + __insert(node) { + this.endNode.parentNode.insertBefore(node, this.endNode); + } + __commitNode(value) { + if (this.value === value) { + return; + } + this.clear(); + this.__insert(value); + this.value = value; + } + __commitText(value) { + const node = this.startNode.nextSibling; + value = value == null ? '' : value; + // If `value` isn't already a string, we explicitly convert it here in case + // it can't be implicitly converted - i.e. it's a symbol. + const valueAsString = typeof value === 'string' ? value : String(value); + if (node === this.endNode.previousSibling && + node.nodeType === 3 /* Node.TEXT_NODE */) { + // If we only have a single text node between the markers, we can just + // set its value, rather than replacing it. + // TODO(justinfagnani): Can we just check if this.value is primitive? + node.data = valueAsString; + } else + { + this.__commitNode(document.createTextNode(valueAsString)); + } + this.value = value; + } + __commitTemplateResult(value) { + const template = this.options.templateFactory(value); + if (this.value instanceof TemplateInstance && + this.value.template === template) { + this.value.update(value.values); + } else + { + // Make sure we propagate the template processor from the TemplateResult + // so that we use its syntax extension, etc. The template factory comes + // from the render function options so that it can control template + // caching and preprocessing. + const instance = new TemplateInstance(template, value.processor, this.options); + const fragment = instance._clone(); + instance.update(value.values); + this.__commitNode(fragment); + this.value = instance; + } + } + __commitIterable(value) { + // For an Iterable, we create a new InstancePart per item, then set its + // value to the item. This is a little bit of overhead for every item in + // an Iterable, but it lets us recurse easily and efficiently update Arrays + // of TemplateResults that will be commonly returned from expressions like: + // array.map((i) => html`${i}`), by reusing existing TemplateInstances. + // If _value is an array, then the previous render was of an + // iterable and _value will contain the NodeParts from the previous + // render. If _value is not an array, clear this part and make a new + // array for NodeParts. + if (!Array.isArray(this.value)) { + this.value = []; + this.clear(); + } + // Lets us keep track of how many items we stamped so we can clear leftover + // items from a previous render + const itemParts = this.value; + let partIndex = 0; + let itemPart; + for (const item of value) { + // Try to reuse an existing part + itemPart = itemParts[partIndex]; + // If no existing part, create a new one + if (itemPart === undefined) { + itemPart = new NodePart(this.options); + itemParts.push(itemPart); + if (partIndex === 0) { + itemPart.appendIntoPart(this); + } else + { + itemPart.insertAfterPart(itemParts[partIndex - 1]); + } + } + itemPart.setValue(item); + itemPart.commit(); + partIndex++; + } + if (partIndex < itemParts.length) { + // Truncate the parts array so _value reflects the current state + itemParts.length = partIndex; + this.clear(itemPart && itemPart.endNode); + } + } + clear(startNode = this.startNode) { + removeNodes(this.startNode.parentNode, startNode.nextSibling, this.endNode); + }} + +/** + * Implements a boolean attribute, roughly as defined in the HTML + * specification. + * + * If the value is truthy, then the attribute is present with a value of + * ''. If the value is falsey, the attribute is removed. + */ +export class BooleanAttributePart { + constructor(element, name, strings) { + this.value = undefined; + this.__pendingValue = undefined; + if (strings.length !== 2 || strings[0] !== '' || strings[1] !== '') { + throw new Error('Boolean attributes can only contain a single expression'); + } + this.element = element; + this.name = name; + this.strings = strings; + } + setValue(value) { + this.__pendingValue = value; + } + commit() { + while (isDirective(this.__pendingValue)) { + const directive = this.__pendingValue; + this.__pendingValue = noChange; + directive(this); + } + if (this.__pendingValue === noChange) { + return; + } + const value = !!this.__pendingValue; + if (this.value !== value) { + if (value) { + this.element.setAttribute(this.name, ''); + } else + { + this.element.removeAttribute(this.name); + } + this.value = value; + } + this.__pendingValue = noChange; + }} + +/** + * Sets attribute values for PropertyParts, so that the value is only set once + * even if there are multiple parts for a property. + * + * If an expression controls the whole property value, then the value is simply + * assigned to the property under control. If there are string literals or + * multiple expressions, then the strings are expressions are interpolated into + * a string first. + */ +export class PropertyCommitter extends AttributeCommitter { + constructor(element, name, strings) { + super(element, name, strings); + this.single = + strings.length === 2 && strings[0] === '' && strings[1] === ''; + } + _createPart() { + return new PropertyPart(this); + } + _getValue() { + if (this.single) { + return this.parts[0].value; + } + return super._getValue(); + } + commit() { + if (this.dirty) { + this.dirty = false; + // tslint:disable-next-line:no-any + this.element[this.name] = this._getValue(); + } + }} + +export class PropertyPart extends AttributePart {} + +// Detect event listener options support. If the `capture` property is read +// from the options object, then options are supported. If not, then the thrid +// argument to add/removeEventListener is interpreted as the boolean capture +// value so we should only pass the `capture` property. +let eventOptionsSupported = false; +try { + const options = { + get capture() { + eventOptionsSupported = true; + return false; + } }; + + // tslint:disable-next-line:no-any + window.addEventListener('test', options, options); + // tslint:disable-next-line:no-any + window.removeEventListener('test', options, options); +} +catch (_e) { +} +export class EventPart { + constructor(element, eventName, eventContext) { + this.value = undefined; + this.__pendingValue = undefined; + this.element = element; + this.eventName = eventName; + this.eventContext = eventContext; + this.__boundHandleEvent = e => this.handleEvent(e); + } + setValue(value) { + this.__pendingValue = value; + } + commit() { + while (isDirective(this.__pendingValue)) { + const directive = this.__pendingValue; + this.__pendingValue = noChange; + directive(this); + } + if (this.__pendingValue === noChange) { + return; + } + const newListener = this.__pendingValue; + const oldListener = this.value; + const shouldRemoveListener = newListener == null || + oldListener != null && ( + newListener.capture !== oldListener.capture || + newListener.once !== oldListener.once || + newListener.passive !== oldListener.passive); + const shouldAddListener = newListener != null && (oldListener == null || shouldRemoveListener); + if (shouldRemoveListener) { + this.element.removeEventListener(this.eventName, this.__boundHandleEvent, this.__options); + } + if (shouldAddListener) { + this.__options = getOptions(newListener); + this.element.addEventListener(this.eventName, this.__boundHandleEvent, this.__options); + } + this.value = newListener; + this.__pendingValue = noChange; + } + handleEvent(event) { + if (typeof this.value === 'function') { + this.value.call(this.eventContext || this.element, event); + } else + { + this.value.handleEvent(event); + } + }} + +// We copy options because of the inconsistent behavior of browsers when reading +// the third argument of add/removeEventListener. IE11 doesn't support options +// at all. Chrome 41 only reads `capture` if the argument is an object. +const getOptions = o => o && ( +eventOptionsSupported ? +{ capture: o.capture, passive: o.passive, once: o.once } : +o.capture); \ No newline at end of file diff --git a/test/lit-html/dist/lib/render.js b/test/lit-html/dist/lib/render.js new file mode 100644 index 0000000..78ea4c3 --- /dev/null +++ b/test/lit-html/dist/lib/render.js @@ -0,0 +1,45 @@ +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +/** + * @module lit-html + */ +import { removeNodes } from "./dom.js?module"; +import { NodePart } from "./parts.js?module"; +import { templateFactory } from "./template-factory.js?module"; +export const parts = new WeakMap(); +/** + * Renders a template result or other value to a container. + * + * To update a container with new values, reevaluate the template literal and + * call `render` with the new result. + * + * @param result Any value renderable by NodePart - typically a TemplateResult + * created by evaluating a template tag like `html` or `svg`. + * @param container A DOM parent to render to. The entire contents are either + * replaced, or efficiently updated if the same result type was previous + * rendered there. + * @param options RenderOptions for the entire render tree rendered to this + * container. Render options must *not* change between renders to the same + * container, as those changes will not effect previously rendered DOM. + */ +export const render = (result, container, options) => { + let part = parts.get(container); + if (part === undefined) { + removeNodes(container, container.firstChild); + parts.set(container, part = new NodePart(Object.assign({ templateFactory }, options))); + part.appendInto(container); + } + part.setValue(result); + part.commit(); +}; \ No newline at end of file diff --git a/test/lit-html/dist/lib/template-factory.js b/test/lit-html/dist/lib/template-factory.js new file mode 100644 index 0000000..780030d --- /dev/null +++ b/test/lit-html/dist/lib/template-factory.js @@ -0,0 +1,47 @@ +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +import { marker, Template } from "./template.js?module"; +/** + * The default TemplateFactory which caches Templates keyed on + * result.type and result.strings. + */ +export function templateFactory(result) { + let templateCache = templateCaches.get(result.type); + if (templateCache === undefined) { + templateCache = { + stringsArray: new WeakMap(), + keyString: new Map() }; + + templateCaches.set(result.type, templateCache); + } + let template = templateCache.stringsArray.get(result.strings); + if (template !== undefined) { + return template; + } + // If the TemplateStringsArray is new, generate a key from the strings + // This key is shared between all templates with identical content + const key = result.strings.join(marker); + // Check if we already have a Template for this key + template = templateCache.keyString.get(key); + if (template === undefined) { + // If we have not seen this key before, create a new Template + template = new Template(result, result.getTemplateElement()); + // Cache the Template for this key + templateCache.keyString.set(key, template); + } + // Cache all future queries for this TemplateStringsArray + templateCache.stringsArray.set(result.strings, template); + return template; +} +export const templateCaches = new Map(); \ No newline at end of file diff --git a/test/lit-html/dist/lib/template-instance.js b/test/lit-html/dist/lib/template-instance.js new file mode 100644 index 0000000..2ec495b --- /dev/null +++ b/test/lit-html/dist/lib/template-instance.js @@ -0,0 +1,135 @@ +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +/** + * @module lit-html + */ +import { isCEPolyfill } from "./dom.js?module"; +import { isTemplatePartActive } from "./template.js?module"; +/** + * An instance of a `Template` that can be attached to the DOM and updated + * with new values. + */ +export class TemplateInstance { + constructor(template, processor, options) { + this.__parts = []; + this.template = template; + this.processor = processor; + this.options = options; + } + update(values) { + let i = 0; + for (const part of this.__parts) { + if (part !== undefined) { + part.setValue(values[i]); + } + i++; + } + for (const part of this.__parts) { + if (part !== undefined) { + part.commit(); + } + } + } + _clone() { + // There are a number of steps in the lifecycle of a template instance's + // DOM fragment: + // 1. Clone - create the instance fragment + // 2. Adopt - adopt into the main document + // 3. Process - find part markers and create parts + // 4. Upgrade - upgrade custom elements + // 5. Update - set node, attribute, property, etc., values + // 6. Connect - connect to the document. Optional and outside of this + // method. + // + // We have a few constraints on the ordering of these steps: + // * We need to upgrade before updating, so that property values will pass + // through any property setters. + // * We would like to process before upgrading so that we're sure that the + // cloned fragment is inert and not disturbed by self-modifying DOM. + // * We want custom elements to upgrade even in disconnected fragments. + // + // Given these constraints, with full custom elements support we would + // prefer the order: Clone, Process, Adopt, Upgrade, Update, Connect + // + // But Safari dooes not implement CustomElementRegistry#upgrade, so we + // can not implement that order and still have upgrade-before-update and + // upgrade disconnected fragments. So we instead sacrifice the + // process-before-upgrade constraint, since in Custom Elements v1 elements + // must not modify their light DOM in the constructor. We still have issues + // when co-existing with CEv0 elements like Polymer 1, and with polyfills + // that don't strictly adhere to the no-modification rule because shadow + // DOM, which may be created in the constructor, is emulated by being placed + // in the light DOM. + // + // The resulting order is on native is: Clone, Adopt, Upgrade, Process, + // Update, Connect. document.importNode() performs Clone, Adopt, and Upgrade + // in one step. + // + // The Custom Elements v1 polyfill supports upgrade(), so the order when + // polyfilled is the more ideal: Clone, Process, Adopt, Upgrade, Update, + // Connect. + const fragment = isCEPolyfill ? + this.template.element.content.cloneNode(true) : + document.importNode(this.template.element.content, true); + const stack = []; + const parts = this.template.parts; + // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null + const walker = document.createTreeWalker(fragment, 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */, null, false); + let partIndex = 0; + let nodeIndex = 0; + let part; + let node = walker.nextNode(); + // Loop through all the nodes and parts of a template + while (partIndex < parts.length) { + part = parts[partIndex]; + if (!isTemplatePartActive(part)) { + this.__parts.push(undefined); + partIndex++; + continue; + } + // Progress the tree walker until we find our next part's node. + // Note that multiple parts may share the same node (attribute parts + // on a single element), so this loop may not run at all. + while (nodeIndex < part.index) { + nodeIndex++; + if (node.nodeName === 'TEMPLATE') { + stack.push(node); + walker.currentNode = node.content; + } + if ((node = walker.nextNode()) === null) { + // We've exhausted the content inside a nested template element. + // Because we still have parts (the outer for-loop), we know: + // - There is a template in the stack + // - The walker will find a nextNode outside the template + walker.currentNode = stack.pop(); + node = walker.nextNode(); + } + } + // We've arrived at our part's node. + if (part.type === 'node') { + const part = this.processor.handleTextExpression(this.options); + part.insertAfterNode(node.previousSibling); + this.__parts.push(part); + } else + { + this.__parts.push(...this.processor.handleAttributeExpressions(node, part.name, part.strings, this.options)); + } + partIndex++; + } + if (isCEPolyfill) { + document.adoptNode(fragment); + customElements.upgrade(fragment); + } + return fragment; + }} \ No newline at end of file diff --git a/test/lit-html/dist/lib/template-result.js b/test/lit-html/dist/lib/template-result.js new file mode 100644 index 0000000..a70eeda --- /dev/null +++ b/test/lit-html/dist/lib/template-result.js @@ -0,0 +1,111 @@ +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +/** + * @module lit-html + */ +import { reparentNodes } from "./dom.js?module"; +import { boundAttributeSuffix, lastAttributeNameRegex, marker, nodeMarker } from "./template.js?module"; +const commentMarker = ` ${marker} `; +/** + * The return type of `html`, which holds a Template and the values from + * interpolated expressions. + */ +export class TemplateResult { + constructor(strings, values, type, processor) { + this.strings = strings; + this.values = values; + this.type = type; + this.processor = processor; + } + /** + * Returns a string of HTML used to create a `