diff --git a/frameworks/keyed/react-hooks-global-state/index.html b/frameworks/keyed/react-hooks-global-state/index.html
new file mode 100644
index 000000000..23297f4ec
--- /dev/null
+++ b/frameworks/keyed/react-hooks-global-state/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ react-hooks-global-state
+
+
+
+
+
+
+
diff --git a/frameworks/keyed/react-hooks-global-state/package.json b/frameworks/keyed/react-hooks-global-state/package.json
new file mode 100644
index 000000000..8593af612
--- /dev/null
+++ b/frameworks/keyed/react-hooks-global-state/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "js-framework-benchmark-react-hooks",
+ "version": "1.1.1",
+ "description": "React Hooks demo",
+ "main": "index.js",
+ "js-framework-benchmark": {
+ "frameworkVersionFromPackage": "react-hooks-global-state"
+ },
+ "scripts": {
+ "build-dev": "webpack --watch",
+ "build-prod": "webpack"
+ },
+ "keywords": [
+ "react",
+ "webpack"
+ ],
+ "author": "Stefan Krause",
+ "license": "Apache-2.0",
+ "homepage": "https://github.com/krausest/js-framework-benchmark",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/krausest/js-framework-benchmark.git"
+ },
+ "devDependencies": {
+ "@babel/core": "7.4.5",
+ "@babel/preset-env": "7.4.5",
+ "@babel/preset-react": "7.0.0",
+ "@babel/plugin-proposal-class-properties": "7.4.4",
+ "babel-loader": "8.0.6",
+ "terser-webpack-plugin": "1.3.0",
+ "webpack": "4.34.0",
+ "webpack-cli": "3.3.4"
+ },
+ "dependencies": {
+ "react": "16.8.6",
+ "react-dom": "16.8.6",
+ "react-hooks-global-state": "1.0.0"
+ }
+}
diff --git a/frameworks/keyed/react-hooks-global-state/src/main.jsx b/frameworks/keyed/react-hooks-global-state/src/main.jsx
new file mode 100644
index 000000000..d202e24e0
--- /dev/null
+++ b/frameworks/keyed/react-hooks-global-state/src/main.jsx
@@ -0,0 +1,131 @@
+import React, { memo, useReducer, useCallback } from 'react';
+import ReactDOM from 'react-dom';
+import { createGlobalState } from 'react-hooks-global-state';
+
+function random(max) { return Math.round(Math.random() * 1000) % max; }
+
+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 = 1;
+
+function buildData(count) {
+ const data = new Array(count);
+ for (let i = 0; i < count; i++) {
+ data[i] = {
+ id: nextId++,
+ label: `${A[random(A.length)]} ${C[random(C.length)]} ${N[random(N.length)]}`,
+ };
+ }
+ return data;
+}
+
+const { useGlobalState, setGlobalState } = createGlobalState({ data: [], selected: 0 });
+
+function doAction(action) {
+ switch (action.type) {
+ case 'RUN':
+ setGlobalState('data', buildData(1000));
+ setGlobalState('selected', 0);
+ break;
+ case 'RUN_LOTS':
+ setGlobalState('data', buildData(10000));
+ setGlobalState('selected', 0);
+ break;
+ case 'ADD':
+ setGlobalState('data', data => data.concat(buildData(1000)));
+ break;
+ case 'UPDATE':
+ setGlobalState('data', data => {
+ const newData = data.slice(0);
+ for (let i = 0; i < newData.length; i += 10) {
+ const r = newData[i];
+ newData[i] = { id: r.id, label: r.label + " !!!" };
+ }
+ return newData;
+ });
+ break;
+ case 'CLEAR':
+ setGlobalState('data', []);
+ setGlobalState('selected', 0);
+ break;
+ case 'SWAP_ROWS':
+ setGlobalState('data', data => [data[0], data[998], ...data.slice(2, 998), data[1], data[999]]);
+ break;
+ case 'REMOVE':
+ setGlobalState('data', data => {
+ const idx = data.findIndex((d) => d.id === action.id);
+ return [...data.slice(0, idx), ...data.slice(idx + 1)];
+ });
+ break;
+ case 'SELECT':
+ setGlobalState('selected', action.id);
+ break;
+ }
+}
+
+const GlyphIcon = ;
+
+const Row = memo(({ item, selected }) => {
+ const select = useCallback(() => doAction({ type: 'SELECT', id: item.id }), []);
+ const remove = useCallback(() => doAction({ type: 'REMOVE', id: item.id }), []);
+
+ return (
+ | {item.id} |
+ {item.label} |
+ {GlyphIcon} |
+ |
+
);
+});
+
+const Button = ({ id, cb, title }) => (
+
+
+
+);
+
+const Jumbotron = memo(() => (
+
+
+
+
react-hooks-global-state
+
+
+
+
+
+
+
+), () => true);
+
+const Main = () => {
+ const [data] = useGlobalState('data');
+ const [selected] = useGlobalState('selected');
+
+ return (
+
+
+ {data.map(item => (
+
+ ))}
+
+
+
);
+};
+
+ReactDOM.render(
+ (
+
+ ),
+ document.getElementById('main')
+);
diff --git a/frameworks/keyed/react-hooks-global-state/webpack.config.js b/frameworks/keyed/react-hooks-global-state/webpack.config.js
new file mode 100644
index 000000000..f6607fcc0
--- /dev/null
+++ b/frameworks/keyed/react-hooks-global-state/webpack.config.js
@@ -0,0 +1,78 @@
+const path = require('path');
+const webpack = require('webpack');
+const TerserPlugin = require('terser-webpack-plugin');
+
+module.exports = {
+ mode: 'production',
+ // mode: 'development',
+ entry: {
+ main: path.join(__dirname, 'src', 'main.jsx'),
+ },
+ output: {
+ path: path.join(__dirname, 'dist'),
+ filename: '[name].js'
+ },
+ resolve: {
+ extensions: ['.js', '.jsx']
+ },
+ module: {
+ rules: [{
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/preset-env', '@babel/preset-react'],
+ plugins: ['@babel/plugin-proposal-class-properties'],
+ }
+ }
+ ]
+ }]
+ },
+ optimization: {
+ minimizer: [
+ new TerserPlugin({
+ terserOptions: {
+ parse: {
+ // we want terser to parse ecma 8 code. However, we don't want it
+ // to apply any minfication steps that turns valid ecma 5 code
+ // into invalid ecma 5 code. This is why the 'compress' and 'output'
+ // sections only apply transformations that are ecma 5 safe
+ // https://github.com/facebook/create-react-app/pull/4234
+ ecma: 8,
+ },
+ compress: {
+ ecma: 5,
+ warnings: false,
+ // Disabled because of an issue with Uglify breaking seemingly valid code:
+ // https://github.com/facebook/create-react-app/issues/2376
+ // Pending further investigation:
+ // https://github.com/mishoo/UglifyJS2/issues/2011
+ comparisons: false,
+ },
+ mangle: {
+ safari10: true,
+ },
+ output: {
+ ecma: 5,
+ comments: false,
+ // Turned on because emoji and regex is not minified properly using default
+ // https://github.com/facebook/create-react-app/issues/2488
+ ascii_only: true,
+ },
+ },
+ // Use multi-process parallel running to improve the build speed
+ // Default number of concurrent runs: os.cpus().length - 1
+ parallel: true,
+ // Enable file caching
+ cache: true,
+ }),
+ ]
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': { NODE_ENV: JSON.stringify('production') }
+ }),
+ ],
+};
diff --git a/frameworks/keyed/react-redux-hooks-worker/index.html b/frameworks/keyed/react-redux-hooks-worker/index.html
new file mode 100755
index 000000000..17e95220c
--- /dev/null
+++ b/frameworks/keyed/react-redux-hooks-worker/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ React + Redux
+
+
+
+
+
+
+
diff --git a/frameworks/keyed/react-redux-hooks-worker/package.json b/frameworks/keyed/react-redux-hooks-worker/package.json
new file mode 100644
index 000000000..8daa094a5
--- /dev/null
+++ b/frameworks/keyed/react-redux-hooks-worker/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "js-framework-benchmark-react",
+ "version": "1.1.1",
+ "description": "React demo",
+ "main": "index.js",
+ "js-framework-benchmark": {
+ "frameworkVersionFromPackage": "react-redux:redux-in-worker"
+ },
+ "scripts": {
+ "build-dev": "webpack --watch",
+ "build-prod": "webpack"
+ },
+ "keywords": [
+ "react",
+ "webpack"
+ ],
+ "author": "Stefan Krause",
+ "license": "Apache-2.0",
+ "homepage": "https://github.com/krausest/js-framework-benchmark",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/krausest/js-framework-benchmark.git"
+ },
+ "devDependencies": {
+ "@babel/core": "7.4.5",
+ "@babel/plugin-proposal-class-properties": "7.4.4",
+ "@babel/preset-env": "7.4.5",
+ "@babel/preset-react": "7.0.0",
+ "babel-loader": "8.0.6",
+ "terser-webpack-plugin": "1.3.0",
+ "webpack": "4.34.0",
+ "webpack-cli": "3.3.4",
+ "worker-plugin": "^3.1.0"
+ },
+ "dependencies": {
+ "react": "16.8.6",
+ "react-dom": "16.8.6",
+ "react-redux": "7.1.0",
+ "redux": "4.0.1",
+ "redux-in-worker": "0.7.0"
+ }
+}
diff --git a/frameworks/keyed/react-redux-hooks-worker/src/main.jsx b/frameworks/keyed/react-redux-hooks-worker/src/main.jsx
new file mode 100755
index 000000000..3197fe8a4
--- /dev/null
+++ b/frameworks/keyed/react-redux-hooks-worker/src/main.jsx
@@ -0,0 +1,71 @@
+import React, { useCallback } from "react";
+import ReactDOM from "react-dom";
+import { wrapStore } from "redux-in-worker";
+import { Provider, useDispatch, useSelector } from "react-redux";
+
+import { initialState } from "./store.worker";
+
+const worker = new Worker("./store.worker", { type: "module" });
+const store = wrapStore(worker, initialState);
+
+const GlyphIcon = ;
+
+const Row = React.memo(({ data }) => {
+ const isSelected = useSelector((state) => state.selected === data.id);
+ const dispatch = useDispatch();
+ const select = useCallback(() => { dispatch({ type: "SELECT", id: data.id }); }, [data]);
+ const remove = useCallback(() => { dispatch({ type: "REMOVE", id: data.id }); }, [data]);
+ return (
+
+ | {data.id} |
+ {data.label} |
+ {GlyphIcon} |
+ |
+
+ )
+});
+
+const RowList = React.memo(() => {
+ const rows = useSelector((state) => state.data);
+ return rows.map((data) =>
);
+});
+
+const Button = React.memo(({ id, title, cb }) => (
+
+ {title}
+
+));
+
+const Main = () => {
+ const dispatch = useDispatch();
+ const run = useCallback(() => { dispatch({ type: "RUN" }); }, []);
+ const runLots = useCallback(() => { dispatch({ type: "RUN_LOTS" }); }, []);
+ const add = useCallback(() => { dispatch({ type: "ADD" }); }, []);
+ const update = useCallback(() => { dispatch({ type: "UPDATE" }); }, []);
+ const clear = useCallback(() => { dispatch({ type: "CLEAR" }); }, []);
+ const swapRows = useCallback(() => { dispatch({ type: "SWAP_ROWS" }); }, []);
+ return (
+
+ );
+};
+
+ReactDOM.render(
+ ,
+ document.getElementById("main")
+);
diff --git a/frameworks/keyed/react-redux-hooks-worker/src/store.worker.js b/frameworks/keyed/react-redux-hooks-worker/src/store.worker.js
new file mode 100755
index 000000000..9a0b2a2e4
--- /dev/null
+++ b/frameworks/keyed/react-redux-hooks-worker/src/store.worker.js
@@ -0,0 +1,64 @@
+import { createStore } from "redux";
+import { exposeStore } from "redux-in-worker";
+
+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"];
+
+const random = (max) => Math.round(Math.random() * 1000) % max;
+
+let nextId = 1;
+function buildData(count) {
+ const data = new Array(count);
+ for (let i = 0; i < count; i++) {
+ data[i] = {
+ id: nextId++,
+ label: `${A[random(A.length)]} ${C[random(C.length)]} ${N[random(N.length)]}`,
+ }
+ }
+ return data;
+}
+
+export const initialState = { data: [], selected: 0 };
+const store = createStore((state = initialState, action) => {
+ const { data, selected } = state;
+ switch (action.type) {
+ case "RUN":
+ return { data: buildData(1000), selected: 0 };
+ case "RUN_LOTS":
+ return { data: buildData(10000), selected: 0 };
+ case "ADD":
+ return { data: data.concat(buildData(1000)), selected };
+ case "UPDATE": {
+ const newData = data.slice();
+ for (let i = 0; i < newData.length; i += 10) {
+ const r = newData[i];
+ newData[i] = { id: r.id, label: r.label + " !!!" };
+ }
+ return { data: newData, selected };
+ }
+ case "REMOVE": {
+ const idx = data.findIndex(d => d.id === action.id);
+ const newData = data.slice();
+ newData.splice(idx, 1);
+ return { data: newData, selected };
+ }
+ case "SELECT":
+ return { data, selected: action.id };
+ case "CLEAR":
+ return { data: [], selected: 0 };
+ case "SWAP_ROWS": {
+ const newData = data.slice();
+ const tmp = newData[1];
+ newData[1] = newData[998];
+ newData[998] = tmp;
+ return { data: newData, selected };
+ }
+ }
+ return state;
+});
+
+exposeStore(store);
diff --git a/frameworks/keyed/react-redux-hooks-worker/webpack.config.js b/frameworks/keyed/react-redux-hooks-worker/webpack.config.js
new file mode 100755
index 000000000..ee7ffddfc
--- /dev/null
+++ b/frameworks/keyed/react-redux-hooks-worker/webpack.config.js
@@ -0,0 +1,81 @@
+const path = require('path');
+const webpack = require('webpack');
+const TerserPlugin = require('terser-webpack-plugin');
+const WorkerPlugin = require('worker-plugin');
+
+module.exports = {
+ mode: 'production',
+ // mode: 'development',
+ entry: {
+ main: path.join(__dirname, 'src', 'main.jsx'),
+ },
+ output: {
+ path: path.join(__dirname, 'dist'),
+ filename: '[name].js',
+ publicPath: 'dist/'
+ },
+ resolve: {
+ extensions: ['.js', '.jsx']
+ },
+ module: {
+ rules: [{
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/preset-env', '@babel/preset-react'],
+ plugins: ['@babel/plugin-proposal-class-properties'],
+ }
+ }
+ ]
+ }]
+ },
+ optimization: {
+ minimizer: [
+ new TerserPlugin({
+ terserOptions: {
+ parse: {
+ // we want terser to parse ecma 8 code. However, we don't want it
+ // to apply any minfication steps that turns valid ecma 5 code
+ // into invalid ecma 5 code. This is why the 'compress' and 'output'
+ // sections only apply transformations that are ecma 5 safe
+ // https://github.com/facebook/create-react-app/pull/4234
+ ecma: 8,
+ },
+ compress: {
+ ecma: 5,
+ warnings: false,
+ // Disabled because of an issue with Uglify breaking seemingly valid code:
+ // https://github.com/facebook/create-react-app/issues/2376
+ // Pending further investigation:
+ // https://github.com/mishoo/UglifyJS2/issues/2011
+ comparisons: false,
+ },
+ mangle: {
+ safari10: true,
+ },
+ output: {
+ ecma: 5,
+ comments: false,
+ // Turned on because emoji and regex is not minified properly using default
+ // https://github.com/facebook/create-react-app/issues/2488
+ ascii_only: true,
+ },
+ },
+ // Use multi-process parallel running to improve the build speed
+ // Default number of concurrent runs: os.cpus().length - 1
+ parallel: true,
+ // Enable file caching
+ cache: true,
+ }),
+ ]
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': { NODE_ENV: JSON.stringify('production') }
+ }),
+ new WorkerPlugin(),
+ ],
+};
diff --git a/frameworks/keyed/react-redux-hooks/package.json b/frameworks/keyed/react-redux-hooks/package.json
index 43aa15a52..42c759321 100644
--- a/frameworks/keyed/react-redux-hooks/package.json
+++ b/frameworks/keyed/react-redux-hooks/package.json
@@ -4,7 +4,7 @@
"description": "React demo",
"main": "index.js",
"js-framework-benchmark": {
- "frameworkVersionFromPackage": "react:react-redux"
+ "frameworkVersionFromPackage": "react-redux"
},
"scripts": {
"build-dev": "webpack --watch",
diff --git a/frameworks/keyed/react-redux-use-context-selector/index.html b/frameworks/keyed/react-redux-use-context-selector/index.html
new file mode 100755
index 000000000..5245ca3f6
--- /dev/null
+++ b/frameworks/keyed/react-redux-use-context-selector/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Redux + useContextSelector
+
+
+
+
+
+
+
diff --git a/frameworks/keyed/react-redux-use-context-selector/package.json b/frameworks/keyed/react-redux-use-context-selector/package.json
new file mode 100644
index 000000000..c0f547cd3
--- /dev/null
+++ b/frameworks/keyed/react-redux-use-context-selector/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "js-framework-benchmark-react",
+ "version": "1.1.1",
+ "description": "React demo",
+ "main": "index.js",
+ "js-framework-benchmark": {
+ "frameworkVersionFromPackage": "use-context-selector"
+ },
+ "scripts": {
+ "build-dev": "webpack --watch",
+ "build-prod": "webpack"
+ },
+ "keywords": [
+ "react",
+ "webpack"
+ ],
+ "author": "Stefan Krause",
+ "license": "Apache-2.0",
+ "homepage": "https://github.com/krausest/js-framework-benchmark",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/krausest/js-framework-benchmark.git"
+ },
+ "devDependencies": {
+ "@babel/core": "7.4.5",
+ "@babel/preset-env": "7.4.5",
+ "@babel/preset-react": "7.0.0",
+ "@babel/plugin-proposal-class-properties": "7.4.4",
+ "babel-loader": "8.0.6",
+ "terser-webpack-plugin": "1.3.0",
+ "webpack": "4.34.0",
+ "webpack-cli": "3.3.4"
+ },
+ "dependencies": {
+ "react": "16.8.6",
+ "react-dom": "16.8.6",
+ "redux": "4.0.1",
+ "use-context-selector": "1.0.1"
+ }
+}
diff --git a/frameworks/keyed/react-redux-use-context-selector/src/main.jsx b/frameworks/keyed/react-redux-use-context-selector/src/main.jsx
new file mode 100755
index 000000000..3c33bad7d
--- /dev/null
+++ b/frameworks/keyed/react-redux-use-context-selector/src/main.jsx
@@ -0,0 +1,144 @@
+import React, { useCallback, useState, useEffect } from "react";
+import ReactDOM from "react-dom";
+import { createStore } from "redux";
+import { createContext, useContextSelector } from "use-context-selector";
+
+const DispatchContext = createContext();
+const StateContext = createContext();
+const Provider = ({ store, children }) => {
+ const [state, setState] = useState(store.getState());
+ useEffect(() => {
+ return store.subscribe(() => setState(store.getState()));
+ }, []);
+ return (
+
+
+ {children}
+
+
+ );
+};
+const identity = x => x;
+const useDispatch = () => useContextSelector(DispatchContext, identity);
+const useSelector = (selector) => useContextSelector(StateContext, selector);
+
+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"];
+
+const random = (max) => Math.round(Math.random() * 1000) % max;
+
+let nextId = 1;
+function buildData(count) {
+ const data = new Array(count);
+ for (let i = 0; i < count; i++) {
+ data[i] = {
+ id: nextId++,
+ label: `${A[random(A.length)]} ${C[random(C.length)]} ${N[random(N.length)]}`,
+ }
+ }
+ return data;
+}
+
+const store = createStore((state = { data: [], selected: 0 }, action) => {
+ const { data, selected } = state;
+ switch (action.type) {
+ case "RUN":
+ return { data: buildData(1000), selected: 0 };
+ case "RUN_LOTS":
+ return { data: buildData(10000), selected: 0 };
+ case "ADD":
+ return { data: data.concat(buildData(1000)), selected };
+ case "UPDATE": {
+ const newData = data.slice();
+ for (let i = 0; i < newData.length; i += 10) {
+ const r = newData[i];
+ newData[i] = { id: r.id, label: r.label + " !!!" };
+ }
+ return { data: newData, selected };
+ }
+ case "REMOVE": {
+ const newData = data.slice();
+ newData.splice(data.indexOf(action.item), 1);
+ return { data: newData, selected };
+ }
+ case "SELECT":
+ return { data, selected: action.id };
+ case "CLEAR":
+ return { data: [], selected: 0 };
+ case "SWAP_ROWS": {
+ const newData = data.slice();
+ const tmp = newData[1];
+ newData[1] = newData[998];
+ newData[998] = tmp;
+ return { data: newData, selected };
+ }
+ }
+ return state;
+});
+
+const GlyphIcon = ;
+
+const Row = React.memo(({ data }) => {
+ const dataId = data.id;
+ const isSelected = useSelector(useCallback((state) => state.selected === dataId, [dataId]));
+ const dispatch = useDispatch();
+ const select = useCallback(() => { dispatch({ type: "SELECT", id: dataId }); }, [dataId]);
+ const remove = useCallback(() => { dispatch({ type: "REMOVE", item: data }); }, [data]);
+ return (
+
+ | {dataId} |
+ {data.label} |
+ {GlyphIcon} |
+ |
+
+ )
+});
+
+const RowList = React.memo(() => {
+ const rows = useSelector(useCallback((state) => state.data, []));
+ return rows.map((data) =>
);
+});
+
+const Button = React.memo(({ id, title, cb }) => (
+
+ {title}
+
+));
+
+const Main = () => {
+ const dispatch = useDispatch();
+ const run = useCallback(() => { dispatch({ type: "RUN" }); }, []);
+ const runLots = useCallback(() => { dispatch({ type: "RUN_LOTS" }); }, []);
+ const add = useCallback(() => { dispatch({ type: "ADD" }); }, []);
+ const update = useCallback(() => { dispatch({ type: "UPDATE" }); }, []);
+ const clear = useCallback(() => { dispatch({ type: "CLEAR" }); }, []);
+ const swapRows = useCallback(() => { dispatch({ type: "SWAP_ROWS" }); }, []);
+ return (
+
+
+
+
Redux + useContextSelector
+
+
+
+
+
+
+ );
+};
+
+ReactDOM.render(
+ ,
+ document.getElementById("main")
+);
diff --git a/frameworks/keyed/react-redux-use-context-selector/webpack.config.js b/frameworks/keyed/react-redux-use-context-selector/webpack.config.js
new file mode 100755
index 000000000..f6607fcc0
--- /dev/null
+++ b/frameworks/keyed/react-redux-use-context-selector/webpack.config.js
@@ -0,0 +1,78 @@
+const path = require('path');
+const webpack = require('webpack');
+const TerserPlugin = require('terser-webpack-plugin');
+
+module.exports = {
+ mode: 'production',
+ // mode: 'development',
+ entry: {
+ main: path.join(__dirname, 'src', 'main.jsx'),
+ },
+ output: {
+ path: path.join(__dirname, 'dist'),
+ filename: '[name].js'
+ },
+ resolve: {
+ extensions: ['.js', '.jsx']
+ },
+ module: {
+ rules: [{
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/preset-env', '@babel/preset-react'],
+ plugins: ['@babel/plugin-proposal-class-properties'],
+ }
+ }
+ ]
+ }]
+ },
+ optimization: {
+ minimizer: [
+ new TerserPlugin({
+ terserOptions: {
+ parse: {
+ // we want terser to parse ecma 8 code. However, we don't want it
+ // to apply any minfication steps that turns valid ecma 5 code
+ // into invalid ecma 5 code. This is why the 'compress' and 'output'
+ // sections only apply transformations that are ecma 5 safe
+ // https://github.com/facebook/create-react-app/pull/4234
+ ecma: 8,
+ },
+ compress: {
+ ecma: 5,
+ warnings: false,
+ // Disabled because of an issue with Uglify breaking seemingly valid code:
+ // https://github.com/facebook/create-react-app/issues/2376
+ // Pending further investigation:
+ // https://github.com/mishoo/UglifyJS2/issues/2011
+ comparisons: false,
+ },
+ mangle: {
+ safari10: true,
+ },
+ output: {
+ ecma: 5,
+ comments: false,
+ // Turned on because emoji and regex is not minified properly using default
+ // https://github.com/facebook/create-react-app/issues/2488
+ ascii_only: true,
+ },
+ },
+ // Use multi-process parallel running to improve the build speed
+ // Default number of concurrent runs: os.cpus().length - 1
+ parallel: true,
+ // Enable file caching
+ cache: true,
+ }),
+ ]
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': { NODE_ENV: JSON.stringify('production') }
+ }),
+ ],
+};
diff --git a/frameworks/keyed/react-tracked-useSelector/index.html b/frameworks/keyed/react-tracked-useSelector/index.html
new file mode 100755
index 000000000..873072985
--- /dev/null
+++ b/frameworks/keyed/react-tracked-useSelector/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ react-tracked
+
+
+
+
+
+
+
diff --git a/frameworks/keyed/react-tracked-useSelector/package.json b/frameworks/keyed/react-tracked-useSelector/package.json
new file mode 100644
index 000000000..6f6716532
--- /dev/null
+++ b/frameworks/keyed/react-tracked-useSelector/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "js-framework-benchmark-react",
+ "version": "1.1.1",
+ "description": "React demo",
+ "main": "index.js",
+ "js-framework-benchmark": {
+ "frameworkVersionFromPackage": "react-tracked"
+ },
+ "scripts": {
+ "build-dev": "webpack --watch",
+ "build-prod": "webpack"
+ },
+ "keywords": [
+ "react",
+ "webpack"
+ ],
+ "author": "Stefan Krause",
+ "license": "Apache-2.0",
+ "homepage": "https://github.com/krausest/js-framework-benchmark",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/krausest/js-framework-benchmark.git"
+ },
+ "devDependencies": {
+ "@babel/core": "7.4.5",
+ "@babel/preset-env": "7.4.5",
+ "@babel/preset-react": "7.0.0",
+ "@babel/plugin-proposal-class-properties": "7.4.4",
+ "babel-loader": "8.0.6",
+ "terser-webpack-plugin": "1.3.0",
+ "webpack": "4.34.0",
+ "webpack-cli": "3.3.4"
+ },
+ "dependencies": {
+ "react": "16.8.6",
+ "react-dom": "16.8.6",
+ "react-tracked": "1.0.6"
+ }
+}
diff --git a/frameworks/keyed/react-tracked-useSelector/src/main.jsx b/frameworks/keyed/react-tracked-useSelector/src/main.jsx
new file mode 100755
index 000000000..4dc867e16
--- /dev/null
+++ b/frameworks/keyed/react-tracked-useSelector/src/main.jsx
@@ -0,0 +1,125 @@
+import React, { useCallback, useReducer } from "react";
+import ReactDOM from "react-dom";
+import { Provider, useDispatch, useSelector } from "react-tracked";
+
+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"];
+
+const random = (max) => Math.round(Math.random() * 1000) % max;
+
+let nextId = 1;
+function buildData(count) {
+ const data = new Array(count);
+ for (let i = 0; i < count; i++) {
+ data[i] = {
+ id: nextId++,
+ label: `${A[random(A.length)]} ${C[random(C.length)]} ${N[random(N.length)]}`,
+ }
+ }
+ return data;
+}
+
+const initialState = { data: [], selected: 0 };
+const reducer = (state, action) => {
+ const { data, selected } = state;
+ switch (action.type) {
+ case "RUN":
+ return { data: buildData(1000), selected: 0 };
+ case "RUN_LOTS":
+ return { data: buildData(10000), selected: 0 };
+ case "ADD":
+ return { data: data.concat(buildData(1000)), selected };
+ case "UPDATE": {
+ const newData = data.slice();
+ for (let i = 0; i < newData.length; i += 10) {
+ const r = newData[i];
+ newData[i] = { id: r.id, label: r.label + " !!!" };
+ }
+ return { data: newData, selected };
+ }
+ case "REMOVE": {
+ const newData = data.slice();
+ newData.splice(data.indexOf(action.item), 1);
+ return { data: newData, selected };
+ }
+ case "SELECT":
+ return { data, selected: action.id };
+ case "CLEAR":
+ return { data: [], selected: 0 };
+ case "SWAP_ROWS": {
+ const newData = data.slice();
+ const tmp = newData[1];
+ newData[1] = newData[998];
+ newData[998] = tmp;
+ return { data: newData, selected };
+ }
+ }
+ return state;
+};
+const useValue = () => useReducer(reducer, initialState);
+
+const GlyphIcon = ;
+
+const Row = React.memo(({ data }) => {
+ const isSelected = useSelector((state) => state.selected === data.id);
+ const dispatch = useDispatch();
+ const select = useCallback(() => { dispatch({ type: "SELECT", id: data.id }); }, [data]);
+ const remove = useCallback(() => { dispatch({ type: "REMOVE", item: data }); }, [data]);
+ return (
+
+ | {data.id} |
+ {data.label} |
+ {GlyphIcon} |
+ |
+
+ )
+});
+
+const RowList = React.memo(() => {
+ const rows = useSelector((state) => state.data);
+ return rows.map((data) =>
);
+});
+
+const Button = React.memo(({ id, title, cb }) => (
+
+ {title}
+
+));
+
+const Main = () => {
+ const dispatch = useDispatch();
+ const run = useCallback(() => { dispatch({ type: "RUN" }); }, []);
+ const runLots = useCallback(() => { dispatch({ type: "RUN_LOTS" }); }, []);
+ const add = useCallback(() => { dispatch({ type: "ADD" }); }, []);
+ const update = useCallback(() => { dispatch({ type: "UPDATE" }); }, []);
+ const clear = useCallback(() => { dispatch({ type: "CLEAR" }); }, []);
+ const swapRows = useCallback(() => { dispatch({ type: "SWAP_ROWS" }); }, []);
+ return (
+
+
+
+
react-tracked useSelector
+
+
+
+
+
+
+ );
+};
+
+ReactDOM.render(
+ ,
+ document.getElementById("main")
+);
diff --git a/frameworks/keyed/react-tracked-useSelector/webpack.config.js b/frameworks/keyed/react-tracked-useSelector/webpack.config.js
new file mode 100755
index 000000000..f6607fcc0
--- /dev/null
+++ b/frameworks/keyed/react-tracked-useSelector/webpack.config.js
@@ -0,0 +1,78 @@
+const path = require('path');
+const webpack = require('webpack');
+const TerserPlugin = require('terser-webpack-plugin');
+
+module.exports = {
+ mode: 'production',
+ // mode: 'development',
+ entry: {
+ main: path.join(__dirname, 'src', 'main.jsx'),
+ },
+ output: {
+ path: path.join(__dirname, 'dist'),
+ filename: '[name].js'
+ },
+ resolve: {
+ extensions: ['.js', '.jsx']
+ },
+ module: {
+ rules: [{
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/preset-env', '@babel/preset-react'],
+ plugins: ['@babel/plugin-proposal-class-properties'],
+ }
+ }
+ ]
+ }]
+ },
+ optimization: {
+ minimizer: [
+ new TerserPlugin({
+ terserOptions: {
+ parse: {
+ // we want terser to parse ecma 8 code. However, we don't want it
+ // to apply any minfication steps that turns valid ecma 5 code
+ // into invalid ecma 5 code. This is why the 'compress' and 'output'
+ // sections only apply transformations that are ecma 5 safe
+ // https://github.com/facebook/create-react-app/pull/4234
+ ecma: 8,
+ },
+ compress: {
+ ecma: 5,
+ warnings: false,
+ // Disabled because of an issue with Uglify breaking seemingly valid code:
+ // https://github.com/facebook/create-react-app/issues/2376
+ // Pending further investigation:
+ // https://github.com/mishoo/UglifyJS2/issues/2011
+ comparisons: false,
+ },
+ mangle: {
+ safari10: true,
+ },
+ output: {
+ ecma: 5,
+ comments: false,
+ // Turned on because emoji and regex is not minified properly using default
+ // https://github.com/facebook/create-react-app/issues/2488
+ ascii_only: true,
+ },
+ },
+ // Use multi-process parallel running to improve the build speed
+ // Default number of concurrent runs: os.cpus().length - 1
+ parallel: true,
+ // Enable file caching
+ cache: true,
+ }),
+ ]
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': { NODE_ENV: JSON.stringify('production') }
+ }),
+ ],
+};
diff --git a/frameworks/keyed/react-tracked-useTrackedState/index.html b/frameworks/keyed/react-tracked-useTrackedState/index.html
new file mode 100755
index 000000000..873072985
--- /dev/null
+++ b/frameworks/keyed/react-tracked-useTrackedState/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ react-tracked
+
+
+
+
+
+
+
diff --git a/frameworks/keyed/react-tracked-useTrackedState/package.json b/frameworks/keyed/react-tracked-useTrackedState/package.json
new file mode 100644
index 000000000..6f6716532
--- /dev/null
+++ b/frameworks/keyed/react-tracked-useTrackedState/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "js-framework-benchmark-react",
+ "version": "1.1.1",
+ "description": "React demo",
+ "main": "index.js",
+ "js-framework-benchmark": {
+ "frameworkVersionFromPackage": "react-tracked"
+ },
+ "scripts": {
+ "build-dev": "webpack --watch",
+ "build-prod": "webpack"
+ },
+ "keywords": [
+ "react",
+ "webpack"
+ ],
+ "author": "Stefan Krause",
+ "license": "Apache-2.0",
+ "homepage": "https://github.com/krausest/js-framework-benchmark",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/krausest/js-framework-benchmark.git"
+ },
+ "devDependencies": {
+ "@babel/core": "7.4.5",
+ "@babel/preset-env": "7.4.5",
+ "@babel/preset-react": "7.0.0",
+ "@babel/plugin-proposal-class-properties": "7.4.4",
+ "babel-loader": "8.0.6",
+ "terser-webpack-plugin": "1.3.0",
+ "webpack": "4.34.0",
+ "webpack-cli": "3.3.4"
+ },
+ "dependencies": {
+ "react": "16.8.6",
+ "react-dom": "16.8.6",
+ "react-tracked": "1.0.6"
+ }
+}
diff --git a/frameworks/keyed/react-tracked-useTrackedState/src/main.jsx b/frameworks/keyed/react-tracked-useTrackedState/src/main.jsx
new file mode 100755
index 000000000..20e88fd92
--- /dev/null
+++ b/frameworks/keyed/react-tracked-useTrackedState/src/main.jsx
@@ -0,0 +1,125 @@
+import React, { useCallback, useReducer } from "react";
+import ReactDOM from "react-dom";
+import { Provider, useDispatch, useTrackedState } from "react-tracked";
+
+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"];
+
+const random = (max) => Math.round(Math.random() * 1000) % max;
+
+let nextId = 1;
+function buildData(count) {
+ const data = new Array(count);
+ for (let i = 0; i < count; i++) {
+ data[i] = {
+ id: nextId++,
+ label: `${A[random(A.length)]} ${C[random(C.length)]} ${N[random(N.length)]}`,
+ }
+ }
+ return data;
+}
+
+const initialState = { data: [], selected: 0 };
+const reducer = (state , action) => {
+ const { data, selected } = state;
+ switch (action.type) {
+ case "RUN":
+ return { data: buildData(1000), selected: 0 };
+ case "RUN_LOTS":
+ return { data: buildData(10000), selected: 0 };
+ case "ADD":
+ return { data: data.concat(buildData(1000)), selected };
+ case "UPDATE": {
+ const newData = data.slice();
+ for (let i = 0; i < newData.length; i += 10) {
+ const r = newData[i];
+ newData[i] = { id: r.id, label: r.label + " !!!" };
+ }
+ return { data: newData, selected };
+ }
+ case "REMOVE": {
+ const idx = data.findIndex(d => d.id === action.id);
+ const newData = data.slice();
+ newData.splice(idx, 1);
+ return { data: newData, selected };
+ }
+ case "SELECT":
+ return { data, selected: action.id };
+ case "CLEAR":
+ return { data: [], selected: 0 };
+ case "SWAP_ROWS": {
+ const newData = data.slice();
+ const tmp = newData[1];
+ newData[1] = newData[998];
+ newData[998] = tmp;
+ return { data: newData, selected };
+ }
+ }
+ return state;
+};
+const useValue = () => useReducer(reducer, initialState);
+
+const GlyphIcon = ;
+
+const Row = React.memo(({ data, selected }) => {
+ const dispatch = useDispatch();
+ const select = useCallback(() => { dispatch({ type: "SELECT", id: data.id }); }, [data]);
+ const remove = useCallback(() => { dispatch({ type: "REMOVE", id: data.id }); }, [data]);
+ return (
+
+ | {data.id} |
+ {data.label} |
+ {GlyphIcon} |
+ |
+
+ )
+});
+
+const RowList = React.memo(() => {
+ const state = useTrackedState();
+ return state.data.map((data) =>
);
+});
+
+const Button = React.memo(({ id, title, cb }) => (
+
+ {title}
+
+));
+
+const Main = () => {
+ const dispatch = useDispatch();
+ const run = useCallback(() => { dispatch({ type: "RUN" }); }, []);
+ const runLots = useCallback(() => { dispatch({ type: "RUN_LOTS" }); }, []);
+ const add = useCallback(() => { dispatch({ type: "ADD" }); }, []);
+ const update = useCallback(() => { dispatch({ type: "UPDATE" }); }, []);
+ const clear = useCallback(() => { dispatch({ type: "CLEAR" }); }, []);
+ const swapRows = useCallback(() => { dispatch({ type: "SWAP_ROWS" }); }, []);
+ return (
+
+
+
+
react-tracked useTrackedState
+
+
+
+
+
+
+ );
+};
+
+ReactDOM.render(
+ ,
+ document.getElementById("main")
+);
diff --git a/frameworks/keyed/react-tracked-useTrackedState/webpack.config.js b/frameworks/keyed/react-tracked-useTrackedState/webpack.config.js
new file mode 100755
index 000000000..f6607fcc0
--- /dev/null
+++ b/frameworks/keyed/react-tracked-useTrackedState/webpack.config.js
@@ -0,0 +1,78 @@
+const path = require('path');
+const webpack = require('webpack');
+const TerserPlugin = require('terser-webpack-plugin');
+
+module.exports = {
+ mode: 'production',
+ // mode: 'development',
+ entry: {
+ main: path.join(__dirname, 'src', 'main.jsx'),
+ },
+ output: {
+ path: path.join(__dirname, 'dist'),
+ filename: '[name].js'
+ },
+ resolve: {
+ extensions: ['.js', '.jsx']
+ },
+ module: {
+ rules: [{
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/preset-env', '@babel/preset-react'],
+ plugins: ['@babel/plugin-proposal-class-properties'],
+ }
+ }
+ ]
+ }]
+ },
+ optimization: {
+ minimizer: [
+ new TerserPlugin({
+ terserOptions: {
+ parse: {
+ // we want terser to parse ecma 8 code. However, we don't want it
+ // to apply any minfication steps that turns valid ecma 5 code
+ // into invalid ecma 5 code. This is why the 'compress' and 'output'
+ // sections only apply transformations that are ecma 5 safe
+ // https://github.com/facebook/create-react-app/pull/4234
+ ecma: 8,
+ },
+ compress: {
+ ecma: 5,
+ warnings: false,
+ // Disabled because of an issue with Uglify breaking seemingly valid code:
+ // https://github.com/facebook/create-react-app/issues/2376
+ // Pending further investigation:
+ // https://github.com/mishoo/UglifyJS2/issues/2011
+ comparisons: false,
+ },
+ mangle: {
+ safari10: true,
+ },
+ output: {
+ ecma: 5,
+ comments: false,
+ // Turned on because emoji and regex is not minified properly using default
+ // https://github.com/facebook/create-react-app/issues/2488
+ ascii_only: true,
+ },
+ },
+ // Use multi-process parallel running to improve the build speed
+ // Default number of concurrent runs: os.cpus().length - 1
+ parallel: true,
+ // Enable file caching
+ cache: true,
+ }),
+ ]
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': { NODE_ENV: JSON.stringify('production') }
+ }),
+ ],
+};
diff --git a/frameworks/keyed/reactive-react-redux-useSelector/index.html b/frameworks/keyed/reactive-react-redux-useSelector/index.html
new file mode 100755
index 000000000..c2ba7599a
--- /dev/null
+++ b/frameworks/keyed/reactive-react-redux-useSelector/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ reactive-react-redux
+
+
+
+
+
+
+
diff --git a/frameworks/keyed/reactive-react-redux-useSelector/package.json b/frameworks/keyed/reactive-react-redux-useSelector/package.json
new file mode 100644
index 000000000..1352de4ac
--- /dev/null
+++ b/frameworks/keyed/reactive-react-redux-useSelector/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "js-framework-benchmark-react",
+ "version": "1.1.1",
+ "description": "React demo",
+ "main": "index.js",
+ "js-framework-benchmark": {
+ "frameworkVersionFromPackage": "reactive-react-redux"
+ },
+ "scripts": {
+ "build-dev": "webpack --watch",
+ "build-prod": "webpack"
+ },
+ "keywords": [
+ "react",
+ "webpack"
+ ],
+ "author": "Stefan Krause",
+ "license": "Apache-2.0",
+ "homepage": "https://github.com/krausest/js-framework-benchmark",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/krausest/js-framework-benchmark.git"
+ },
+ "devDependencies": {
+ "@babel/core": "7.4.5",
+ "@babel/preset-env": "7.4.5",
+ "@babel/preset-react": "7.0.0",
+ "@babel/plugin-proposal-class-properties": "7.4.4",
+ "babel-loader": "8.0.6",
+ "terser-webpack-plugin": "1.3.0",
+ "webpack": "4.34.0",
+ "webpack-cli": "3.3.4"
+ },
+ "dependencies": {
+ "react": "16.8.6",
+ "react-dom": "16.8.6",
+ "reactive-react-redux": "4.5.0",
+ "redux": "4.0.1"
+ }
+}
diff --git a/frameworks/keyed/reactive-react-redux-useSelector/src/main.jsx b/frameworks/keyed/reactive-react-redux-useSelector/src/main.jsx
new file mode 100755
index 000000000..0a96807db
--- /dev/null
+++ b/frameworks/keyed/reactive-react-redux-useSelector/src/main.jsx
@@ -0,0 +1,124 @@
+import React, { useCallback } from "react";
+import ReactDOM from "react-dom";
+import { createStore } from "redux";
+import { Provider, useDispatch, useSelector } from "reactive-react-redux";
+
+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"];
+
+const random = (max) => Math.round(Math.random() * 1000) % max;
+
+let nextId = 1;
+function buildData(count) {
+ const data = new Array(count);
+ for (let i = 0; i < count; i++) {
+ data[i] = {
+ id: nextId++,
+ label: `${A[random(A.length)]} ${C[random(C.length)]} ${N[random(N.length)]}`,
+ }
+ }
+ return data;
+}
+
+const store = createStore((state = { data: [], selected: 0 }, action) => {
+ const { data, selected } = state;
+ switch (action.type) {
+ case "RUN":
+ return { data: buildData(1000), selected: 0 };
+ case "RUN_LOTS":
+ return { data: buildData(10000), selected: 0 };
+ case "ADD":
+ return { data: data.concat(buildData(1000)), selected };
+ case "UPDATE": {
+ const newData = data.slice();
+ for (let i = 0; i < newData.length; i += 10) {
+ const r = newData[i];
+ newData[i] = { id: r.id, label: r.label + " !!!" };
+ }
+ return { data: newData, selected };
+ }
+ case "REMOVE": {
+ const newData = data.slice();
+ newData.splice(data.indexOf(action.item), 1);
+ return { data: newData, selected };
+ }
+ case "SELECT":
+ return { data, selected: action.id };
+ case "CLEAR":
+ return { data: [], selected: 0 };
+ case "SWAP_ROWS": {
+ const newData = data.slice();
+ const tmp = newData[1];
+ newData[1] = newData[998];
+ newData[998] = tmp;
+ return { data: newData, selected };
+ }
+ }
+ return state;
+});
+
+const GlyphIcon = ;
+
+const Row = React.memo(({ data }) => {
+ const isSelected = useSelector((state) => state.selected === data.id);
+ const dispatch = useDispatch();
+ const select = useCallback(() => { dispatch({ type: "SELECT", id: data.id }); }, [data]);
+ const remove = useCallback(() => { dispatch({ type: "REMOVE", item: data }); }, [data]);
+ return (
+
+ | {data.id} |
+ {data.label} |
+ {GlyphIcon} |
+ |
+
+ )
+});
+
+const RowList = React.memo(() => {
+ const rows = useSelector((state) => state.data);
+ return rows.map((data) =>
);
+});
+
+const Button = React.memo(({ id, title, cb }) => (
+
+ {title}
+
+));
+
+const Main = () => {
+ const dispatch = useDispatch();
+ const run = useCallback(() => { dispatch({ type: "RUN" }); }, []);
+ const runLots = useCallback(() => { dispatch({ type: "RUN_LOTS" }); }, []);
+ const add = useCallback(() => { dispatch({ type: "ADD" }); }, []);
+ const update = useCallback(() => { dispatch({ type: "UPDATE" }); }, []);
+ const clear = useCallback(() => { dispatch({ type: "CLEAR" }); }, []);
+ const swapRows = useCallback(() => { dispatch({ type: "SWAP_ROWS" }); }, []);
+ return (
+
+
+
+
reactive-react-redux useSelector
+
+
+
+
+
+
+ );
+};
+
+ReactDOM.render(
+ ,
+ document.getElementById("main")
+);
diff --git a/frameworks/keyed/reactive-react-redux-useSelector/webpack.config.js b/frameworks/keyed/reactive-react-redux-useSelector/webpack.config.js
new file mode 100755
index 000000000..f6607fcc0
--- /dev/null
+++ b/frameworks/keyed/reactive-react-redux-useSelector/webpack.config.js
@@ -0,0 +1,78 @@
+const path = require('path');
+const webpack = require('webpack');
+const TerserPlugin = require('terser-webpack-plugin');
+
+module.exports = {
+ mode: 'production',
+ // mode: 'development',
+ entry: {
+ main: path.join(__dirname, 'src', 'main.jsx'),
+ },
+ output: {
+ path: path.join(__dirname, 'dist'),
+ filename: '[name].js'
+ },
+ resolve: {
+ extensions: ['.js', '.jsx']
+ },
+ module: {
+ rules: [{
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/preset-env', '@babel/preset-react'],
+ plugins: ['@babel/plugin-proposal-class-properties'],
+ }
+ }
+ ]
+ }]
+ },
+ optimization: {
+ minimizer: [
+ new TerserPlugin({
+ terserOptions: {
+ parse: {
+ // we want terser to parse ecma 8 code. However, we don't want it
+ // to apply any minfication steps that turns valid ecma 5 code
+ // into invalid ecma 5 code. This is why the 'compress' and 'output'
+ // sections only apply transformations that are ecma 5 safe
+ // https://github.com/facebook/create-react-app/pull/4234
+ ecma: 8,
+ },
+ compress: {
+ ecma: 5,
+ warnings: false,
+ // Disabled because of an issue with Uglify breaking seemingly valid code:
+ // https://github.com/facebook/create-react-app/issues/2376
+ // Pending further investigation:
+ // https://github.com/mishoo/UglifyJS2/issues/2011
+ comparisons: false,
+ },
+ mangle: {
+ safari10: true,
+ },
+ output: {
+ ecma: 5,
+ comments: false,
+ // Turned on because emoji and regex is not minified properly using default
+ // https://github.com/facebook/create-react-app/issues/2488
+ ascii_only: true,
+ },
+ },
+ // Use multi-process parallel running to improve the build speed
+ // Default number of concurrent runs: os.cpus().length - 1
+ parallel: true,
+ // Enable file caching
+ cache: true,
+ }),
+ ]
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': { NODE_ENV: JSON.stringify('production') }
+ }),
+ ],
+};
diff --git a/frameworks/keyed/reactive-react-redux-useTrackedState/index.html b/frameworks/keyed/reactive-react-redux-useTrackedState/index.html
new file mode 100755
index 000000000..c2ba7599a
--- /dev/null
+++ b/frameworks/keyed/reactive-react-redux-useTrackedState/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+ reactive-react-redux
+
+
+
+
+
+
+
diff --git a/frameworks/keyed/reactive-react-redux-useTrackedState/package.json b/frameworks/keyed/reactive-react-redux-useTrackedState/package.json
new file mode 100644
index 000000000..1352de4ac
--- /dev/null
+++ b/frameworks/keyed/reactive-react-redux-useTrackedState/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "js-framework-benchmark-react",
+ "version": "1.1.1",
+ "description": "React demo",
+ "main": "index.js",
+ "js-framework-benchmark": {
+ "frameworkVersionFromPackage": "reactive-react-redux"
+ },
+ "scripts": {
+ "build-dev": "webpack --watch",
+ "build-prod": "webpack"
+ },
+ "keywords": [
+ "react",
+ "webpack"
+ ],
+ "author": "Stefan Krause",
+ "license": "Apache-2.0",
+ "homepage": "https://github.com/krausest/js-framework-benchmark",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/krausest/js-framework-benchmark.git"
+ },
+ "devDependencies": {
+ "@babel/core": "7.4.5",
+ "@babel/preset-env": "7.4.5",
+ "@babel/preset-react": "7.0.0",
+ "@babel/plugin-proposal-class-properties": "7.4.4",
+ "babel-loader": "8.0.6",
+ "terser-webpack-plugin": "1.3.0",
+ "webpack": "4.34.0",
+ "webpack-cli": "3.3.4"
+ },
+ "dependencies": {
+ "react": "16.8.6",
+ "react-dom": "16.8.6",
+ "reactive-react-redux": "4.5.0",
+ "redux": "4.0.1"
+ }
+}
diff --git a/frameworks/keyed/reactive-react-redux-useTrackedState/src/main.jsx b/frameworks/keyed/reactive-react-redux-useTrackedState/src/main.jsx
new file mode 100755
index 000000000..6fc70ab25
--- /dev/null
+++ b/frameworks/keyed/reactive-react-redux-useTrackedState/src/main.jsx
@@ -0,0 +1,124 @@
+import React, { useCallback } from "react";
+import ReactDOM from "react-dom";
+import { createStore } from "redux";
+import { Provider, useDispatch, useTrackedState } from "reactive-react-redux";
+
+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"];
+
+const random = (max) => Math.round(Math.random() * 1000) % max;
+
+let nextId = 1;
+function buildData(count) {
+ const data = new Array(count);
+ for (let i = 0; i < count; i++) {
+ data[i] = {
+ id: nextId++,
+ label: `${A[random(A.length)]} ${C[random(C.length)]} ${N[random(N.length)]}`,
+ }
+ }
+ return data;
+}
+
+const store = createStore((state = { data: [], selected: 0 }, action) => {
+ const { data, selected } = state;
+ switch (action.type) {
+ case "RUN":
+ return { data: buildData(1000), selected: 0 };
+ case "RUN_LOTS":
+ return { data: buildData(10000), selected: 0 };
+ case "ADD":
+ return { data: data.concat(buildData(1000)), selected };
+ case "UPDATE": {
+ const newData = data.slice();
+ for (let i = 0; i < newData.length; i += 10) {
+ const r = newData[i];
+ newData[i] = { id: r.id, label: r.label + " !!!" };
+ }
+ return { data: newData, selected };
+ }
+ case "REMOVE": {
+ const idx = data.findIndex(d => d.id === action.id);
+ const newData = data.slice();
+ newData.splice(idx, 1);
+ return { data: newData, selected };
+ }
+ case "SELECT":
+ return { data, selected: action.id };
+ case "CLEAR":
+ return { data: [], selected: 0 };
+ case "SWAP_ROWS": {
+ const newData = data.slice();
+ const tmp = newData[1];
+ newData[1] = newData[998];
+ newData[998] = tmp;
+ return { data: newData, selected };
+ }
+ }
+ return state;
+});
+
+const GlyphIcon = ;
+
+const Row = React.memo(({ data, selected }) => {
+ const dispatch = useDispatch();
+ const select = useCallback(() => { dispatch({ type: "SELECT", id: data.id }); }, [data]);
+ const remove = useCallback(() => { dispatch({ type: "REMOVE", id: data.id }); }, [data]);
+ return (
+
+ | {data.id} |
+ {data.label} |
+ {GlyphIcon} |
+ |
+
+ )
+});
+
+const RowList = React.memo(() => {
+ const state = useTrackedState();
+ return state.data.map((data) =>
);
+});
+
+const Button = React.memo(({ id, title, cb }) => (
+
+ {title}
+
+));
+
+const Main = () => {
+ const dispatch = useDispatch();
+ const run = useCallback(() => { dispatch({ type: "RUN" }); }, []);
+ const runLots = useCallback(() => { dispatch({ type: "RUN_LOTS" }); }, []);
+ const add = useCallback(() => { dispatch({ type: "ADD" }); }, []);
+ const update = useCallback(() => { dispatch({ type: "UPDATE" }); }, []);
+ const clear = useCallback(() => { dispatch({ type: "CLEAR" }); }, []);
+ const swapRows = useCallback(() => { dispatch({ type: "SWAP_ROWS" }); }, []);
+ return (
+
+
+
+
reactive-react-redux useTrackedState
+
+
+
+
+
+
+ );
+};
+
+ReactDOM.render(
+ ,
+ document.getElementById("main")
+);
diff --git a/frameworks/keyed/reactive-react-redux-useTrackedState/webpack.config.js b/frameworks/keyed/reactive-react-redux-useTrackedState/webpack.config.js
new file mode 100755
index 000000000..f6607fcc0
--- /dev/null
+++ b/frameworks/keyed/reactive-react-redux-useTrackedState/webpack.config.js
@@ -0,0 +1,78 @@
+const path = require('path');
+const webpack = require('webpack');
+const TerserPlugin = require('terser-webpack-plugin');
+
+module.exports = {
+ mode: 'production',
+ // mode: 'development',
+ entry: {
+ main: path.join(__dirname, 'src', 'main.jsx'),
+ },
+ output: {
+ path: path.join(__dirname, 'dist'),
+ filename: '[name].js'
+ },
+ resolve: {
+ extensions: ['.js', '.jsx']
+ },
+ module: {
+ rules: [{
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/preset-env', '@babel/preset-react'],
+ plugins: ['@babel/plugin-proposal-class-properties'],
+ }
+ }
+ ]
+ }]
+ },
+ optimization: {
+ minimizer: [
+ new TerserPlugin({
+ terserOptions: {
+ parse: {
+ // we want terser to parse ecma 8 code. However, we don't want it
+ // to apply any minfication steps that turns valid ecma 5 code
+ // into invalid ecma 5 code. This is why the 'compress' and 'output'
+ // sections only apply transformations that are ecma 5 safe
+ // https://github.com/facebook/create-react-app/pull/4234
+ ecma: 8,
+ },
+ compress: {
+ ecma: 5,
+ warnings: false,
+ // Disabled because of an issue with Uglify breaking seemingly valid code:
+ // https://github.com/facebook/create-react-app/issues/2376
+ // Pending further investigation:
+ // https://github.com/mishoo/UglifyJS2/issues/2011
+ comparisons: false,
+ },
+ mangle: {
+ safari10: true,
+ },
+ output: {
+ ecma: 5,
+ comments: false,
+ // Turned on because emoji and regex is not minified properly using default
+ // https://github.com/facebook/create-react-app/issues/2488
+ ascii_only: true,
+ },
+ },
+ // Use multi-process parallel running to improve the build speed
+ // Default number of concurrent runs: os.cpus().length - 1
+ parallel: true,
+ // Enable file caching
+ cache: true,
+ }),
+ ]
+ },
+ plugins: [
+ new webpack.DefinePlugin({
+ 'process.env': { NODE_ENV: JSON.stringify('production') }
+ }),
+ ],
+};