diff --git a/frameworks/keyed/incremental-dom/index.html b/frameworks/keyed/incremental-dom/index.html
new file mode 100644
index 000000000..af992ce83
--- /dev/null
+++ b/frameworks/keyed/incremental-dom/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Incremental DOM-"keyed"
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frameworks/keyed/incremental-dom/package-lock.json b/frameworks/keyed/incremental-dom/package-lock.json
new file mode 100644
index 000000000..992e877f2
--- /dev/null
+++ b/frameworks/keyed/incremental-dom/package-lock.json
@@ -0,0 +1,77 @@
+{
+ "name": "js-framework-benchmark-idom",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "js-framework-benchmark-idom",
+ "version": "1.0.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "incremental-dom": "^0.7.0"
+ },
+ "devDependencies": {
+ "esbuild": "^0.18.15"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.18.15",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.15.tgz",
+ "integrity": "sha512-XmrFwEOYauKte9QjS6hz60FpOCnw4zaPAb7XV7O4lx1r39XjJhTN7ZpXqJh4sN6q60zbP6QwAVVA8N/wUyBH/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.18.15",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.15.tgz",
+ "integrity": "sha512-3WOOLhrvuTGPRzQPU6waSDWrDTnQriia72McWcn6UCi43GhCHrXH4S59hKMeez+IITmdUuUyvbU9JIp+t3xlPQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.18.15",
+ "@esbuild/android-arm64": "0.18.15",
+ "@esbuild/android-x64": "0.18.15",
+ "@esbuild/darwin-arm64": "0.18.15",
+ "@esbuild/darwin-x64": "0.18.15",
+ "@esbuild/freebsd-arm64": "0.18.15",
+ "@esbuild/freebsd-x64": "0.18.15",
+ "@esbuild/linux-arm": "0.18.15",
+ "@esbuild/linux-arm64": "0.18.15",
+ "@esbuild/linux-ia32": "0.18.15",
+ "@esbuild/linux-loong64": "0.18.15",
+ "@esbuild/linux-mips64el": "0.18.15",
+ "@esbuild/linux-ppc64": "0.18.15",
+ "@esbuild/linux-riscv64": "0.18.15",
+ "@esbuild/linux-s390x": "0.18.15",
+ "@esbuild/linux-x64": "0.18.15",
+ "@esbuild/netbsd-x64": "0.18.15",
+ "@esbuild/openbsd-x64": "0.18.15",
+ "@esbuild/sunos-x64": "0.18.15",
+ "@esbuild/win32-arm64": "0.18.15",
+ "@esbuild/win32-ia32": "0.18.15",
+ "@esbuild/win32-x64": "0.18.15"
+ }
+ },
+ "node_modules/incremental-dom": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/incremental-dom/-/incremental-dom-0.7.0.tgz",
+ "integrity": "sha512-SBHQ6AiCmtwh7TU9hjq2CspasJe7ggGa9k+qYZft+d5Qq9v7V+07wlnRSZH5GGYjI8wn6U5p7dDua7f1bih52g=="
+ }
+ }
+}
diff --git a/frameworks/keyed/incremental-dom/package.json b/frameworks/keyed/incremental-dom/package.json
new file mode 100644
index 000000000..e2f1ce395
--- /dev/null
+++ b/frameworks/keyed/incremental-dom/package.json
@@ -0,0 +1,29 @@
+{
+ "author": "Ahmed Elzeiny",
+ "license": "Apache-2.0",
+ "name": "js-framework-benchmark-incremental-dom",
+ "version": "1.0.0",
+ "description": "Benchmark for Google's incremental-dom framework",
+ "js-framework-benchmark": {
+ "frameworkVersionFromPackage": "incremental-dom",
+ "frameworkHomeURL": "http://google.github.io/incremental-dom/"
+ },
+ "scripts": {
+ "build-dev": "esbuild src/main.ts --bundle --outfile=dist/main.js --watch",
+ "build-prod": "esbuild src/main.ts --bundle --minify --outfile=dist/main.js"
+ },
+ "keywords": [
+ "ractive"
+ ],
+ "homepage": "https://github.com/krausest/js-framework-benchmark",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/krausest/js-framework-benchmark.git"
+ },
+ "dependencies": {
+ "incremental-dom": "^0.7.0"
+ },
+ "devDependencies": {
+ "esbuild": "^0.18.15"
+ }
+}
\ No newline at end of file
diff --git a/frameworks/keyed/incremental-dom/src/components/button.ts b/frameworks/keyed/incremental-dom/src/components/button.ts
new file mode 100644
index 000000000..4464f9ec2
--- /dev/null
+++ b/frameworks/keyed/incremental-dom/src/components/button.ts
@@ -0,0 +1,13 @@
+import { elementOpen, close, text, Key } from 'incremental-dom';
+
+export function Button(id: string, innerText: string, onClick: () => void, key: Key = null) {
+ elementOpen(
+ 'button', key, null,
+ // attributes
+ 'class', 'btn btn-primary btn-block',
+ 'id', id
+ );
+ text(innerText)
+ const el = close();
+ el.addEventListener('click', onClick);
+}
\ No newline at end of file
diff --git a/frameworks/keyed/incremental-dom/src/components/div.ts b/frameworks/keyed/incremental-dom/src/components/div.ts
new file mode 100644
index 000000000..0991a5d49
--- /dev/null
+++ b/frameworks/keyed/incremental-dom/src/components/div.ts
@@ -0,0 +1,7 @@
+import { elementOpen, elementClose } from 'incremental-dom';
+
+export function Div(className: string, func: () => void) {
+ elementOpen('div', null, null, 'class', className);
+ func();
+ elementClose('div');
+}
\ No newline at end of file
diff --git a/frameworks/keyed/incremental-dom/src/components/header.ts b/frameworks/keyed/incremental-dom/src/components/header.ts
new file mode 100644
index 000000000..f5592b07e
--- /dev/null
+++ b/frameworks/keyed/incremental-dom/src/components/header.ts
@@ -0,0 +1,7 @@
+import { elementOpen, close, text } from 'incremental-dom';
+
+export function H1(value: string) {
+ elementOpen('h1');
+ text(value);
+ close();
+}
\ No newline at end of file
diff --git a/frameworks/keyed/incremental-dom/src/components/span.ts b/frameworks/keyed/incremental-dom/src/components/span.ts
new file mode 100644
index 000000000..8fbd1b415
--- /dev/null
+++ b/frameworks/keyed/incremental-dom/src/components/span.ts
@@ -0,0 +1,13 @@
+import { elementOpen } from "incremental-dom";
+
+export function GlyphIconSpan() {
+ elementOpen(
+ 'span', null, null,
+ // attributes
+ 'class', 'preloadicon glyphicon glyphicon-remove',
+ 'aria-hidden', 'true'
+ );
+
+ close();
+ //
+}
\ No newline at end of file
diff --git a/frameworks/keyed/incremental-dom/src/components/table.ts b/frameworks/keyed/incremental-dom/src/components/table.ts
new file mode 100644
index 000000000..82b0ffd8e
--- /dev/null
+++ b/frameworks/keyed/incremental-dom/src/components/table.ts
@@ -0,0 +1,26 @@
+import { elementOpen, elementClose, close, text } from 'incremental-dom';
+import { DataEntry } from '../data-collection';
+
+
+export function Table(func: () => void) {
+ elementOpen('table', null, null, 'class', 'table table-hover table-striped test-data');
+ elementOpen('tbody', null, null, 'id', 'tbody');
+ func();
+ elementClose('tbody');
+ elementClose('table');
+}
+
+function TableCell(value: string) {
+ elementOpen('td');
+ text(value);
+ close();
+}
+
+export function TableRow(data: DataEntry) {
+ elementOpen('tr', data.id);
+
+ TableCell(data.id.toString());
+ TableCell(data.label);
+
+ close();
+}
\ No newline at end of file
diff --git a/frameworks/keyed/incremental-dom/src/data-collection.ts b/frameworks/keyed/incremental-dom/src/data-collection.ts
new file mode 100644
index 000000000..00f205f20
--- /dev/null
+++ b/frameworks/keyed/incremental-dom/src/data-collection.ts
@@ -0,0 +1,62 @@
+export interface DataEntry {
+ label: string;
+ id: number;
+}
+
+const A = ["pretty", "large", "big", "small", "tall", "short", "long", "handsome", "plain", "quaint", "clean",
+ "elegant", "easy", "angry", "crazy", "helpful", "mushy", "odd", "unsightly", "adorable", "important", "inexpensive",
+ "cheap", "expensive", "fancy"];
+const C = ["red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", "orange"];
+const N = ["table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse",
+ "keyboard"];
+
+let nextId = 0;
+
+const random = (max) => Math.round(Math.random() * 1000) % max;
+
+export const genLabel = () => `${A[random(A.length)]} ${C[random(C.length)]} ${N[random(N.length)]}`;
+export const genId = () => ++nextId;
+export function genData(): DataEntry {
+ return { label: genLabel(), id: genId() }
+}
+
+
+export class DataCollection {
+ data: DataEntry[];
+
+ constructor() {
+ this.data = [];
+ }
+
+ createRows(n: number) {
+ this.data = new Array(n);
+ this.data.length = n;
+ for (let i = 0; i < n; i++) {
+ this.data[i] = genData();
+ }
+ }
+
+ appendRows(n: number) {
+ for (let i = 0; i < n; i++) {
+ this.data.push(genData());
+ }
+ }
+
+ mutate(mutator: (e: DataEntry) => void, increment: number = 1) {
+ for (let i = 0; i < this.data.length; i += increment) {
+ mutator(this.data[i]);
+ }
+ }
+
+ clear() {
+ this.data = [];
+ }
+
+ swap(idxA: number, idxB: number) {
+ if (this.data.length > idxA && this.data.length > idxB) {
+ const tmp = this.data[idxA];
+ this.data[idxA] = this.data[idxB];
+ this.data[idxB] = tmp;
+ }
+ }
+}
diff --git a/frameworks/keyed/incremental-dom/src/main.ts b/frameworks/keyed/incremental-dom/src/main.ts
new file mode 100644
index 000000000..7437a5855
--- /dev/null
+++ b/frameworks/keyed/incremental-dom/src/main.ts
@@ -0,0 +1,84 @@
+/**
+ * Incremental DOM is a virtual-dom diffing library; not a framework.
+ * See: http://google.github.io/incremental-dom/
+ */
+
+import { patch } from "incremental-dom";
+import { DataCollection } from "./data-collection";
+
+import { Button } from "./components/button";
+import { H1 } from "./components/header";
+import { Div } from "./components/div";
+import { GlyphIconSpan } from "./components/span";
+import { Table, TableRow } from "./components/table";
+
+
+let root: HTMLElement;
+let state: DataCollection = new DataCollection();
+
+function render() {
+ if (!root) root = document.getElementById('main')!;
+ patch(root, incrementalDom);
+}
+
+function actionRun() {
+ state.createRows(1000);
+ render();
+}
+
+function actionRunLots() {
+ state.createRows(10000);
+ render();
+}
+
+function actionAdd() {
+ state.appendRows(1000);
+ render();
+}
+
+function actionUpdate() {
+ state.mutate((e) => { e.label += ' !!! ' }, 10);
+ render();
+}
+
+function actionClear() {
+ state.clear();
+ render();
+}
+
+function actionSwapRows() {
+ state.swap(1, 998);
+ render();
+}
+
+
+function incrementalDom() {
+ const divBtnClass = 'col-sm-6 smallpad';
+
+ Div('container', () => {
+ Div('jumbotron', () => {
+ Div('row', () => {
+ Div('col-md-6', () => {
+ H1('Incremental DOM-"keyed"');
+ });
+ Div('col-md-6', () => {
+ Div(divBtnClass, () => Button('run', 'Create 1,000 rows', actionRun));
+ Div(divBtnClass, () => Button('runlots', 'Create 10,000 rows', actionRunLots));
+ Div(divBtnClass, () => Button('add', 'Append 1,000 rows', actionAdd));
+ Div(divBtnClass, () => Button('update', 'Update every 10th row', actionUpdate));
+ Div(divBtnClass, () => Button('clear', 'Clear', actionClear));
+ Div(divBtnClass, () => Button('swaprows', 'Swap Rows', actionSwapRows));
+ });
+ });
+ });
+ Table(() => {
+ for (let i = 0; i < state.data.length; i++) {
+ TableRow(state.data[i]);
+ }
+ });
+ GlyphIconSpan();
+ });
+
+}
+
+window.addEventListener('load', render);