From 570b6bfb551848c490c07625df3c149e5bddce79 Mon Sep 17 00:00:00 2001 From: Ahmed Elzeiny Date: Fri, 21 Jul 2023 00:19:24 -0700 Subject: [PATCH] Introduce Google's IncrementalDOM library to JS Benchmarks. --- frameworks/keyed/incremental-dom/index.html | 15 ++++ .../keyed/incremental-dom/package-lock.json | 77 +++++++++++++++++ frameworks/keyed/incremental-dom/package.json | 29 +++++++ .../incremental-dom/src/components/button.ts | 13 +++ .../incremental-dom/src/components/div.ts | 7 ++ .../incremental-dom/src/components/header.ts | 7 ++ .../incremental-dom/src/components/span.ts | 13 +++ .../incremental-dom/src/components/table.ts | 26 ++++++ .../incremental-dom/src/data-collection.ts | 62 ++++++++++++++ frameworks/keyed/incremental-dom/src/main.ts | 84 +++++++++++++++++++ 10 files changed, 333 insertions(+) create mode 100644 frameworks/keyed/incremental-dom/index.html create mode 100644 frameworks/keyed/incremental-dom/package-lock.json create mode 100644 frameworks/keyed/incremental-dom/package.json create mode 100644 frameworks/keyed/incremental-dom/src/components/button.ts create mode 100644 frameworks/keyed/incremental-dom/src/components/div.ts create mode 100644 frameworks/keyed/incremental-dom/src/components/header.ts create mode 100644 frameworks/keyed/incremental-dom/src/components/span.ts create mode 100644 frameworks/keyed/incremental-dom/src/components/table.ts create mode 100644 frameworks/keyed/incremental-dom/src/data-collection.ts create mode 100644 frameworks/keyed/incremental-dom/src/main.ts 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);