From ec31f087039c177c1bd9af53137c541769e8c09d Mon Sep 17 00:00:00 2001 From: Sergey Sova Date: Fri, 24 Apr 2020 14:47:59 +0300 Subject: [PATCH] feat: add effector-react --- frameworks/keyed/effector-react/index.html | 12 + frameworks/keyed/effector-react/package.json | 40 +++ frameworks/keyed/effector-react/src/main.jsx | 244 ++++++++++++++++++ .../keyed/effector-react/webpack.config.js | 83 ++++++ 4 files changed, 379 insertions(+) create mode 100755 frameworks/keyed/effector-react/index.html create mode 100644 frameworks/keyed/effector-react/package.json create mode 100755 frameworks/keyed/effector-react/src/main.jsx create mode 100755 frameworks/keyed/effector-react/webpack.config.js diff --git a/frameworks/keyed/effector-react/index.html b/frameworks/keyed/effector-react/index.html new file mode 100755 index 000000000..567a5b245 --- /dev/null +++ b/frameworks/keyed/effector-react/index.html @@ -0,0 +1,12 @@ + + + + + React + + + +
+ + + diff --git a/frameworks/keyed/effector-react/package.json b/frameworks/keyed/effector-react/package.json new file mode 100644 index 000000000..ef18764ac --- /dev/null +++ b/frameworks/keyed/effector-react/package.json @@ -0,0 +1,40 @@ +{ + "name": "js-framework-benchmark-effector-react", + "version": "0.1.0", + "description": "Effector-React demo", + "main": "index.js", + "js-framework-benchmark": { + "frameworkVersionFromPackage": "effector-react" + }, + "scripts": { + "build-dev": "webpack --watch", + "build-prod": "webpack" + }, + "keywords": [ + "react", + "webpack" + ], + "author": "Sergey Sova", + "license": "MIT", + "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": { + "effector": "^20.14.0", + "effector-react": "^20.7.1", + "react": "16.8.6", + "react-dom": "16.8.6" + } +} diff --git a/frameworks/keyed/effector-react/src/main.jsx b/frameworks/keyed/effector-react/src/main.jsx new file mode 100755 index 000000000..68b4e84ea --- /dev/null +++ b/frameworks/keyed/effector-react/src/main.jsx @@ -0,0 +1,244 @@ +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import { createStore, createEvent, sample } from "effector"; +import { useList, useStoreMap } from "effector-react"; + +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 GlyphIcon = ( + +); + +class Row extends React.Component { + onSelect = () => { + this.props.select(this.props.item); + }; + + onRemove = () => { + this.props.remove(this.props.item); + }; + + shouldComponentUpdate(nextProps) { + return ( + nextProps.item !== this.props.item || + nextProps.selected !== this.props.selected + ); + } + + render() { + let { selected, item } = this.props; + return ( + + {item.id} + + {item.label} + + + {GlyphIcon} + + + + ); + } +} + +function Button({ id, cb, title }) { + return ( +
+ +
+ ); +} + +class Jumbotron extends React.Component { + shouldComponentUpdate() { + return false; + } + + render() { + const { run, runLots, add, update, clear, swapRows } = this.props; + return ( +
+
+
+

React keyed

+
+
+
+
+
+
+
+ ); + } +} + +const $data = createStore([]); +const $selected = createStore(0); + +const run = createEvent(); +const runLots = createEvent(); +const add = createEvent(); +const update = createEvent(); +const select = createEvent(); // item +const remove = createEvent(); // item +const clear = createEvent(); +const swapRows = createEvent(); + +$data + .on(run, () => buildData(1000)) + .on(runLots, () => buildData(10000)) + .on(add, (list) => list.concat(buildData(1000))) + .on(update, (list) => { + const data = list.concat([]); // to change ref to arrays + for (let i = 0; i < data.length; i += 10) { + const item = data[i]; + data[i] = { id: item.id, label: item.label + " !!!" }; + } + return data; + }) + .on(remove, (list, item) => { + const data = list.concat([]); // to change ref + data.splice(data.indexOf(item), 1); + return data; + }) + .on(clear, () => []) + .on(swapRows, (list) => { + const data = list.concat([]); // to change ref + if (data.length > 998) { + let temp = data[1]; + data[1] = data[998]; + data[998] = temp; + } + return data; + }); + +// @ts-ignore +$selected.on(select, (_, item) => item.id).on(clear, () => 0); + +function Main() { + return ( +
+ + + + {useList($data, (item) => { + const isSelected = useStoreMap({ + store: $selected, + keys: [item.id], + fn: (selected, [id]) => selected === id, + }); + return ( + + ); + })} + +
+ +
+ ); +} + +ReactDOM.render(
, document.getElementById("main")); diff --git a/frameworks/keyed/effector-react/webpack.config.js b/frameworks/keyed/effector-react/webpack.config.js new file mode 100755 index 000000000..216f7afb7 --- /dev/null +++ b/frameworks/keyed/effector-react/webpack.config.js @@ -0,0 +1,83 @@ +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", + "effector/babel-plugin", + ], + }, + }, + ], + }, + ], + }, + 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") }, + }), + ], +};