diff --git a/maquette-v3.0.1-keyed/build.js b/maquette-v3.0.1-keyed/build.js
new file mode 100644
index 000000000..dc0ff244e
--- /dev/null
+++ b/maquette-v3.0.1-keyed/build.js
@@ -0,0 +1,16 @@
+const rollup = require('rollup').rollup;
+const buble = require('rollup-plugin-buble');
+const uglify = require('rollup-plugin-uglify');
+const commonjs = require('rollup-plugin-commonjs');
+
+rollup({
+ input: 'src/main.es6.js',
+ plugins: [
+ commonjs(),
+ buble(),
+ uglify(),
+ ]
+}).then(bundle => bundle.write({
+ file: 'dist/bundle.js',
+ format: 'iife'
+})).catch(err => console.log(err.stack));
\ No newline at end of file
diff --git a/maquette-v3.0.1-keyed/index.html b/maquette-v3.0.1-keyed/index.html
new file mode 100644
index 000000000..2e0c7bc67
--- /dev/null
+++ b/maquette-v3.0.1-keyed/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+ maquette v3.0.1-keyed
+
+
+
+
+
+
diff --git a/maquette-v3.0.1-keyed/package.json b/maquette-v3.0.1-keyed/package.json
new file mode 100644
index 000000000..517fec7ff
--- /dev/null
+++ b/maquette-v3.0.1-keyed/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "js-framework-benchmark-maquette-keyed",
+ "version": "3.0.1-non-keyed",
+ "description": "Benchmark for maquette framework (keyed)",
+ "scripts": {
+ "build-dev": "node build.js",
+ "build-prod": "node build.js"
+ },
+ "devDependencies": {
+ "rollup": "*",
+ "rollup-plugin-buble": "*",
+ "rollup-plugin-commonjs": "*",
+ "rollup-plugin-uglify": "*"
+ },
+ "dependencies": {
+ "maquette": "^3.0.1"
+ }
+}
diff --git a/maquette-v3.0.1-keyed/src/main.es6.js b/maquette-v3.0.1-keyed/src/main.es6.js
new file mode 100644
index 000000000..3fc465034
--- /dev/null
+++ b/maquette-v3.0.1-keyed/src/main.es6.js
@@ -0,0 +1,112 @@
+import maquette from './maquette.umd.js';
+import {Store} from './store.es6';
+
+const h = maquette.h;
+const projector = maquette.createProjector();
+
+const store = new Store();
+const app = App();
+
+projector.append(document.body, app.render);
+
+function App() {
+ const jumbo = Jumbotron();
+ const table = Table();
+
+ return {
+ render: () =>
+ h("div#main", [
+ h("div.container", [
+ jumbo.render(),
+ table.render(),
+ h("span.preloadicon.glyphicon.glyphicon-remove", {"aria-hidden": ""})
+ ])
+ ])
+ };
+}
+
+function Jumbotron() {
+ let exec = name => e => {
+ store[name]();
+ };
+
+ let run = exec("run");
+ let runLots = exec("runLots");
+ let add = exec("add");
+ let update = exec("update");
+ let clear = exec("clear");
+ let swapRows = exec("swapRows");
+
+ return {
+ render: () =>
+ h("div.jumbotron", [
+ h("div.row", [
+ h("div.col-md-6", [
+ h("h1", ["maquette v3.0.1 (keyed)"])
+ ]),
+ h("div.col-md-6", [
+ h("div.row", [
+ h("div.col-sm-6.smallpad", [
+ h("button.btn.btn-primary.btn-block#run", {type: "button", onclick: run}, ["Create 1,000 rows"])
+ ]),
+ h("div.col-sm-6.smallpad", [
+ h("button.btn.btn-primary.btn-block#runlots", {type: "button", onclick: runLots}, ["Create 10,000 rows"])
+ ]),
+ h("div.col-sm-6.smallpad", [
+ h("button.btn.btn-primary.btn-block#add", {type: "button", onclick: add}, ["Append 1,000 rows"])
+ ]),
+ h("div.col-sm-6.smallpad", [
+ h("button.btn.btn-primary.btn-block#update", {type: "button", onclick: update}, ["Update every 10th row"])
+ ]),
+ h("div.col-sm-6.smallpad", [
+ h("button.btn.btn-primary.btn-block#clear", {type: "button", onclick: clear}, ["Clear"])
+ ]),
+ h("div.col-sm-6.smallpad", [
+ h("button.btn.btn-primary.btn-block#swaprows", {type: "button", onclick: swapRows}, ["Swap Rows"])
+ ])
+ ])
+ ])
+ ])
+ ])
+ };
+}
+
+function Table() {
+ // delegated handler
+ function tableClick(e) {
+ var node = e.target;
+
+ if (node.matches(".remove, .remove *")) {
+ while (node.nodeName != "TR")
+ node = node.parentNode;
+ store.delete(+node.firstChild.textContent);
+ e.stopPropagation();
+ }
+ else if (node.matches(".lbl")) {
+ while (node.nodeName != "TR")
+ node = node.parentNode;
+ store.select(+node.firstChild.textContent);
+ e.stopPropagation();
+ }
+ }
+
+ return {
+ render: () =>
+ h("table.table.table-hover.table-striped.test-data", {onclick: tableClick}, [
+ h("tbody", store.data.map(item =>
+ h("tr" + (item.id === store.selected ? '.danger' : ''), {key: item.id}, [
+ h("td.col-md-1", [""+item.id]),
+ h("td.col-md-4", [
+ h("a.lbl", [item.label])
+ ]),
+ h("td.col-md-1", [
+ h("a.remove", [
+ h("span.glyphicon.glyphicon-remove", {"aria-hidden": ""})
+ ])
+ ]),
+ h("td.col-md-6")
+ ])
+ ))
+ ])
+ };
+}
\ No newline at end of file
diff --git a/maquette-v3.0.1-keyed/src/maquette.umd.js b/maquette-v3.0.1-keyed/src/maquette.umd.js
new file mode 100644
index 000000000..1b1b86795
--- /dev/null
+++ b/maquette-v3.0.1-keyed/src/maquette.umd.js
@@ -0,0 +1,856 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (factory((global.maquette = {})));
+}(this, (function (exports) { 'use strict';
+
+/* tslint:disable no-http-string */
+var NAMESPACE_W3 = 'http://www.w3.org/';
+/* tslint:enable no-http-string */
+var NAMESPACE_SVG = NAMESPACE_W3 + "2000/svg";
+var NAMESPACE_XLINK = NAMESPACE_W3 + "1999/xlink";
+var emptyArray = [];
+var extend = function (base, overrides) {
+ var result = {};
+ Object.keys(base).forEach(function (key) {
+ result[key] = base[key];
+ });
+ if (overrides) {
+ Object.keys(overrides).forEach(function (key) {
+ result[key] = overrides[key];
+ });
+ }
+ return result;
+};
+var same = function (vnode1, vnode2) {
+ if (vnode1.vnodeSelector !== vnode2.vnodeSelector) {
+ return false;
+ }
+ if (vnode1.properties && vnode2.properties) {
+ if (vnode1.properties.key !== vnode2.properties.key) {
+ return false;
+ }
+ return vnode1.properties.bind === vnode2.properties.bind;
+ }
+ return !vnode1.properties && !vnode2.properties;
+};
+var checkStyleValue = function (styleValue) {
+ if (typeof styleValue !== 'string') {
+ throw new Error('Style values must be strings');
+ }
+};
+var findIndexOfChild = function (children, sameAs, start) {
+ if (sameAs.vnodeSelector !== '') {
+ // Never scan for text-nodes
+ for (var i = start; i < children.length; i++) {
+ if (same(children[i], sameAs)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+};
+var checkDistinguishable = function (childNodes, indexToCheck, parentVNode, operation) {
+ var childNode = childNodes[indexToCheck];
+ if (childNode.vnodeSelector === '') {
+ return; // Text nodes need not be distinguishable
+ }
+ var properties = childNode.properties;
+ var key = properties ? (properties.key === undefined ? properties.bind : properties.key) : undefined;
+ if (!key) {
+ for (var i = 0; i < childNodes.length; i++) {
+ if (i !== indexToCheck) {
+ var node = childNodes[i];
+ if (same(node, childNode)) {
+ throw new Error(parentVNode.vnodeSelector + " had a " + childNode.vnodeSelector + " child " + (operation === 'added' ? operation : 'removed') + ", but there is now more than one. You must add unique key properties to make them distinguishable.");
+ }
+ }
+ }
+ }
+};
+var nodeAdded = function (vNode) {
+ if (vNode.properties) {
+ var enterAnimation = vNode.properties.enterAnimation;
+ if (enterAnimation) {
+ enterAnimation(vNode.domNode, vNode.properties);
+ }
+ }
+};
+var removedNodes = [];
+var requestedIdleCallback = false;
+var visitRemovedNode = function (node) {
+ (node.children || []).forEach(visitRemovedNode);
+ if (node.properties && node.properties.afterRemoved) {
+ node.properties.afterRemoved.apply(node.properties.bind || node.properties, [node.domNode]);
+ }
+};
+var processPendingNodeRemovals = function () {
+ requestedIdleCallback = false;
+ removedNodes.forEach(visitRemovedNode);
+ removedNodes.length = 0;
+};
+var scheduleNodeRemoval = function (vNode) {
+ removedNodes.push(vNode);
+ if (!requestedIdleCallback) {
+ requestedIdleCallback = true;
+ if (typeof window !== 'undefined' && 'requestIdleCallback' in window) {
+ window.requestIdleCallback(processPendingNodeRemovals, { timeout: 16 });
+ }
+ else {
+ setTimeout(processPendingNodeRemovals, 16);
+ }
+ }
+};
+var nodeToRemove = function (vNode) {
+ var domNode = vNode.domNode;
+ if (vNode.properties) {
+ var exitAnimation = vNode.properties.exitAnimation;
+ if (exitAnimation) {
+ domNode.style.pointerEvents = 'none';
+ var removeDomNode = function () {
+ if (domNode.parentNode) {
+ domNode.parentNode.removeChild(domNode);
+ scheduleNodeRemoval(vNode);
+ }
+ };
+ exitAnimation(domNode, removeDomNode, vNode.properties);
+ return;
+ }
+ }
+ if (domNode.parentNode) {
+ domNode.parentNode.removeChild(domNode);
+ scheduleNodeRemoval(vNode);
+ }
+};
+var setProperties = function (domNode, properties, projectionOptions) {
+ if (!properties) {
+ return;
+ }
+ var eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;
+ var propNames = Object.keys(properties);
+ var propCount = propNames.length;
+ var _loop_1 = function (i) {
+ var propName = propNames[i];
+ var propValue = properties[propName];
+ if (propName === 'className') {
+ throw new Error('Property "className" is not supported, use "class".');
+ }
+ else if (propName === 'class') {
+ propValue.split(/\s+/).forEach(function (token) { return domNode.classList.add(token); });
+ }
+ else if (propName === 'classes') {
+ // object with string keys and boolean values
+ var classNames = Object.keys(propValue);
+ var classNameCount = classNames.length;
+ for (var j = 0; j < classNameCount; j++) {
+ var className = classNames[j];
+ if (propValue[className]) {
+ domNode.classList.add(className);
+ }
+ }
+ }
+ else if (propName === 'styles') {
+ // object with string keys and string (!) values
+ var styleNames = Object.keys(propValue);
+ var styleCount = styleNames.length;
+ for (var j = 0; j < styleCount; j++) {
+ var styleName = styleNames[j];
+ var styleValue = propValue[styleName];
+ if (styleValue) {
+ checkStyleValue(styleValue);
+ projectionOptions.styleApplyer(domNode, styleName, styleValue);
+ }
+ }
+ }
+ else if (propName !== 'key' && propValue !== null && propValue !== undefined) {
+ var type = typeof propValue;
+ if (type === 'function') {
+ if (propName.lastIndexOf('on', 0) === 0) {
+ if (eventHandlerInterceptor) {
+ propValue = eventHandlerInterceptor(propName, propValue, domNode, properties); // intercept eventhandlers
+ }
+ if (propName === 'oninput') {
+ /* tslint:disable no-this-keyword no-invalid-this only-arrow-functions no-void-expression */
+ (function () {
+ // record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput
+ var oldPropValue = propValue;
+ propValue = function (evt) {
+ oldPropValue.apply(this, [evt]);
+ evt.target['oninput-value'] = evt.target.value; // may be HTMLTextAreaElement as well
+ };
+ }());
+ /* tslint:enable */
+ }
+ domNode[propName] = propValue;
+ }
+ }
+ else if (type === 'string' && propName !== 'value' && propName !== 'innerHTML') {
+ if (projectionOptions.namespace === NAMESPACE_SVG && propName === 'href') {
+ domNode.setAttributeNS(NAMESPACE_XLINK, propName, propValue);
+ }
+ else {
+ domNode.setAttribute(propName, propValue);
+ }
+ }
+ else {
+ domNode[propName] = propValue;
+ }
+ }
+ };
+ for (var i = 0; i < propCount; i++) {
+ _loop_1(i);
+ }
+};
+var addChildren = function (domNode, children, projectionOptions) {
+ if (!children) {
+ return;
+ }
+ for (var _i = 0, children_1 = children; _i < children_1.length; _i++) {
+ var child = children_1[_i];
+ createDom(child, domNode, undefined, projectionOptions);
+ }
+};
+var initPropertiesAndChildren = function (domNode, vnode, projectionOptions) {
+ addChildren(domNode, vnode.children, projectionOptions); // children before properties, needed for value property of