Speedometer: Add a Preact.js TodoMVC implementation
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 May 2017 02:45:36 +0000 (02:45 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 12 May 2017 02:45:36 +0000 (02:45 +0000)
https://bugs.webkit.org/show_bug.cgi?id=171323

Patch by Addy Osmani <addyosmani@gmail.com> on 2017-05-11
Reviewed by Ryosuke Niwa.

Adds Preact.js TodoMVC implementation to Speedometer

* Speedometer/resources/todomvc/architecture-examples/preact/.babelrc: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/README.md: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/dist/app.js: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/dist/index.html: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc-common/base.css: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc-common/base.js: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc.css: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/package.json: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/rollup.config.js: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/src/app/footer.js: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/src/app/index.js: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/src/app/item.js: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/src/app/model.js: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/src/app/util.js: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/src/index.html: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/src/index.js: Added.
* Speedometer/resources/todomvc/architecture-examples/preact/webpack.config.babel.js: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@216715 268f45cc-cd09-0410-ab3c-d52691b4dbfc

18 files changed:
PerformanceTests/ChangeLog
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/.babelrc [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/README.md [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/app.js [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/index.html [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc-common/base.css [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc-common/base.js [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc.css [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/package.json [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/rollup.config.js [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/footer.js [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/index.js [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/item.js [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/model.js [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/util.js [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/index.html [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/index.js [new file with mode: 0644]
PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/webpack.config.babel.js [new file with mode: 0644]

index 112b50c..394d2d8 100644 (file)
@@ -1,5 +1,32 @@
 2017-05-11  Addy Osmani  <addyosmani@gmail.com>
 
+        Speedometer: Add a Preact.js TodoMVC implementation
+        https://bugs.webkit.org/show_bug.cgi?id=171323
+
+        Reviewed by Ryosuke Niwa.
+
+        Adds Preact.js TodoMVC implementation to Speedometer
+
+        * Speedometer/resources/todomvc/architecture-examples/preact/.babelrc: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/README.md: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/dist/app.js: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/dist/index.html: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc-common/base.css: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc-common/base.js: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc.css: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/package.json: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/rollup.config.js: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/src/app/footer.js: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/src/app/index.js: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/src/app/item.js: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/src/app/model.js: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/src/app/util.js: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/src/index.html: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/src/index.js: Added.
+        * Speedometer/resources/todomvc/architecture-examples/preact/webpack.config.babel.js: Added.
+
+2017-05-11  Addy Osmani  <addyosmani@gmail.com>
+
         Speedometer: Update the jQuery implementation to a more recent library version
         https://bugs.webkit.org/show_bug.cgi?id=171308
 
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/.babelrc b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/.babelrc
new file mode 100644 (file)
index 0000000..ab72ed1
--- /dev/null
@@ -0,0 +1,9 @@
+{
+  "presets": [
+    "es2015-loose",
+    "stage-0"
+  ],
+  "plugins": [
+    ["transform-react-jsx", { "pragma":"h" }]
+  ]
+}
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/README.md b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/README.md
new file mode 100644 (file)
index 0000000..50546f1
--- /dev/null
@@ -0,0 +1,19 @@
+# Preact TodoMVC
+
+[**Demo**](https://preact-todomvc.surge.sh)
+
+TodoMVC done in [Preact]. The app is 6kb of minified + gzipped JavaScript.
+
+This is an ES6 + Preact port of the [ES5 + React TodoMVC example](https://github.com/tastejs/todomvc/tree/gh-pages/examples/react).
+
+[preact]: https://github.com/developit/preact
+
+## Building
+
+Run `npm install` followed by `npm run build` to build the project.
+
+## Speedometer instructions
+
+The compiled version of the application can be found in the `build` directory.
+This is what the benchmark runner uses when running the Preact TodoMVC as part
+of a benchmark run.
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/app.js b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/app.js
new file mode 100644 (file)
index 0000000..8baed36
--- /dev/null
@@ -0,0 +1,2 @@
+!function(){"use strict";function e(e,t,n){this.nodeName=e,this.attributes=t,this.children=n,this.key=t&&t.key}function t(t,n){var o=[],r=void 0,i=void 0,l=void 0,a=void 0;for(a=arguments.length;a-- >2;)Y.push(arguments[a]);for(n&&n.children&&(Y.length||Y.push(n.children),delete n.children);Y.length;)if((i=Y.pop())instanceof Array)for(a=i.length;a--;)Y.push(i[a]);else null!=i&&i!==!1&&("number"!=typeof i&&i!==!0||(i+=""),l="string"==typeof i,l&&r?o[o.length-1]+=i:(o.push(i),r=l));var s=new e(t,n||void 0,o);return X.vnode&&X.vnode(s),s}function n(e,t){if(t)for(var n in t)e[n]=t[n];return e}function o(e){return n({},e)}function r(e,t){for(var n=t.split("."),o=0;o<n.length&&e;o++)e=e[n[o]];return e}function i(e){return"function"==typeof e}function l(e){return"string"==typeof e}function a(e){var t="";for(var n in e)e[n]&&(t&&(t+=" "),t+=n);return t}function s(e,t,n){var o=t.split(".");return function(t){for(var i=t&&t.target||this,a={},s=a,c=l(n)?r(t,n):i.nodeName?i.type.match(/^che|rad/)?i.checked:i.value:t,u=0;u<o.length-1;u++)s=s[o[u]]||(s[o[u]]=!u&&e.state[o[u]]||{});s[o[u]]=c,e.setState(a)}}function c(e){!e._dirty&&(e._dirty=!0)&&1==ue.push(e)&&(X.debounceRendering||te)(u)}function u(){var e=void 0,t=ue;for(ue=[];e=t.pop();)e._dirty&&j(e)}function f(e){var t=e&&e.nodeName;return t&&i(t)&&!(t.prototype&&t.prototype.render)}function d(e,t){return e.nodeName(m(e),t||le)}function p(e,t){return l(t)?e instanceof Text:l(t.nodeName)?!e._componentConstructor&&h(e,t.nodeName):i(t.nodeName)?!e._componentConstructor||e._componentConstructor===t.nodeName||f(t):void 0}function h(e,t){return e.normalizedNodeName===t||$(e.nodeName)===$(t)}function m(e){var t=o(e.attributes);t.children=e.children;var n=e.nodeName.defaultProps;if(n)for(var r in n)void 0===t[r]&&(t[r]=n[r]);return t}function v(e){var t=e.parentNode;t&&t.removeChild(e)}function y(e,t,n,o,r){if("className"===t&&(t="class"),"class"===t&&o&&"object"===(void 0===o?"undefined":fe(o))&&(o=a(o)),"key"===t);else if("class"!==t||r)if("style"===t){if((!o||l(o)||l(n))&&(e.style.cssText=o||""),o&&"object"===(void 0===o?"undefined":fe(o))){if(!l(n))for(var s in n)s in o||(e.style[s]="");for(var c in o)e.style[c]="number"!=typeof o[c]||se[c]?o[c]:o[c]+"px"}}else if("dangerouslySetInnerHTML"===t)e.innerHTML=o&&o.__html||"";else if("o"==t[0]&&"n"==t[1]){var u=e._listeners||(e._listeners={});t=$(t.substring(2)),o?u[t]||e.addEventListener(t,g,!!ce[t]):u[t]&&e.removeEventListener(t,g,!!ce[t]),u[t]=o}else if("list"!==t&&"type"!==t&&!r&&t in e)b(e,t,null==o?"":o),null!=o&&o!==!1||e.removeAttribute(t);else{var f=r&&t.match(/^xlink\:?(.+)/);null==o||o===!1?f?e.removeAttributeNS("http://www.w3.org/1999/xlink",$(f[1])):e.removeAttribute(t):"object"===(void 0===o?"undefined":fe(o))||i(o)||(f?e.setAttributeNS("http://www.w3.org/1999/xlink",$(f[1]),o):e.setAttribute(t,o))}else e.className=o||""}function b(e,t,n){try{e[t]=n}catch(e){}}function g(e){return this._listeners[e.type](X.event&&X.event(e)||e)}function _(e){if(v(e),e instanceof Element){e._component=e._componentConstructor=null;var t=e.normalizedNodeName||$(e.nodeName);(de[t]||(de[t]=[])).push(e)}}function w(e,t){var n=$(e),o=de[n]&&de[n].pop()||(t?document.createElementNS("http://www.w3.org/2000/svg",e):document.createElement(e));return o.normalizedNodeName=n,o}function C(){for(var e=void 0;e=pe.pop();)X.afterMount&&X.afterMount(e),e.componentDidMount&&e.componentDidMount()}function x(e,t,n,o,r,i){he++||(me=r instanceof SVGElement,ve=e&&!(ae in e));var l=S(e,t,n,o);return r&&l.parentNode!==r&&r.appendChild(l),--he||(ve=!1,i||C()),l}function S(e,t,n,o){for(var r=t&&t.attributes;f(t);)t=d(t,n);if(null==t&&(t=""),l(t))return e&&e instanceof Text?e.nodeValue!=t&&(e.nodeValue=t):(e&&N(e),e=document.createTextNode(t)),e[ae]=!0,e;if(i(t.nodeName))return P(e,t,n,o);var a=e,s=t.nodeName+"",c=me,u=t.children;if(me="svg"===s||"foreignObject"!==s&&me,e){if(!h(e,s)){for(a=w(s,me);e.firstChild;)a.appendChild(e.firstChild);e.parentNode&&e.parentNode.replaceChild(a,e),N(e)}}else a=w(s,me);var p=a.firstChild,m=a[ae];if(!m){a[ae]=m={};for(var v=a.attributes,y=v.length;y--;)m[v[y].name]=v[y].value}return T(a,t.attributes,m),!ve&&u&&1===u.length&&"string"==typeof u[0]&&p&&p instanceof Text&&!p.nextSibling?p.nodeValue!=u[0]&&(p.nodeValue=u[0]):(u&&u.length||p)&&k(a,u,n,o),r&&"function"==typeof r.ref&&(m.ref=r.ref)(a),me=c,a}function k(e,t,n,o){var r=e.childNodes,i=[],l={},a=0,s=0,c=r.length,u=0,f=t&&t.length,d=void 0,h=void 0,m=void 0,y=void 0;if(c)for(var b=0;b<c;b++){var g=r[b],_=g[ae],w=f?(h=g._component)?h.__key:_?_.key:null:null;null!=w?(a++,l[w]=g):(ve||_)&&(i[u++]=g)}if(f)for(var C=0;C<f;C++){m=t[C],y=null;var x=m.key;if(null!=x)a&&x in l&&(y=l[x],l[x]=void 0,a--);else if(!y&&s<u)for(d=s;d<u;d++)if(h=i[d],h&&p(h,m)){y=h,i[d]=void 0,d===u-1&&u--,d===s&&s++;break}y=S(y,m,n,o),y&&y!==e&&(C>=c?e.appendChild(y):y!==r[C]&&(y===r[C+1]&&v(r[C]),e.insertBefore(y,r[C]||null)))}if(a)for(var k in l)l[k]&&N(l[k]);for(;s<=u;)y=i[u--],y&&N(y)}function N(e,t){var n=e._component;if(n)U(n,!t);else{e[ae]&&e[ae].ref&&e[ae].ref(null),t||_(e);for(var o=void 0;o=e.lastChild;)N(o,t)}}function T(e,t,n){for(var o in n)t&&o in t||null==n[o]||y(e,o,n[o],n[o]=void 0,me);if(t)for(var r in t)"children"===r||"innerHTML"===r||r in n&&t[r]===("value"===r||"checked"===r?e[r]:n[r])||y(e,r,n[r],n[r]=t[r],me)}function E(e){var t=e.constructor.name,n=ye[t];n?n.push(e):ye[t]=[e]}function O(e,t,n){var o=new e(t,n),r=ye[e.name];if(A.call(o,t,n),r)for(var i=r.length;i--;)if(r[i].constructor===e){o.nextBase=r[i].nextBase,r.splice(i,1);break}return o}function D(e,t,n,o,r){e._disable||(e._disable=!0,(e.__ref=t.ref)&&delete t.ref,(e.__key=t.key)&&delete t.key,!e.base||r?e.componentWillMount&&e.componentWillMount():e.componentWillReceiveProps&&e.componentWillReceiveProps(t,o),o&&o!==e.context&&(e.prevContext||(e.prevContext=e.context),e.context=o),e.prevProps||(e.prevProps=e.props),e.props=t,e._disable=!1,n!==ne&&(n!==oe&&X.syncComponentUpdates===!1&&e.base?c(e):j(e,oe,r)),e.__ref&&e.__ref(e))}function j(e,t,r,l){if(!e._disable){var a=void 0,s=void 0,c=e.props,u=e.state,p=e.context,h=e.prevProps||c,v=e.prevState||u,y=e.prevContext||p,b=e.base,g=e.nextBase,_=b||g,w=e._component,S=void 0,k=void 0;if(b&&(e.props=h,e.state=v,e.context=y,t!==re&&e.shouldComponentUpdate&&e.shouldComponentUpdate(c,u,p)===!1?a=!0:e.componentWillUpdate&&e.componentWillUpdate(c,u,p),e.props=c,e.state=u,e.context=p),e.prevProps=e.prevState=e.prevContext=e.nextBase=null,e._dirty=!1,!a){for(e.render&&(s=e.render(c,u,p)),e.getChildContext&&(p=n(o(p),e.getChildContext()));f(s);)s=d(s,p);var T=s&&s.nodeName,E=void 0,P=void 0;if(i(T)){var A=m(s);S=w,S&&S.constructor===T&&A.key==S.__key?D(S,A,oe,p):(E=S,S=O(T,A,p),S.nextBase=S.nextBase||g,S._parentComponent=e,e._component=S,D(S,A,ne,p),j(S,oe,r,!0)),P=S.base}else k=_,E=w,E&&(k=e._component=null),(_||t===oe)&&(k&&(k._component=null),P=x(k,s,p,r||!b,_&&_.parentNode,!0));if(_&&P!==_&&S!==w){var B=_.parentNode;B&&P!==B&&(B.replaceChild(P,_),E||(_._component=null,N(_)))}if(E&&U(E,P!==_),e.base=P,P&&!l){for(var M=e,W=e;W=W._parentComponent;)(M=W).base=P;P._component=M,P._componentConstructor=M.constructor}}!b||r?pe.unshift(e):a||(e.componentDidUpdate&&e.componentDidUpdate(h,v,y),X.afterUpdate&&X.afterUpdate(e));var L=e._renderCallbacks,R=void 0;if(L)for(;R=L.pop();)R.call(e);he||l||C()}}function P(e,t,n,o){for(var r=e&&e._component,i=e,l=r&&e._componentConstructor===t.nodeName,a=l,s=m(t);r&&!a&&(r=r._parentComponent);)a=r.constructor===t.nodeName;return r&&a&&(!o||r._component)?(D(r,s,ie,n,o),e=r.base):(r&&!l&&(U(r,!0),e=i=null),r=O(t.nodeName,s,n),e&&!r.nextBase&&(r.nextBase=e,i=null),D(r,s,oe,n,o),e=r.base,i&&e!==i&&(i._component=null,N(i))),e}function U(e,t){X.beforeUnmount&&X.beforeUnmount(e);var n=e.base;e._disable=!0,e.componentWillUnmount&&e.componentWillUnmount(),e.base=null;var o=e._component;if(o)U(o,t);else if(n){n[ae]&&n[ae].ref&&n[ae].ref(null),e.nextBase=n,t&&(v(n),E(e));for(var r=void 0;r=n.lastChild;)N(r,!t)}e.__ref&&e.__ref(null),e.componentDidUnmount&&e.componentDidUnmount()}function A(e,t){this._dirty=!0,this.context=t,this.props=e,this.state||(this.state={})}function B(e,t,n){return x(n,e,{},!1,t)}function M(){for(var e="",t=0;t<32;t++){var n=16*Math.random()|0;8!==t&&12!==t&&16!==t&&20!==t||(e+="-"),e+=(12===t?4:16===t?3&n|8:n).toString(16)}return e}function W(e,t){return 1===e?t:t+"s"}function L(){return[]}function R(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function z(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function K(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function V(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function H(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function I(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function F(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function G(e){if(null==e)throw new TypeError("Cannot destructure undefined")}function q(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function J(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function Q(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var X={},Y=[],Z={},$=function(e){return Z[e]||(Z[e]=e.toLowerCase())},ee="undefined"!=typeof Promise&&Promise.resolve(),te=ee?function(e){ee.then(e)}:setTimeout,ne=0,oe=1,re=2,ie=3,le={},ae="undefined"!=typeof Symbol?Symbol.for("preactattr"):"__preactattr_",se={boxFlex:1,boxFlexGroup:1,columnCount:1,fillOpacity:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,fontWeight:1,lineClamp:1,lineHeight:1,opacity:1,order:1,orphans:1,strokeOpacity:1,widows:1,zIndex:1,zoom:1},ce={blur:1,error:1,focus:1,load:1,resize:1,scroll:1},ue=[],fe="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},de={},pe=[],he=0,me=!1,ve=!1,ye={};n(A.prototype,{linkState:function(e,t){var n=this._linkedStates||(this._linkedStates={});return n[e+t]||(n[e+t]=s(this,e,t))},setState:function(e,t){var r=this.state;this.prevState||(this.prevState=o(r)),n(r,i(e)?e(r,this.props):e),t&&(this._renderCallbacks=this._renderCallbacks||[]).push(t),c(this)},forceUpdate:function(){j(this,re)},render:function(){}});var be=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(e[o]=n[o])}return e},ge=function(){function e(t,n){R(this,e),this.key=t,this.todos=L(t)||[],this.onChanges=[n]}return e.prototype.inform=function(){L(this.key,this.todos),this.onChanges.forEach(function(e){return e()})},e.prototype.addTodo=function(e){this.todos=this.todos.concat({id:M(),title:e,completed:!1}),this.inform()},e.prototype.toggleAll=function(e){this.todos=this.todos.map(function(t){return be({},t,{completed:e})}),this.inform()},e.prototype.toggle=function(e){this.todos=this.todos.map(function(t){return t!==e?t:be({},t,{completed:!t.completed})}),this.inform()},e.prototype.destroy=function(e){this.todos=this.todos.filter(function(t){return t!==e}),this.inform()},e.prototype.save=function(e,t){this.todos=this.todos.map(function(n){return n!==e?n:be({},n,{title:t})}),this.inform()},e.prototype.clearCompleted=function(){this.todos=this.todos.filter(function(e){return!e.completed}),this.inform()},e}(),_e=function(e){function n(){return z(this,n),K(this,e.apply(this,arguments))}return V(n,e),n.prototype.render=function(e){var n=e.nowShowing,o=e.count,r=e.completedCount,i=e.onClearCompleted;return t("footer",{class:"footer"},t("span",{class:"todo-count"},t("strong",null,o)," ",W(o,"item")," left"),t("ul",{class:"filters"},t("li",null,t("a",{href:"#/",class:"all"==n&&"selected"},"All"))," ",t("li",null,t("a",{href:"#/active",class:"active"==n&&"selected"},"Active"))," ",t("li",null,t("a",{href:"#/completed",class:"completed"==n&&"selected"},"Completed"))),r>0&&t("button",{class:"clear-completed",onClick:i},"Clear completed"))},n}(A),we=27,Ce=13,xe=function(e){function n(){var t,o,r;H(this,n);for(var i=arguments.length,l=Array(i),a=0;a<i;a++)l[a]=arguments[a];return t=o=I(this,e.call.apply(e,[this].concat(l))),o.handleSubmit=function(){var e=o.props,t=e.onSave,n=e.onDestroy,r=e.todo,i=o.state.editText.trim();i?(t(r,i),o.setState({editText:i})):n(r)},o.handleEdit=function(){var e=o.props,t=e.onEdit,n=e.todo;t(n),o.setState({editText:n.title})},o.toggle=function(e){var t=o.props;(0,t.onToggle)(t.todo),e.preventDefault()},o.handleKeyDown=function(e){if(e.which===we){var t=o.props.todo;o.setState({editText:t.title}),o.props.onCancel(t)}else e.which===Ce&&o.handleSubmit()},o.handleDestroy=function(){o.props.onDestroy(o.props.todo)},r=t,I(o,r)}return F(n,e),n.prototype.componentDidUpdate=function(){var e=this.base&&this.base.querySelector(".edit");e&&e.focus()},n.prototype.render=function(e,n){var o=e.todo,r=o.title,i=o.completed,l=e.editing,a=n.editText;return t("li",{class:{completed:i,editing:l}},t("div",{class:"view"},t("input",{class:"toggle",type:"checkbox",checked:i,onChange:this.toggle}),t("label",{onDblClick:this.handleEdit},r),t("button",{class:"destroy",onClick:this.handleDestroy})),l&&t("input",{class:"edit",value:a,onBlur:this.handleSubmit,onInput:this.linkState("editText"),onKeyDown:this.handleKeyDown}))},n}(A),Se={all:function(){return!0},active:function(e){return!e.completed},completed:function(e){return e.completed}};B(t(function(e){function n(){q(this,n);var t=J(this,e.call(this));return t.handleNewTodoKeyDown=function(e){if(13===e.keyCode){e.preventDefault();var n=e.target.value.trim();n&&(t.model.addTodo(n),t.setState({newTodo:""}))}},t.toggleAll=function(e){t.model.toggleAll(e.target.checked)},t.toggle=function(e){t.model.toggle(e)},t.destroy=function(e){t.model.destroy(e)},t.edit=function(e){t.setState({editing:e.id})},t.save=function(e,n){t.model.save(e,n),t.setState({editing:null})},t.cancel=function(){t.setState({editing:null})},t.clearCompleted=function(){t.model.clearCompleted()},t.model=new ge("preact-todos",function(){return t.setState({})}),addEventListener("hashchange",t.handleRoute.bind(t)),t.handleRoute(),t}return Q(n,e),n.prototype.handleRoute=function(){var e=((location.hash||"")+"").split("/").pop();Se[e]||(e="all"),this.setState({nowShowing:e})},n.prototype.render=function(e,n){var o=this,r=n.nowShowing,i=void 0===r?ALL_TODOS:r,l=n.newTodo,a=n.editing;G(e);var s=this.model.todos,c=s.filter(Se[i]),u=s.reduce(function(e,t){return e+(t.completed?0:1)},0),f=s.length-u;return t("div",null,t("header",{class:"header"},t("h1",null,"todos"),t("input",{class:"new-todo",placeholder:"What needs to be done?",value:l,onKeyDown:this.handleNewTodoKeyDown,onInput:this.linkState("newTodo"),autoFocus:!0})),s.length?t("section",{class:"main"},t("input",{class:"toggle-all",type:"checkbox",onChange:this.toggleAll,checked:0===u}),t("ul",{class:"todo-list"},c.map(function(e){return t(xe,{todo:e,onToggle:o.toggle,onDestroy:o.destroy,onEdit:o.edit,editing:a===e.id,onSave:o.save,onCancel:o.cancel})}))):null,u||f?t(_e,{count:u,completedCount:f,nowShowing:i,onClearCompleted:this.clearCompleted}):null)},n}(A),null),document.querySelector(".todoapp"))}();
+//# sourceMappingURL=app.js.map
\ No newline at end of file
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/index.html b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/index.html
new file mode 100644 (file)
index 0000000..78e5dff
--- /dev/null
@@ -0,0 +1,18 @@
+<!doctype html>
+<html lang="en" data-framework="preact">
+    <head>
+        <meta charset="utf-8">
+        <title>Preact • TodoMVC</title>
+        <link rel="stylesheet" href="todomvc-common/base.css">
+        <link rel="stylesheet" href="todomvc.css">
+    </head>
+    <body>
+        <section class="todoapp"></section>
+        <footer class="info">
+            <p>Double-click to edit a todo</p>
+            <p>Created by <a href="http://github.com/petehunt/">petehunt</a></p>
+            <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
+        </footer>
+        <script src="app.js"></script>
+    </body>
+</html>
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc-common/base.css b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc-common/base.css
new file mode 100644 (file)
index 0000000..4d25d3c
--- /dev/null
@@ -0,0 +1,141 @@
+hr {
+    margin: 20px 0;
+    border: 0;
+    border-top: 1px dashed #c5c5c5;
+    border-bottom: 1px dashed #f7f7f7;
+}
+
+.learn a {
+    font-weight: normal;
+    text-decoration: none;
+    color: #b83f45;
+}
+
+.learn a:hover {
+    text-decoration: underline;
+    color: #787e7e;
+}
+
+.learn h3,
+.learn h4,
+.learn h5 {
+    margin: 10px 0;
+    font-weight: 500;
+    line-height: 1.2;
+    color: #000;
+}
+
+.learn h3 {
+    font-size: 24px;
+}
+
+.learn h4 {
+    font-size: 18px;
+}
+
+.learn h5 {
+    margin-bottom: 0;
+    font-size: 14px;
+}
+
+.learn ul {
+    padding: 0;
+    margin: 0 0 30px 25px;
+}
+
+.learn li {
+    line-height: 20px;
+}
+
+.learn p {
+    font-size: 15px;
+    font-weight: 300;
+    line-height: 1.3;
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+#issue-count {
+    display: none;
+}
+
+.quote {
+    border: none;
+    margin: 20px 0 60px 0;
+}
+
+.quote p {
+    font-style: italic;
+}
+
+.quote p:before {
+    content: '“';
+    font-size: 50px;
+    opacity: .15;
+    position: absolute;
+    top: -20px;
+    left: 3px;
+}
+
+.quote p:after {
+    content: '”';
+    font-size: 50px;
+    opacity: .15;
+    position: absolute;
+    bottom: -42px;
+    right: 3px;
+}
+
+.quote footer {
+    position: absolute;
+    bottom: -40px;
+    right: 0;
+}
+
+.quote footer img {
+    border-radius: 3px;
+}
+
+.quote footer a {
+    margin-left: 5px;
+    vertical-align: middle;
+}
+
+.speech-bubble {
+    position: relative;
+    padding: 10px;
+    background: rgba(0, 0, 0, .04);
+    border-radius: 5px;
+}
+
+.speech-bubble:after {
+    content: '';
+    position: absolute;
+    top: 100%;
+    right: 30px;
+    border: 13px solid transparent;
+    border-top-color: rgba(0, 0, 0, .04);
+}
+
+.learn-bar > .learn {
+    position: absolute;
+    width: 272px;
+    top: 8px;
+    left: -300px;
+    padding: 10px;
+    border-radius: 5px;
+    background-color: rgba(255, 255, 255, .6);
+    transition-property: left;
+    transition-duration: 500ms;
+}
+
+@media (min-width: 899px) {
+    .learn-bar {
+        width: auto;
+        padding-left: 300px;
+    }
+
+    .learn-bar > .learn {
+        left: 8px;
+    }
+}
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc-common/base.js b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc-common/base.js
new file mode 100644 (file)
index 0000000..81e006b
--- /dev/null
@@ -0,0 +1,249 @@
+/* global _ */
+(function () {
+    'use strict';
+
+    /* jshint ignore:start */
+    // Underscore's Template Module
+    // Courtesy of underscorejs.org
+    var _ = (function (_) {
+        _.defaults = function (object) {
+            if (!object) {
+                return object;
+            }
+            for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {
+                var iterable = arguments[argsIndex];
+                if (iterable) {
+                    for (var key in iterable) {
+                        if (object[key] == null) {
+                            object[key] = iterable[key];
+                        }
+                    }
+                }
+            }
+            return object;
+        }
+
+        // By default, Underscore uses ERB-style template delimiters, change the
+        // following template settings to use alternative delimiters.
+        _.templateSettings = {
+            evaluate    : /<%([\s\S]+?)%>/g,
+            interpolate : /<%=([\s\S]+?)%>/g,
+            escape      : /<%-([\s\S]+?)%>/g
+        };
+
+        // When customizing `templateSettings`, if you don't want to define an
+        // interpolation, evaluation or escaping regex, we need one that is
+        // guaranteed not to match.
+        var noMatch = /(.)^/;
+
+        // Certain characters need to be escaped so that they can be put into a
+        // string literal.
+        var escapes = {
+            "'":      "'",
+            '\\':     '\\',
+            '\r':     'r',
+            '\n':     'n',
+            '\t':     't',
+            '\u2028': 'u2028',
+            '\u2029': 'u2029'
+        };
+
+        var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
+
+        // JavaScript micro-templating, similar to John Resig's implementation.
+        // Underscore templating handles arbitrary delimiters, preserves whitespace,
+        // and correctly escapes quotes within interpolated code.
+        _.template = function(text, data, settings) {
+            var render;
+            settings = _.defaults({}, settings, _.templateSettings);
+
+            // Combine delimiters into one regular expression via alternation.
+            var matcher = new RegExp([
+                (settings.escape || noMatch).source,
+                (settings.interpolate || noMatch).source,
+                (settings.evaluate || noMatch).source
+            ].join('|') + '|$', 'g');
+
+            // Compile the template source, escaping string literals appropriately.
+            var index = 0;
+            var source = "__p+='";
+            text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+                source += text.slice(index, offset)
+                    .replace(escaper, function(match) { return '\\' + escapes[match]; });
+
+                if (escape) {
+                    source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+                }
+                if (interpolate) {
+                    source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+                }
+                if (evaluate) {
+                    source += "';\n" + evaluate + "\n__p+='";
+                }
+                index = offset + match.length;
+                return match;
+            });
+            source += "';\n";
+
+            // If a variable is not specified, place data values in local scope.
+            if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+            source = "var __t,__p='',__j=Array.prototype.join," +
+                "print=function(){__p+=__j.call(arguments,'');};\n" +
+                source + "return __p;\n";
+
+            try {
+                render = new Function(settings.variable || 'obj', '_', source);
+            } catch (e) {
+                e.source = source;
+                throw e;
+            }
+
+            if (data) return render(data, _);
+            var template = function(data) {
+                return render.call(this, data, _);
+            };
+
+            // Provide the compiled function source as a convenience for precompilation.
+            template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
+
+            return template;
+        };
+
+        return _;
+    })({});
+
+    if (location.hostname === 'todomvc.com') {
+        (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+        (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+        m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+        })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+        ga('create', 'UA-31081062-1', 'auto');
+        ga('send', 'pageview');
+    }
+    /* jshint ignore:end */
+
+    function redirect() {
+        if (location.hostname === 'tastejs.github.io') {
+            location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com');
+        }
+    }
+
+    function findRoot() {
+        var base = location.href.indexOf('examples/');
+        return location.href.substr(0, base);
+    }
+
+    function getFile(file, callback) {
+        if (!location.host) {
+            return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.');
+        }
+
+        var xhr = new XMLHttpRequest();
+
+        xhr.open('GET', findRoot() + file, true);
+        xhr.send();
+
+        xhr.onload = function () {
+            if (xhr.status === 200 && callback) {
+                callback(xhr.responseText);
+            }
+        };
+    }
+
+    function Learn(learnJSON, config) {
+        if (!(this instanceof Learn)) {
+            return new Learn(learnJSON, config);
+        }
+
+        var template, framework;
+
+        if (typeof learnJSON !== 'object') {
+            try {
+                learnJSON = JSON.parse(learnJSON);
+            } catch (e) {
+                return;
+            }
+        }
+
+        if (config) {
+            template = config.template;
+            framework = config.framework;
+        }
+
+        if (!template && learnJSON.templates) {
+            template = learnJSON.templates.todomvc;
+        }
+
+        if (!framework && document.querySelector('[data-framework]')) {
+            framework = document.querySelector('[data-framework]').dataset.framework;
+        }
+
+        this.template = template;
+
+        if (learnJSON.backend) {
+            this.frameworkJSON = learnJSON.backend;
+            this.frameworkJSON.issueLabel = framework;
+            this.append({
+                backend: true
+            });
+        } else if (learnJSON[framework]) {
+            this.frameworkJSON = learnJSON[framework];
+            this.frameworkJSON.issueLabel = framework;
+            this.append();
+        }
+
+        this.fetchIssueCount();
+    }
+
+    Learn.prototype.append = function (opts) {
+        var aside = document.createElement('aside');
+        aside.innerHTML = _.template(this.template, this.frameworkJSON);
+        aside.className = 'learn';
+
+        if (opts && opts.backend) {
+            // Remove demo link
+            var sourceLinks = aside.querySelector('.source-links');
+            var heading = sourceLinks.firstElementChild;
+            var sourceLink = sourceLinks.lastElementChild;
+            // Correct link path
+            var href = sourceLink.getAttribute('href');
+            sourceLink.setAttribute('href', href.substr(href.lastIndexOf('http')));
+            sourceLinks.innerHTML = heading.outerHTML + sourceLink.outerHTML;
+        } else {
+            // Localize demo links
+            var demoLinks = aside.querySelectorAll('.demo-link');
+            Array.prototype.forEach.call(demoLinks, function (demoLink) {
+                if (demoLink.getAttribute('href').substr(0, 4) !== 'http') {
+                    demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href'));
+                }
+            });
+        }
+
+        document.body.className = (document.body.className + ' learn-bar').trim();
+        document.body.insertAdjacentHTML('afterBegin', aside.outerHTML);
+    };
+
+    Learn.prototype.fetchIssueCount = function () {
+        var issueLink = document.getElementById('issue-count-link');
+        if (issueLink) {
+            var url = issueLink.href.replace('https://github.com', 'https://api.github.com/repos');
+            var xhr = new XMLHttpRequest();
+            xhr.open('GET', url, true);
+            xhr.onload = function (e) {
+                var parsedResponse = JSON.parse(e.target.responseText);
+                if (parsedResponse instanceof Array) {
+                    var count = parsedResponse.length;
+                    if (count !== 0) {
+                        issueLink.innerHTML = 'This app has ' + count + ' open issues';
+                        document.getElementById('issue-count').style.display = 'inline';
+                    }
+                }
+            };
+            xhr.send();
+        }
+    };
+
+    redirect();
+    getFile('learn.json', Learn);
+})();
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc.css b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/dist/todomvc.css
new file mode 100644 (file)
index 0000000..10e3d2f
--- /dev/null
@@ -0,0 +1,370 @@
+html,
+body {
+    margin: 0;
+    padding: 0;
+}
+
+button {
+    margin: 0;
+    padding: 0;
+    border: 0;
+    background: none;
+    font-size: 100%;
+    vertical-align: baseline;
+    font-family: inherit;
+    font-weight: inherit;
+    color: inherit;
+    -webkit-appearance: none;
+    appearance: none;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+    font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
+    line-height: 1.4em;
+    background: #f5f5f5;
+    color: #4d4d4d;
+    min-width: 230px;
+    max-width: 550px;
+    margin: 0 auto;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    font-weight: 300;
+}
+
+:focus {
+    outline: 0;
+}
+
+.hidden {
+    display: none;
+}
+
+.todoapp {
+    background: #fff;
+    margin: 130px 0 40px 0;
+    position: relative;
+    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
+                0 25px 50px 0 rgba(0, 0, 0, 0.1);
+}
+
+.todoapp input::-webkit-input-placeholder {
+    font-style: italic;
+    font-weight: 300;
+    color: #e6e6e6;
+}
+
+.todoapp input::-moz-placeholder {
+    font-style: italic;
+    font-weight: 300;
+    color: #e6e6e6;
+}
+
+.todoapp input::input-placeholder {
+    font-style: italic;
+    font-weight: 300;
+    color: #e6e6e6;
+}
+
+.todoapp h1 {
+    position: absolute;
+    top: -155px;
+    width: 100%;
+    font-size: 100px;
+    font-weight: 100;
+    text-align: center;
+    color: rgba(175, 47, 47, 0.15);
+    -webkit-text-rendering: optimizeLegibility;
+    -moz-text-rendering: optimizeLegibility;
+    text-rendering: optimizeLegibility;
+}
+
+.new-todo,
+.edit {
+    position: relative;
+    margin: 0;
+    width: 100%;
+    font-size: 24px;
+    font-family: inherit;
+    font-weight: inherit;
+    line-height: 1.4em;
+    border: 0;
+    color: inherit;
+    padding: 6px;
+    border: 1px solid #999;
+    box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
+    box-sizing: border-box;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+}
+
+.new-todo {
+    padding: 16px 16px 16px 60px;
+    border: none;
+    background: rgba(0, 0, 0, 0.003);
+    box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
+}
+
+.main {
+    position: relative;
+    z-index: 2;
+    border-top: 1px solid #e6e6e6;
+}
+
+label[for='toggle-all'] {
+    display: none;
+}
+
+.toggle-all {
+    position: absolute;
+    top: -55px;
+    left: -12px;
+    width: 60px;
+    height: 34px;
+    text-align: center;
+    border: none; /* Mobile Safari */
+}
+
+.toggle-all:before {
+    content: '❯';
+    font-size: 22px;
+    color: #e6e6e6;
+    padding: 10px 27px 10px 27px;
+}
+
+.toggle-all:checked:before {
+    color: #737373;
+}
+
+.todo-list {
+    margin: 0;
+    padding: 0;
+    list-style: none;
+}
+
+.todo-list li {
+    position: relative;
+    font-size: 24px;
+    border-bottom: 1px solid #ededed;
+}
+
+.todo-list li:last-child {
+    border-bottom: none;
+}
+
+.todo-list li.editing {
+    border-bottom: none;
+    padding: 0;
+}
+
+.todo-list li.editing .edit {
+    display: block;
+    width: 506px;
+    padding: 12px 16px;
+    margin: 0 0 0 43px;
+}
+
+.todo-list li.editing .view {
+    display: none;
+}
+
+.todo-list li .toggle {
+    text-align: center;
+    width: 40px;
+    /* auto, since non-WebKit browsers doesn't support input styling */
+    height: auto;
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    margin: auto 0;
+    border: none; /* Mobile Safari */
+    -webkit-appearance: none;
+    appearance: none;
+}
+
+.todo-list li .toggle:after {
+    content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
+}
+
+.todo-list li .toggle:checked:after {
+    content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
+}
+
+.todo-list li label {
+    word-break: break-all;
+    padding: 15px 60px 15px 15px;
+    margin-left: 45px;
+    display: block;
+    line-height: 1.2;
+    transition: color 0.4s;
+}
+
+.todo-list li.completed label {
+    color: #d9d9d9;
+    text-decoration: line-through;
+}
+
+.todo-list li .destroy {
+    display: none;
+    position: absolute;
+    top: 0;
+    right: 10px;
+    bottom: 0;
+    width: 40px;
+    height: 40px;
+    margin: auto 0;
+    font-size: 30px;
+    color: #cc9a9a;
+    margin-bottom: 11px;
+    transition: color 0.2s ease-out;
+}
+
+.todo-list li .destroy:hover {
+    color: #af5b5e;
+}
+
+.todo-list li .destroy:after {
+    content: '×';
+}
+
+.todo-list li:hover .destroy {
+    display: block;
+}
+
+.todo-list li .edit {
+    display: none;
+}
+
+.todo-list li.editing:last-child {
+    margin-bottom: -1px;
+}
+
+.footer {
+    color: #777;
+    padding: 10px 15px;
+    height: 20px;
+    text-align: center;
+    border-top: 1px solid #e6e6e6;
+}
+
+.footer:before {
+    content: '';
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    height: 50px;
+    overflow: hidden;
+    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
+                0 8px 0 -3px #f6f6f6,
+                0 9px 1px -3px rgba(0, 0, 0, 0.2),
+                0 16px 0 -6px #f6f6f6,
+                0 17px 2px -6px rgba(0, 0, 0, 0.2);
+}
+
+.todo-count {
+    float: left;
+    text-align: left;
+}
+
+.todo-count strong {
+    font-weight: 300;
+}
+
+.filters {
+    margin: 0;
+    padding: 0;
+    list-style: none;
+    position: absolute;
+    right: 0;
+    left: 0;
+}
+
+.filters li {
+    display: inline;
+}
+
+.filters li a {
+    color: inherit;
+    margin: 3px;
+    padding: 3px 7px;
+    text-decoration: none;
+    border: 1px solid transparent;
+    border-radius: 3px;
+}
+
+.filters li a:hover {
+    border-color: rgba(175, 47, 47, 0.1);
+}
+
+.filters li a.selected {
+    border-color: rgba(175, 47, 47, 0.2);
+}
+
+.clear-completed,
+html .clear-completed:active {
+    float: right;
+    position: relative;
+    line-height: 20px;
+    text-decoration: none;
+    cursor: pointer;
+}
+
+.clear-completed:hover {
+    text-decoration: underline;
+}
+
+.info {
+    margin: 65px auto 0;
+    color: #bfbfbf;
+    font-size: 10px;
+    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+    text-align: center;
+}
+
+.info p {
+    line-height: 1;
+}
+
+.info a {
+    color: inherit;
+    text-decoration: none;
+    font-weight: 400;
+}
+
+.info a:hover {
+    text-decoration: underline;
+}
+
+/*
+    Hack to remove background from Mobile Safari.
+    Can't use it globally since it destroys checkboxes in Firefox
+*/
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+    .toggle-all,
+    .todo-list li .toggle {
+        background: none;
+    }
+
+    .todo-list li .toggle {
+        height: 40px;
+    }
+
+    .toggle-all {
+        -webkit-transform: rotate(90deg);
+        transform: rotate(90deg);
+        -webkit-appearance: none;
+        appearance: none;
+    }
+}
+
+@media (max-width: 430px) {
+    .footer {
+        height: 50px;
+    }
+
+    .filters {
+        bottom: 10px;
+    }
+}
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/package.json b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/package.json
new file mode 100644 (file)
index 0000000..63eba87
--- /dev/null
@@ -0,0 +1,52 @@
+{
+  "name": "preact-todomvc",
+  "version": "0.2.0",
+  "description": "TodoMVC done in Preact",
+  "main": "index.js",
+  "scripts": {
+    "dev": "webpack-dev-server",
+    "start": "http-server build -p ${PORT:-8080}",
+    "prestart": "npm run build",
+    "build": "npm run -s build:rollup",
+    "build:webpack": "mkdirp build && ncp src/index.html build/index.html && webpack -p",
+    "build:rollup": "rm -rf build && mkdirp build/todomvc-common && ncp src/index.html build/index.html && rollup -c rollup.config.js && cp node_modules/todomvc-common/base.{js,css} build/todomvc-common/ && cp node_modules/todomvc-app-css/index.css build/todomvc.css && npm run postbuild",
+    "postbuild": "uglifyjs build/app.js --pure-funcs classCallCheck Object.defineProperty Object.freeze invariant warning -c unsafe,collapse_vars,evaluate,screw_ie8,loops,keep_fargs=false,pure_getters,unused,dead_code -m -o build/app.js -p relative --in-source-map build/app.js.map --source-map build/app.js.map",
+    "deploy": "gh-pages -d build"
+  },
+  "keywords": [
+    "preact"
+  ],
+  "author": "The TodoMVC authors",
+  "license": "MIT",
+  "devDependencies": {
+    "babel-core": "^6.5.2",
+    "babel-loader": "^6.2.3",
+    "babel-plugin-transform-class-properties": "^6.5.2",
+    "babel-plugin-transform-decorators-legacy": "^1.3.4",
+    "babel-plugin-transform-react-jsx": "^6.5.2",
+    "babel-preset-babili": "0.0.9",
+    "babel-preset-es2015": "^6.5.0",
+    "babel-preset-es2015-loose": "^8.0.0",
+    "babel-preset-stage-0": "^6.5.0",
+    "css-loader": "^0.26.0",
+    "extract-text-webpack-plugin": "^1.0.1",
+    "gh-pages": "^0.12.0",
+    "http-server": "^0.9.0",
+    "mkdirp": "^0.5.1",
+    "ncp": "^2.0.0",
+    "replace-bundle-webpack-plugin": "^1.0.0",
+    "rollup": "^0.36.4",
+    "rollup-plugin-babel": "^2.6.1",
+    "rollup-plugin-commonjs": "^5.0.5",
+    "rollup-plugin-node-resolve": "^2.0.0",
+    "style-loader": "^0.13.0",
+    "webpack": "^1.12.14",
+    "webpack-dev-server": "^1.14.1"
+  },
+  "dependencies": {
+    "preact": "^7.1.0",
+    "preact-router": "^2.3.2",
+    "todomvc-app-css": "^2.0.3",
+    "todomvc-common": "^1.0.2"
+  }
+}
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/rollup.config.js b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/rollup.config.js
new file mode 100644 (file)
index 0000000..48c59aa
--- /dev/null
@@ -0,0 +1,27 @@
+import nodeResolve from 'rollup-plugin-node-resolve';
+import commonjs from 'rollup-plugin-commonjs';
+import babel from 'rollup-plugin-babel';
+
+export default {
+    entry: 'src/index.js',
+    dest: 'build/app.js',
+    format: 'iife',
+    sourceMap: true,
+    external: [],
+    plugins: [
+        babel({
+            babelrc: false,
+            presets: [
+                ['es2015', { loose:true, modules:false }],
+                'stage-0'
+            ],
+            plugins: [
+                ['transform-react-jsx', { pragma:'h' }]
+            ]
+        }),
+        nodeResolve({
+            jsnext: true
+        }),
+        commonjs()
+    ]
+};
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/footer.js b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/footer.js
new file mode 100644 (file)
index 0000000..5cb0ff5
--- /dev/null
@@ -0,0 +1,32 @@
+import { h, Component } from 'preact';
+import { pluralize } from './util';
+
+export default class TodoFooter extends Component {
+    render({ nowShowing, count, completedCount, onClearCompleted }) {
+        return (
+            <footer class="footer">
+                <span class="todo-count">
+                    <strong>{count}</strong> {pluralize(count, 'item')} left
+                </span>
+                <ul class="filters">
+                    <li>
+                        <a href="#/" class={nowShowing=='all' && 'selected'}>All</a>
+                    </li>
+                    {' '}
+                    <li>
+                        <a href="#/active" class={nowShowing=='active' && 'selected'}>Active</a>
+                    </li>
+                    {' '}
+                    <li>
+                        <a href="#/completed" class={nowShowing=='completed' && 'selected'}>Completed</a>
+                    </li>
+                </ul>
+                { completedCount > 0 && (
+                    <button class="clear-completed" onClick={onClearCompleted}>
+                        Clear completed
+                    </button>
+                ) }
+            </footer>
+        );
+    }
+}
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/index.js b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/index.js
new file mode 100644 (file)
index 0000000..76f87ce
--- /dev/null
@@ -0,0 +1,132 @@
+import { h, Component } from 'preact';
+import TodoModel from './model';
+import TodoFooter from './footer';
+import TodoItem from './item';
+
+const ENTER_KEY = 13;
+
+const FILTERS = {
+    all: todo => true,
+    active: todo => !todo.completed,
+    completed: todo => todo.completed
+};
+
+export default class App extends Component {
+    constructor() {
+        super();
+        this.model = new TodoModel('preact-todos', () => this.setState({}) );
+        addEventListener('hashchange', this.handleRoute.bind(this));
+        this.handleRoute();
+    }
+
+    handleRoute() {
+        let nowShowing = String(location.hash||'').split('/').pop();
+        if (!FILTERS[nowShowing]) {
+            nowShowing = 'all';
+        }
+        this.setState({ nowShowing });
+    }
+
+    handleNewTodoKeyDown = e => {
+        if (e.keyCode!==ENTER_KEY) return;
+        e.preventDefault();
+
+        // let val = '';
+        // if (this.state.newTodo === undefined) {
+        //  val = e.target.value.trim();
+        // }
+
+        let val = e.target.value.trim();
+
+        if (val) {
+            this.model.addTodo(val);
+            this.setState({ newTodo: '' });
+        }
+    };
+
+    toggleAll = event => {
+        let checked = event.target.checked;
+        this.model.toggleAll(checked);
+    };
+
+    toggle = todo => {
+        this.model.toggle(todo);
+    };
+
+    destroy = todo => {
+        this.model.destroy(todo);
+    };
+
+    edit = todo => {
+        this.setState({ editing: todo.id });
+    };
+
+    save = (todoToSave, text) => {
+        this.model.save(todoToSave, text);
+        this.setState({ editing: null });
+    };
+
+    cancel = () => {
+        this.setState({ editing: null });
+    };
+
+    clearCompleted = () => {
+        this.model.clearCompleted();
+    };
+
+    render({ }, { nowShowing=ALL_TODOS, newTodo, editing }) {
+        let { todos } = this.model,
+            shownTodos = todos.filter( FILTERS[nowShowing] ),
+            activeTodoCount = todos.reduce( (a, todo) => a + (todo.completed ? 0 : 1), 0),
+            completedCount = todos.length - activeTodoCount;
+
+        return (
+            <div>
+                <header class="header">
+                    <h1>todos</h1>
+                    <input
+                        class="new-todo"
+                        placeholder="What needs to be done?"
+                        value={newTodo}
+                        onKeyDown={this.handleNewTodoKeyDown}
+                        onInput={this.linkState('newTodo')}
+                        autoFocus={true}
+                    />
+                </header>
+
+                { todos.length ? (
+                    <section class="main">
+                        <input
+                            class="toggle-all"
+                            type="checkbox"
+                            onChange={this.toggleAll}
+                            checked={activeTodoCount === 0}
+                        />
+                        <ul class="todo-list">
+                            { shownTodos.map( todo => (
+                                <TodoItem
+                                    todo={todo}
+                                    onToggle={this.toggle}
+                                    onDestroy={this.destroy}
+                                    onEdit={this.edit}
+                                    editing={editing === todo.id}
+                                    onSave={this.save}
+                                    onCancel={this.cancel}
+                                />
+                            )) }
+                        </ul>
+                    </section>
+                ) : null }
+
+                { (activeTodoCount || completedCount) ? (
+                    <TodoFooter
+                        count={activeTodoCount}
+                        completedCount={completedCount}
+                        nowShowing={nowShowing}
+                        onClearCompleted={this.clearCompleted}
+                    />
+                ) : null }
+            </div>
+        );
+    }
+}
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/item.js b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/item.js
new file mode 100644 (file)
index 0000000..9854022
--- /dev/null
@@ -0,0 +1,84 @@
+import { h, Component } from 'preact';
+
+const ESCAPE_KEY = 27;
+const ENTER_KEY = 13;
+
+export default class TodoItem extends Component {
+    handleSubmit = () => {
+        let { onSave, onDestroy, todo } = this.props,
+            val = this.state.editText.trim();
+        if (val) {
+            onSave(todo, val);
+            this.setState({ editText: val });
+        }
+        else {
+            onDestroy(todo);
+        }
+    };
+
+    handleEdit = () => {
+        let { onEdit, todo } = this.props;
+        onEdit(todo);
+        this.setState({ editText: todo.title });
+    };
+
+    toggle = e => {
+        let { onToggle, todo } = this.props;
+        onToggle(todo);
+        e.preventDefault();
+    };
+
+    handleKeyDown = e => {
+        if (e.which===ESCAPE_KEY) {
+            let { todo } = this.props;
+            this.setState({ editText: todo.title });
+            this.props.onCancel(todo);
+        }
+        else if (e.which===ENTER_KEY) {
+            this.handleSubmit();
+        }
+    };
+    
+    handleDestroy = () => {
+        this.props.onDestroy(this.props.todo);
+    };
+
+    // shouldComponentUpdate({ todo, editing, editText }) {
+    //  return (
+    //      todo !== this.props.todo ||
+    //      editing !== this.props.editing ||
+    //      editText !== this.state.editText
+    //  );
+    // }
+
+    componentDidUpdate() {
+        let node = this.base && this.base.querySelector('.edit');
+        if (node) node.focus();
+    }
+
+    render({ todo:{ title, completed }, onToggle, onDestroy, editing }, { editText }) {
+        return (
+            <li class={{ completed, editing }}>
+                <div class="view">
+                    <input
+                        class="toggle"
+                        type="checkbox"
+                        checked={completed}
+                        onChange={this.toggle}
+                    />
+                    <label onDblClick={this.handleEdit}>{title}</label>
+                    <button class="destroy" onClick={this.handleDestroy} />
+                </div>
+                { editing && (
+                    <input
+                        class="edit"
+                        value={editText}
+                        onBlur={this.handleSubmit}
+                        onInput={this.linkState('editText')}
+                        onKeyDown={this.handleKeyDown}
+                    />
+                ) }
+            </li>
+        );
+    }
+}
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/model.js b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/model.js
new file mode 100644 (file)
index 0000000..2c8aae1
--- /dev/null
@@ -0,0 +1,54 @@
+import { uuid, store } from './util';
+
+export default class TodoModel {
+    constructor(key, sub) {
+        this.key = key;
+        this.todos = store(key) || [];
+        this.onChanges = [sub];
+    }
+
+    inform() {
+        store(this.key, this.todos);
+        this.onChanges.forEach( cb => cb() );
+    }
+
+    addTodo(title) {
+        this.todos = this.todos.concat({
+            id: uuid(),
+            title,
+            completed: false
+        });
+        this.inform();
+    }
+
+    toggleAll(completed) {
+        this.todos = this.todos.map(
+            todo => ({ ...todo, completed })
+        );
+        this.inform();
+    }
+
+    toggle(todoToToggle) {
+        this.todos = this.todos.map( todo => (
+            todo !== todoToToggle ? todo : ({ ...todo, completed: !todo.completed })
+        ) );
+        this.inform();
+    }
+
+    destroy(todo) {
+        this.todos = this.todos.filter( t => t !== todo );
+        this.inform();
+    }
+
+    save(todoToSave, title) {
+        this.todos = this.todos.map( todo => (
+            todo !== todoToSave ? todo : ({ ...todo, title })
+        ));
+        this.inform();
+    }
+
+    clearCompleted() {
+        this.todos = this.todos.filter( todo => !todo.completed );
+        this.inform();
+    }
+}
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/util.js b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/app/util.js
new file mode 100644 (file)
index 0000000..92e2716
--- /dev/null
@@ -0,0 +1,23 @@
+export function uuid() {
+    let uuid = '';
+    for (let i=0; i<32; i++) {
+        let random = Math.random() * 16 | 0;
+        if (i === 8 || i === 12 || i === 16 || i === 20) {
+            uuid += '-';
+        }
+        uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)).toString(16);
+    }
+    return uuid;
+}
+
+export function pluralize(count, word) {
+    return count === 1 ? word : word + 's';
+}
+
+export function store(namespace, data) {
+    // if (data) return localStorage[namespace] = JSON.stringify(data);
+
+    // let store = localStorage[namespace];
+    // return store && JSON.parse(store) || [];
+    return [];
+}
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/index.html b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/index.html
new file mode 100644 (file)
index 0000000..5bd6d6b
--- /dev/null
@@ -0,0 +1,17 @@
+<!doctype html>
+<html lang="en" data-framework="preact">
+    <head>
+        <meta charset="utf-8">
+        <title>Preact • TodoMVC</title>
+        <link rel="stylesheet" href="todomvc-common/base.css">
+        <link rel="stylesheet" href="todomvc.css">
+    </head>
+    <body>
+        <section class="todoapp"></section>
+        <footer class="info">
+            <p>Double-click to edit a todo</p>
+            <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
+        </footer>
+        <script src="app.js"></script>
+    </body>
+</html>
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/index.js b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/src/index.js
new file mode 100644 (file)
index 0000000..56bc51f
--- /dev/null
@@ -0,0 +1,6 @@
+import { h, render } from 'preact';
+import App from './app';
+// import 'todomvc-common';
+// import 'todomvc-common/base.css';
+// import 'todomvc-app-css/index.css';
+render(<App />, document.querySelector('.todoapp'));
diff --git a/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/webpack.config.babel.js b/PerformanceTests/Speedometer/resources/todomvc/architecture-examples/preact/webpack.config.babel.js
new file mode 100644 (file)
index 0000000..0a72e4c
--- /dev/null
@@ -0,0 +1,39 @@
+import ExtractTextPlugin from 'extract-text-webpack-plugin';
+import ReplacePlugin from 'replace-bundle-webpack-plugin';
+
+module.exports = {
+    entry: {
+        app: './src/index.js',
+        'todomvc-common': 'todomvc-common'
+    },
+    output: {
+        path: './build',
+        filename: '[name].js'
+    },
+    module: {
+        loaders: [
+            {
+                test: /\.jsx?$/,
+                exclude: /node_modules/,
+                loader: 'babel'
+            },
+            {
+                test: /\.css$/,
+                loader: ExtractTextPlugin.extract('style', 'css?sourceMap')
+            }
+        ]
+    },
+    plugins: [
+        new ExtractTextPlugin('style.css', { allChunks: true }),
+        new ReplacePlugin([{
+            // this is actually the property name https://github.com/kimhou/replace-bundle-webpack-plugin/issues/1
+            partten: /throw\s+(new\s+)?[a-zA-Z]+Error\s*\(/g,
+            replacement: () => 'return;('
+        }])
+    ],
+    devtool: 'source-map',
+    devServer: {
+        port: process.env.PORT || 8080,
+        contentBase: './src'
+    }
+};