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 }) => ( +
+ +
+)); + +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 + Redux

+
+
+
+
+
+ +
+ ); +}; + +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 }) => ( +
+ +
+)); + +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 }) => ( +
+ +
+)); + +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 }) => ( +
+ +
+)); + +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 }) => ( +
+ +
+)); + +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 }) => ( +
+ +
+)); + +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') } + }), + ], +};