diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 62c521a7..580d0e0b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -13,5 +13,6 @@ https://github.com/necolas/react-native-web/CONTRIBUTING.md - [ ] includes documentation - [ ] includes tests +- [ ] includes benchmark reports - [ ] includes an interactive example - [ ] includes screenshots/videos diff --git a/.gitignore b/.gitignore index a2af7f53..0a7ae798 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /dist /dist-examples +/dist-performance /node_modules diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 474462e1..44531211 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,5 @@ # Contributing -We are open to, and grateful for, any contributions made by the community. - ## Reporting Issues and Asking Questions Before opening an issue, please search the [issue @@ -31,6 +29,12 @@ Run the examples: npm run examples ``` +Run the benchmarks in a browser by opening `./performance/index.html` after running: + +``` +npm run build:performance +``` + ### Building ``` diff --git a/package.json b/package.json index edfe1d6b..cfb19d26 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,11 @@ "scripts": { "build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__", "build:examples": "build-storybook -o dist-examples -c ./examples/.storybook", + "build:performance": "cd performance && webpack", "build:umd": "webpack --config webpack.config.js --sort-assets-by --progress", "deploy:examples": "git checkout gh-pages && rm -rf ./storybook && mv dist-examples storybook && git add -A && git commit -m \"Storybook deploy\" && git push origin gh-pages && git checkout -", "examples": "start-storybook -p 9001 -c ./examples/.storybook --dont-track", - "lint": "eslint src", + "lint": "eslint performance src", "prepublish": "npm run build && npm run build:umd", "test": "npm run lint && npm run test:jest", "test:jest": "jest", @@ -48,6 +49,7 @@ "eslint-plugin-react": "^6.1.2", "file-loader": "^0.9.0", "jest": "^16.0.2", + "marky": "^1.1.1", "node-libs-browser": "^0.5.3", "react": "~15.4.1", "react-addons-test-utils": "~15.4.1", diff --git a/performance/benchmark.js b/performance/benchmark.js new file mode 100644 index 00000000..aa4c9066 --- /dev/null +++ b/performance/benchmark.js @@ -0,0 +1,65 @@ +import * as marky from 'marky'; + +const fmt = (time) => `${Math.round(time * 100) / 100}ms`; + +const measure = (name, fn) => { + marky.mark(name); + fn(); + const performanceMeasure = marky.stop(name); + return performanceMeasure; +}; + +const benchmark = ({ name, description, setup, teardown, task, runs }) => { + return new Promise((resolve) => { + const performanceMeasures = []; + let i = 0; + + setup(); + const first = measure('first', task); + teardown(); + + const done = () => { + const mean = performanceMeasures.reduce((sum, performanceMeasure) => { + return sum + performanceMeasure.duration; + }, 0) / runs; + + const firstDuration = fmt(first.duration); + const meanDuration = fmt(mean); + + console.log(`First: ${firstDuration}`); + console.log(`Mean: ${meanDuration}`); + console.groupEnd(); + resolve(mean); + }; + + const a = () => { + setup(); + window.requestAnimationFrame(b); + }; + + const b = () => { + performanceMeasures.push(measure('mean', task)); + window.requestAnimationFrame(c); + }; + + const c = () => { + teardown(); + window.requestAnimationFrame(d); + }; + + const d = () => { + i += 1; + if (i < runs) { + window.requestAnimationFrame(a); + } else { + window.requestAnimationFrame(done); + } + }; + + console.group(); + console.log(`[benchmark] ${name}: ${description}`); + window.requestAnimationFrame(a); + }); +}; + +module.exports = benchmark; diff --git a/performance/benchmarks/deepTree/createDeepTree.js b/performance/benchmarks/deepTree/createDeepTree.js new file mode 100644 index 00000000..8a494df9 --- /dev/null +++ b/performance/benchmarks/deepTree/createDeepTree.js @@ -0,0 +1,85 @@ +import React, { Component, PropTypes } from 'react'; + +const createDeepTree = ({ StyleSheet, View }) => { + class DeepTree extends Component { + static propTypes = { + breadth: PropTypes.number.isRequired, + depth: PropTypes.number.isRequired, + id: PropTypes.number.isRequired, + wrap: PropTypes.number.isRequired + }; + + render() { + const { breadth, depth, id, wrap } = this.props; + let result = ( + + {depth === 0 && ( + + )} + {depth !== 0 && Array.from({ length: breadth }).map((el, i) => ( + + ))} + + ); + for (let i = 0; i < wrap; i++) { + result = {result}; + } + return result; + } + } + + const styles = StyleSheet.create({ + outer: { + padding: 4 + }, + odd: { + flexDirection: 'row' + }, + even: { + flexDirection: 'column' + }, + custom0: { + backgroundColor: '#222' + }, + custom1: { + backgroundColor: '#666' + }, + custom2: { + backgroundColor: '#999' + }, + terminal: { + width: 20, + height: 20 + }, + terminal0: { + backgroundColor: 'blue' + }, + terminal1: { + backgroundColor: 'orange' + }, + terminal2: { + backgroundColor: 'red' + } + }); + + return DeepTree; +}; + +module.exports = createDeepTree; diff --git a/performance/benchmarks/deepTree/index.js b/performance/benchmarks/deepTree/index.js new file mode 100644 index 00000000..7f851070 --- /dev/null +++ b/performance/benchmarks/deepTree/index.js @@ -0,0 +1,24 @@ +import benchmark from '../../benchmark'; +import createDeepTree from './createDeepTree'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import ReactNative from 'react-native'; + +// React Native for Web implementation of the tree +const DeepTree = createDeepTree(ReactNative); + +const deepTreeBenchmark = ({ breadth, depth, wrap, runs }, node) => () => { + const setup = () => { }; + const teardown = () => ReactDOM.unmountComponentAtNode(node); + + return benchmark({ + name: 'DeepTree', + description: `depth=${depth}, breadth=${breadth}, wrap=${wrap})`, + runs, + setup, + teardown, + task: () => ReactDOM.render(, node) + }); +}; + +module.exports = deepTreeBenchmark; diff --git a/performance/index.html b/performance/index.html new file mode 100644 index 00000000..0e3fcfc3 --- /dev/null +++ b/performance/index.html @@ -0,0 +1,11 @@ + + + + + Title + + +
+ + + diff --git a/performance/index.js b/performance/index.js new file mode 100644 index 00000000..c05f6c07 --- /dev/null +++ b/performance/index.js @@ -0,0 +1,11 @@ +import deepTree from './benchmarks/deepTree'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +const node = document.querySelector('.root'); + +Promise.resolve() + .then(deepTree({ wrap: 4, depth: 3, breadth: 10, runs: 10 }, node)) + .then(deepTree({ wrap: 1, depth: 5, breadth: 3, runs: 20 }, node)) + .then(() => ReactDOM.render(
Complete
, node)); + diff --git a/performance/webpack.config.js b/performance/webpack.config.js new file mode 100644 index 00000000..0adaab4a --- /dev/null +++ b/performance/webpack.config.js @@ -0,0 +1,45 @@ +const path = require('path'); +const webpack = require('webpack'); +// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; + +module.exports = { + entry: { + performance: './index' + }, + output: { + path: path.resolve(__dirname, '../dist-performance'), + filename: 'performance.bundle.js' + }, + module: { + loaders: [ + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader', + query: { cacheDirectory: true } + } + ] + }, + plugins: [ + new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }), + new webpack.optimize.DedupePlugin(), + // https://github.com/animatedjs/animated/issues/40 + new webpack.NormalModuleReplacementPlugin( + /es6-set/, + path.join(__dirname, '../src/modules/polyfills/Set.js') + ), + new webpack.optimize.OccurenceOrderPlugin(), + new webpack.optimize.UglifyJsPlugin({ + compress: { + dead_code: true, + screw_ie8: true, + warnings: true + } + }) + ], + resolve: { + alias: { + 'react-native': path.join(__dirname, '../src') + } + } +}; diff --git a/yarn.lock b/yarn.lock index 43083255..dae328f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3738,6 +3738,10 @@ marked@^0.3.6: version "0.3.6" resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7" +marky@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/marky/-/marky-1.1.1.tgz#dcd1eba3e2c6412619a76981f635b3698a8761e8" + math-expression-evaluator@^1.2.14: version "1.2.14" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.14.tgz#39511771ed9602405fba9affff17eb4d2a3843ab"