Reorganize and add to benchmarks

Rearrange the benchmark code so that each implementation is
self-contained. Adds the SierpinskiTriangle case that 'emotion'
introduced in their fork of the 'react-native-web' benchmarks. And make
it possible to run benchmarks on a per-library basis.
This commit is contained in:
Nicolas Gallagher
2017-12-27 19:31:15 +00:00
parent f6d1caab9d
commit 86263a2fa0
80 changed files with 847 additions and 227 deletions

View File

@@ -6,41 +6,50 @@ To run these benchmarks:
yarn benchmark
```
Append `?fastest` to the URL to include the fastest "other libraries", and
`?all` to include all the "other libraries".
To run benchmarks for individual implementations append `?<name>,<name>` to the
URL, e.g., `?css-modules,react-native-web`.
## Notes
These benchmarks are crude approximations of extreme cases that libraries may
encounter. The deep and wide tree cases look at the performance of mounting and
rendering large trees of styled elements. The Triangle cases looks at the
performance of repeated style updates to a large mounted tree. Some libraries
must inject new styles for each "dynamic style", whereas others may not.
Libraries without support for dynamic styles (i.e., they rely on user-authored
inline styles) do not include the `SierpinskiTriangle` benchmark.
The components used in the render benchmarks are simple enough to be
implemented by multiple UI or style libraries. The implementations are not
equivalent in functionality. For example, the "React Native for Web" benchmark includes a
complete `View` implementation and the `StyleSheet` also converts React Native
styles to DOM styles, has deterministic resolution, and supports RTL layout.
implemented by multiple UI or style libraries. The benchmark implementations
and the features of the style libraries are _only approximately equivalent in
functionality_.
## Results
Typical render timings*: mean ± two standard deviations.
| Implementation | Deep tree (ms) | Wide tree (ms) | Tweets (ms) |
| Implementation | Deep tree (ms) | Wide tree (ms) | Triangle (ms) |
| :--- | ---: | ---: | ---: |
| `css-modules` | `80.47` `±25.13` | `144.87` 32.70` | |
| `react-native-web@0.2.2` | `88.68` `±28.78` | `178.17` 39.90` | `13.78` 2.90ms` |
| `react-native-web@0.2.2` | `89.67` `±28.51` | `167.46` 27.03` | `65.40` `±19.50` |
| `css-modules` | `77.42` 45.50` | `141.44` 33.96` | - |
| `inline-styles` | `236.25` `±95.57` | `477.01` `±88.30` | `40.95` `±23.53` |
Other libraries
| Implementation | Deep tree (ms) | Wide tree (ms) |
| Implementation | Deep tree (ms) | Wide tree (ms) | Triangle (ms) |
| :--- | ---: | ---: |
| `styletron@3.0.0-rc.5` | `79.41` 27.49` | `152.95` `±29.46` |
| `aphrodite@1.2.5` | `85.13` 25.39` | `162.87` 25.91` |
| `glamor@2.20.40` | `109.92` 29.88` | `193.01` 32.03` |
| `react-jss@8.2.0` | `134.28` `±49.00` | `278.78` 50.39` |
| `emotion@8.0.12` | `139.08` 46.18` | `253.45` 52.69` |
| `styled-components@2.3.2` | `194.43` 46.28` | `404.86` 56.59` |
| `reactxp@0.46.6` | `219.46` 57.24` | `424.18` 76.10` |
| `radium@0.19.6` | `359.32` `±90.27` | `795.91` 88.93` |
| `styletron@3.0.0-rc.5` | `83.53` 33.55` | `153.12` `±39.13` | `56.47` `±24.22` |
| `aphrodite@1.2.5` | `88.23` 31.22` | `164.03` 34.70` | - |
| `glamor@2.20.40` | `110.09` 34.20` | `182.06` 50.39` | ‡ |
| `emotion@8.0.12` | `103.44` `±32.12` | `204.45` `±41.00` | `110.28` 26.94` |
| `react-jss@8.2.0` | `136.17` 59.23` | `270.51` 69.20` | - |
| `styled-components@2.3.2` | `217.57` 51.90` | `437.57` 65.74` | `76.99` `±41.79` |
| `reactxp@0.46.6` | `240.88` `±79.82` | `467.32` 74.42` | `70.95` 32.90`|
| `radium@0.19.6` | `400.19` `±94.58` | `816.59` `±91.10` | `71.13` 27.22` |
These results indicate that render times when using `react-native-web`,
`css-modules`, `aphrodite`, and `styletron` are roughly equivalent and
significantly faster than alternatives.
*MacBook Pro (13-inch, Early 2011); 2.3 GHz Intel Core i5; 8 GB 1333 MHz DDR3. Google Chrome 62.
‡Glamor essentially crashes

View File

@@ -1,63 +0,0 @@
import aphrodite from './src/aphrodite';
import cssModules from './src/css-modules';
import emotion from './src/emotion';
import glamor from './src/glamor';
import jss from './src/jss';
import radium from './src/radium';
import reactNative from './src/react-native';
import styledComponents from './src/styled-components';
import styletron from './src/styletron';
import xp from './src/reactxp';
import renderDeepTree from './tests/renderDeepTree';
import renderTweet from './tests/renderTweet';
import renderWideTree from './tests/renderWideTree';
const testAll = window.location.search === '?all';
const testFastest = window.location.search === '?fastest';
const coreTests = [
() => renderTweet('react-native-web', reactNative),
() => renderDeepTree('css-modules', cssModules),
() => renderWideTree('css-modules', cssModules),
() => renderDeepTree('react-native-web', reactNative),
() => renderWideTree('react-native-web', reactNative)
];
const fastestTests = [
() => renderDeepTree('styletron', styletron),
() => renderWideTree('styletron', styletron),
() => renderDeepTree('aphrodite', aphrodite),
() => renderWideTree('aphrodite', aphrodite)
];
/**
* Optionally run tests using other libraries
*/
const restTests = [
() => renderDeepTree('glamor', glamor),
() => renderWideTree('glamor', glamor),
() => renderDeepTree('react-jss', jss),
() => renderWideTree('react-jss', jss),
() => renderDeepTree('emotion', emotion),
() => renderWideTree('emotion', emotion),
() => renderDeepTree('styled-components', styledComponents),
() => renderWideTree('styled-components', styledComponents),
() => renderDeepTree('reactxp', xp),
() => renderWideTree('reactxp', xp),
() => renderDeepTree('radium', radium),
() => renderWideTree('radium', radium)
];
const tests = [...coreTests];
if (testFastest) {
tests.push(...fastestTests);
}
if (testAll) {
tests.push(...fastestTests);
tests.push(...restTests);
}
// run benchmarks
tests.reduce((promise, test) => promise.then(test()), Promise.resolve());

View File

@@ -7,7 +7,9 @@
},
"dependencies": {
"aphrodite": "^1.2.5",
"babel-polyfill": "^6.26.0",
"classnames": "^2.2.5",
"d3-scale-chromatic": "^1.1.1",
"emotion": "^8.0.12",
"glamor": "^2.20.40",
"marky": "^1.2.0",

View File

@@ -1,7 +0,0 @@
import Box from './components/Box/aphrodite';
import View from './components/View/aphrodite';
export default {
Box,
View
};

View File

@@ -38,30 +38,31 @@ const standardDeviation = values => {
return Math.sqrt(meanSquareDiff);
};
export const log = (name, description, durations) => {
const stdDev = standardDeviation(durations);
const formattedMean = fmt(mean(durations));
const formattedMedian = fmt(median(durations));
const formattedStdDev = fmt(stdDev);
console.groupCollapsed(`${name}\n${formattedMean} ±${fmt(2 * stdDev)}`);
description && console.log(description);
console.log(`Median: ${formattedMedian}`);
console.log(`Mean: ${formattedMean}`);
console.log(`Standard deviation: ${formattedStdDev}`);
console.log(durations);
console.groupEnd();
};
const benchmark = ({ name, description, setup, teardown, task, runs }) => {
return new Promise(resolve => {
const durations = [];
let i = 0;
setup();
const first = measure('first', task);
teardown();
const done = () => {
const stdDev = standardDeviation(durations);
const formattedFirst = fmt(first);
const formattedMean = fmt(mean(durations));
const formattedMedian = fmt(median(durations));
const formattedStdDev = fmt(stdDev);
console.groupCollapsed(`${name}\n${formattedMean} ±${fmt(2 * stdDev)}`);
description && console.log(description);
console.log(`First: ${formattedFirst}`);
console.log(`Median: ${formattedMedian}`);
console.log(`Mean: ${formattedMean}`);
console.log(`Standard deviation: ${formattedStdDev}`);
console.log(durations);
console.groupEnd();
log(name, description, durations);
resolve();
};

View File

@@ -0,0 +1,84 @@
import PropTypes from 'prop-types';
import React from 'react';
import { interpolatePurples, interpolateBuPu, interpolateRdPu } from 'd3-scale-chromatic';
const targetSize = 25;
class SierpinskiTriangle extends React.Component {
static propTypes = {
Dot: PropTypes.node,
depth: PropTypes.number,
renderCount: PropTypes.number,
s: PropTypes.number,
x: PropTypes.number,
y: PropTypes.number
};
static defaultProps = {
depth: 0,
renderCount: 0
};
render() {
const { x, y, depth, renderCount, Dot } = this.props;
let { s } = this.props;
if (s <= targetSize) {
let fn;
switch (depth) {
case 1:
fn = interpolatePurples;
break;
case 2:
fn = interpolateBuPu;
break;
case 3:
default:
fn = interpolateRdPu;
}
return (
<Dot
color={fn(renderCount / 20)}
size={targetSize}
x={x - targetSize / 2}
y={y - targetSize / 2}
/>
);
}
s /= 2;
return [
<SierpinskiTriangle
Dot={Dot}
depth={1}
key={1}
renderCount={renderCount}
s={s}
x={x}
y={y - s / 2}
/>,
<SierpinskiTriangle
Dot={Dot}
depth={2}
key={2}
renderCount={renderCount}
s={s}
x={x - s}
y={y + s / 2}
/>,
<SierpinskiTriangle
Dot={Dot}
depth={3}
key={3}
renderCount={renderCount}
s={s}
x={x + s}
y={y + s / 2}
/>
];
}
}
export default SierpinskiTriangle;

View File

@@ -1,10 +1,10 @@
import createRenderBenchmark from '../createRenderBenchmark';
import NestedTree from '../src/components/NestedTree';
import NestedTree from './NestedTree';
import React from 'react';
const renderDeepTree = (label, components) =>
createRenderBenchmark({
name: `Deep tree [${label}]`,
name: `[${label}] Deep tree`,
runs: 20,
getElement() {
return <NestedTree breadth={3} components={components} depth={6} id={0} wrap={1} />;

View File

@@ -0,0 +1,112 @@
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import SierpinskiTriangle from './SierpinskiTriangle';
import { log } from '../benchmark';
const node = document.querySelector('.root');
let runs = 20;
class Speedometer extends React.Component {
/* necessary for reactxp to work without errors */
static childContextTypes = {
focusManager: PropTypes.object
};
getChildContext() {
return {
focusManager: {
addFocusableComponent() {},
removeFocusableComponent() {},
restrictFocusWithin() {},
removeFocusRestriction() {},
limitFocusWithin() {},
removeFocusLimitation() {}
}
};
}
static propTypes = {
Dot: PropTypes.node.isRequired,
description: PropTypes.string,
name: PropTypes.string.isRequired,
onComplete: PropTypes.node.isRequired
};
state = { renderCount: -1 };
async componentDidMount() {
const durations = [];
while ((runs -= 1)) {
const prev = window.performance.now();
await new Promise(resolve => {
this.raf = window.requestAnimationFrame(() => {
this.setState({ renderCount: this.state.renderCount + 1 }, () => {
const now = window.performance.now();
durations.push(now - prev);
resolve();
});
});
});
}
const { description, name } = this.props;
log(name, description, durations);
runs = 20;
this.props.onComplete();
}
componentWillUnmount() {
window.cancelAnimationFrame(this.raf);
}
render() {
return (
<div style={styles.wrapper}>
<SierpinskiTriangle
Dot={this.props.Dot}
renderCount={this.state.renderCount}
s={1000}
x={0}
y={0}
/>
</div>
);
}
}
const styles = {
wrapper: {
position: 'absolute',
transformOrigin: '0 0',
left: '50%',
top: '50%',
width: '10px',
height: '10px',
backgroundColor: '#eee',
transform: 'scale(0.33)'
}
};
const renderSierpinskiTriangle = (name, { Dot }) => () => {
return new Promise(resolve => {
/* eslint-disable react/jsx-no-bind */
ReactDOM.render(
<Speedometer
Dot={Dot}
description="Dynamic styles"
name={`[${name}] Triangle`}
onComplete={() => {
ReactDOM.unmountComponentAtNode(node);
resolve();
}}
/>,
node
);
/* eslint-enable react/jsx-no-bind */
});
};
export default renderSierpinskiTriangle;

View File

@@ -1,5 +1,4 @@
import createRenderBenchmark from '../createRenderBenchmark';
import Tweet from '../src/components/Tweet';
import React from 'react';
const tweet1 = {
@@ -96,9 +95,9 @@ const tweet2 = {
}
};
const renderTweet = label =>
const renderTweet = (label, { Tweet }) =>
createRenderBenchmark({
name: `Tweet [${label}]`,
name: `[${label}] Tweet`,
runs: 10,
getElement() {
return (

View File

@@ -1,10 +1,10 @@
import createRenderBenchmark from '../createRenderBenchmark';
import NestedTree from '../src/components/NestedTree';
import NestedTree from './NestedTree';
import React from 'react';
const renderWideTree = (label, components) =>
createRenderBenchmark({
name: `Wide tree [${label}]`,
name: `[${label}] Wide tree`,
runs: 20,
getElement() {
return <NestedTree breadth={10} components={components} depth={3} id={0} wrap={4} />;

View File

@@ -1,9 +0,0 @@
import Box from './components/Box/css-modules';
import View from './components/View/css-modules';
const api = {
Box,
View
};
export default api;

View File

@@ -1,7 +0,0 @@
import Box from './components/Box/emotion';
import View from './components/View/emotion';
export default {
Box,
View
};

View File

@@ -1,7 +0,0 @@
import Box from './components/Box/glamor';
import View from './components/View/glamor';
export default {
Box,
View
};

View File

@@ -1,6 +1,6 @@
/* eslint-disable react/prop-types */
import React from 'react';
import View from '../View/aphrodite';
import View from './View';
import { StyleSheet } from 'aphrodite';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (

View File

@@ -0,0 +1,7 @@
import Box from './Box';
import View from './View';
export default {
Box,
View
};

View File

@@ -1,8 +1,8 @@
/* eslint-disable react/prop-types */
import classnames from 'classnames';
import React from 'react';
import View from '../View/css-modules';
import styles from './styles.css';
import View from './View';
import styles from './box-styles.css';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
<View

View File

@@ -1,7 +1,7 @@
/* eslint-disable react/prop-types */
import classnames from 'classnames';
import React from 'react';
import styles from './styles.css';
import styles from './view-styles.css';
class View extends React.Component {
render() {

View File

@@ -0,0 +1,7 @@
import Box from './Box';
import View from './View';
export default {
Box,
View
};

View File

@@ -1,6 +1,6 @@
/* eslint-disable react/prop-types */
import React from 'react';
import View from '../View/glamor';
import View from './View';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
<View

View File

@@ -0,0 +1,32 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { css } from 'emotion';
const Dot = ({ size, x, y, children, color }) => (
<div
className={css(styles.root, {
borderBottomColor: color,
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
left: `${x}px`,
top: `${y}px`
})}
>
{children}
</div>
);
const styles = {
root: {
position: 'absolute',
cursor: 'pointer',
width: 0,
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0
}
};
export default Dot;

View File

@@ -5,7 +5,7 @@ import React from 'react';
class View extends React.Component {
render() {
const { style, ...other } = this.props;
return <div {...other} className={css([viewStyle, ...style])} />;
return <div {...other} className={css(viewStyle, ...style)} />;
}
}

View File

@@ -0,0 +1,9 @@
import Box from './Box';
import Dot from './Dot';
import View from './View';
export default {
Box,
Dot,
View
};

View File

@@ -1,6 +1,6 @@
/* eslint-disable react/prop-types */
import React from 'react';
import View from '../View/emotion';
import View from './View';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
<View

View File

@@ -0,0 +1,32 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { css } from 'glamor';
const Dot = ({ size, x, y, children, color }) => (
<div
className={css(styles.root, {
borderBottomColor: color,
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
left: `${x}px`,
top: `${y}px`
})}
>
{children}
</div>
);
const styles = {
root: {
position: 'absolute',
cursor: 'pointer',
width: 0,
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0
}
};
export default Dot;

View File

@@ -0,0 +1,9 @@
import Box from './Box';
import Dot from './Dot';
import View from './View';
export default {
Box,
Dot,
View
};

View File

@@ -0,0 +1,48 @@
/* eslint-disable react/prop-types */
import React from 'react';
import View from './View';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
<View
{...other}
style={{
...styles[`color${color}`],
...(fixed && styles.fixed),
...(layout === 'row' && styles.row),
...(outer && styles.outer)
}}
/>
);
const styles = {
outer: {
padding: 4
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#222'
},
color1: {
backgroundColor: '#666'
},
color2: {
backgroundColor: '#999'
},
color3: {
backgroundColor: 'blue'
},
color4: {
backgroundColor: 'orange'
},
color5: {
backgroundColor: 'red'
},
fixed: {
width: 20,
height: 20
}
};
export default Box;

View File

@@ -0,0 +1,34 @@
/* eslint-disable react/prop-types */
import React from 'react';
const Dot = ({ size, x, y, children, color }) => (
<div
style={{
...styles.root,
...{
borderBottomColor: color,
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
left: `${x}px`,
top: `${y}px`
}
}}
>
{children}
</div>
);
const styles = {
root: {
position: 'absolute',
cursor: 'pointer',
width: 0,
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0
}
};
export default Dot;

View File

@@ -0,0 +1,36 @@
/* eslint-disable react/prop-types */
import React from 'react';
const compose = (s1, s2) => {
if (s1 && s2) {
return { ...s1, ...s2 };
} else {
return s1 || s2;
}
};
class View extends React.Component {
render() {
const { style, ...other } = this.props;
return <div {...other} style={compose(viewStyle, style)} />;
}
}
const viewStyle = {
alignItems: 'stretch',
borderWidth: 0,
borderStyle: 'solid',
boxSizing: 'border-box',
display: 'flex',
flexBasis: 'auto',
flexDirection: 'column',
flexShrink: 0,
margin: 0,
padding: 0,
position: 'relative',
// fix flexbox bugs
minHeight: 0,
minWidth: 0
};
export default View;

View File

@@ -0,0 +1,9 @@
import Box from './Box';
import Dot from './Dot';
import View from './View';
export default {
Box,
Dot,
View
};

View File

@@ -2,7 +2,7 @@
import classnames from 'classnames';
import injectSheet from 'react-jss';
import React from 'react';
import View from '../View/jss';
import View from './View';
const Box = ({ classes, color, fixed = false, layout = 'column', outer = false, ...other }) => (
<View

View File

@@ -0,0 +1,7 @@
import Box from './Box';
import View from './View';
export default {
Box,
View
};

View File

@@ -1,7 +1,7 @@
/* eslint-disable react/prop-types */
import Radium from 'radium';
import React from 'react';
import View from '../View/radium';
import View from './View';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
<View

View File

@@ -0,0 +1,35 @@
/* eslint-disable react/prop-types */
import Radium from 'radium';
import React from 'react';
const Dot = ({ size, x, y, children, color }) => (
<div
style={[
styles.root,
{
borderBottomColor: color,
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
left: `${x}px`,
top: `${y}px`
}
]}
>
{children}
</div>
);
const styles = {
root: {
position: 'absolute',
cursor: 'pointer',
width: 0,
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0
}
};
export default Radium(Dot);

View File

@@ -0,0 +1,9 @@
import Box from './Box';
import Dot from './Dot';
import View from './View';
export default {
Box,
Dot,
View
};

View File

@@ -0,0 +1,32 @@
/* eslint-disable react/prop-types */
import { createElement, StyleSheet } from 'react-native';
const Dot = ({ size, x, y, children, color }) =>
createElement('div', {
children,
style: [
styles.root,
{
borderBottomColor: color,
borderRightWidth: size / 2,
borderBottomWidth: size / 2,
borderLeftWidth: size / 2,
left: x,
top: y
}
]
});
const styles = StyleSheet.create({
root: {
position: 'absolute',
cursor: 'pointer',
width: 0,
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0
}
});
export default Dot;

View File

@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
import theme from '../theme';
import theme from './theme';
import React, { PureComponent } from 'react';
import { StyleSheet, Text } from 'react-native';

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import { StyleSheet, View } from 'react-native';
import { StyleSheet, View, ViewPropTypes } from 'react-native';
class AspectRatio extends PureComponent {
static displayName = 'AspectRatio';
@@ -8,7 +8,7 @@ class AspectRatio extends PureComponent {
static propTypes = {
children: PropTypes.any,
ratio: PropTypes.number,
style: PropTypes.object
style: ViewPropTypes.style
};
static defaultProps = {

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { StyleSheet, View } from 'react-native';
import { StyleSheet, View, ViewPropTypes } from 'react-native';
import React, { Component } from 'react';
import theme from '../theme';
import theme from './theme';
class GridView extends Component {
static displayName = 'GridView';
@@ -9,7 +9,7 @@ class GridView extends Component {
static propTypes = {
children: PropTypes.node,
hasGap: PropTypes.bool,
style: PropTypes.object
style: ViewPropTypes.style
};
render() {

View File

@@ -1,11 +1,11 @@
import IconReply from '../Icons/Reply';
import IconHeart from '../Icons/Heart';
import IconRetweet from '../Icons/Retweet';
import IconDirectMessage from '../Icons/DirectMessage';
import IconReply from './IconReply';
import IconHeart from './IconHeart';
import IconRetweet from './IconRetweet';
import IconDirectMessage from './IconDirectMessage';
import PropTypes from 'prop-types';
import React from 'react';
import theme from '../theme';
import { Text, View, StyleSheet } from 'react-native';
import theme from './theme';
import { Text, View, ViewPropTypes, StyleSheet } from 'react-native';
const getIcon = (icon, highlighted) => {
switch (icon) {
@@ -30,7 +30,7 @@ export default class TweetAction extends React.Component {
displayMode: PropTypes.oneOf(['like', 'reply', 'retweet', 'directMessage']),
highlighted: PropTypes.bool,
onPress: PropTypes.func,
style: PropTypes.object
style: ViewPropTypes.style
};
render() {

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import TweetAction from '../TweetAction';
import { View, StyleSheet } from 'react-native';
import TweetAction from './TweetAction';
import { View, ViewPropTypes, StyleSheet } from 'react-native';
import React, { PureComponent } from 'react';
const actionNames = ['reply', 'retweet', 'like', 'directMessage'];
@@ -16,7 +16,7 @@ export default class TweetActionsBar extends PureComponent {
onPress: PropTypes.func
})
),
style: PropTypes.object
style: ViewPropTypes.style
};
render() {

View File

@@ -1,6 +1,6 @@
import AppText from '../AppText';
import AppText from './AppText';
import React from 'react';
import TweetTextPart from '../TweetTextPart';
import TweetTextPart from './TweetTextPart';
import { array, number, string } from 'prop-types';
class TweetText extends React.Component {

View File

@@ -2,7 +2,7 @@
import { Image, StyleSheet, Text } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
import theme from '../theme';
import theme from './theme';
const createTextEntity = ({ part }) => <Text>{`${part.prefix}${part.text}`}</Text>;
@@ -106,7 +106,7 @@ const styles = StyleSheet.create({
width: '1.25em',
paddingRight: '0.05em',
paddingLeft: '0.1em',
verticalAlign: '-0.2em'
textAlignVertical: '-0.2em'
}
});

View File

@@ -1,8 +1,8 @@
import AspectRatio from '../AspectRatio';
import AspectRatio from './AspectRatio';
import PropTypes from 'prop-types';
import { Image, StyleSheet } from 'react-native';
import { Image, StyleSheet, ViewPropTypes } from 'react-native';
import React, { PureComponent } from 'react';
import theme from '../theme';
import theme from './theme';
class UserAvatar extends PureComponent {
static displayName = 'UserAvatar';
@@ -10,7 +10,7 @@ class UserAvatar extends PureComponent {
static propTypes = {
accessibilityLabel: PropTypes.string,
circle: PropTypes.bool,
style: PropTypes.object,
style: ViewPropTypes.style,
uri: PropTypes.string
};

View File

@@ -1,6 +1,6 @@
import AppText from '../AppText';
import AppText from './AppText';
import PropTypes from 'prop-types';
import { StyleSheet } from 'react-native';
import { StyleSheet, ViewPropTypes } from 'react-native';
import React, { PureComponent } from 'react';
class UserNames extends PureComponent {
@@ -11,7 +11,7 @@ class UserNames extends PureComponent {
layout: PropTypes.oneOf(['nowrap', 'stack']),
onPress: PropTypes.func,
screenName: PropTypes.string,
style: PropTypes.object
style: ViewPropTypes.style
};
static defaultProps = {

View File

@@ -1,13 +1,13 @@
import AspectRatio from '../AspectRatio';
import GridView from '../GridView';
import AspectRatio from './AspectRatio';
import GridView from './GridView';
import PropTypes from 'prop-types';
import TweetActionsBar from '../TweetActionsBar';
import TweetText from '../TweetText';
import UserAvatar from '../UserAvatar';
import UserNames from '../UserNames';
import TweetActionsBar from './TweetActionsBar';
import TweetText from './TweetText';
import UserAvatar from './UserAvatar';
import UserNames from './UserNames';
import { Image, StyleSheet, Text, View } from 'react-native';
import React, { Component } from 'react';
import theme from '../theme';
import theme from './theme';
export class Tweet extends Component {
static displayName = 'Tweet';

View File

@@ -8,7 +8,7 @@ const styles = StyleSheet.create({
maxWidth: '100%',
position: 'relative',
userSelect: 'none',
verticalAlign: 'text-bottom'
textAlignVertical: 'text-bottom'
}
});

View File

@@ -0,0 +1,11 @@
import Box from './Box';
import Dot from './Dot';
import Tweet from './Tweet';
import { View } from 'react-native';
export default {
Box,
Dot,
Tweet,
View
};

View File

@@ -0,0 +1,34 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { Styles, View } from 'reactxp';
const Dot = ({ size, x, y, children, color }) => (
<View
children={children}
style={[
styles.root,
{
borderBottomColor: color,
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
left: `${x}px`,
top: `${y}px`
}
]}
/>
);
const styles = {
root: Styles.createViewStyle({
position: 'absolute',
cursor: 'pointer',
width: 0,
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0
})
};
export default Dot;

View File

@@ -1,7 +1,9 @@
import Box from './components/Box/reactxp';
import Box from './Box';
import Dot from './Dot';
import { View } from 'reactxp';
export default {
Box,
Dot,
View
};

View File

@@ -1,5 +1,5 @@
import styled from 'styled-components';
import View from '../View/styled-components';
import View from './View';
const getColor = color => {
switch (color) {

View File

@@ -0,0 +1,24 @@
/* eslint-disable react/prop-types */
import styled from 'styled-components';
import View from './View';
const Dot = styled(View).attrs({
style: props => ({
left: `${props.x}px`,
top: `${props.y}px`,
borderRightWidth: `${props.size / 2}px`,
borderBottomWidth: `${props.size / 2}px`,
borderLeftWidth: `${props.size / 2}px`,
borderBottomColor: `${props.color}`
})
})`
position: absolute;
cursor: pointer;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-top-width: 0;
`;
export default Dot;

View File

@@ -0,0 +1,9 @@
import Box from './Box';
import Dot from './Dot';
import View from './View';
export default {
Box,
Dot,
View
};

View File

@@ -1,7 +1,7 @@
/* eslint-disable react/prop-types */
import { injectStylePrefixed } from 'styletron-utils';
import React from 'react';
import View, { styletron } from '../View/styletron';
import View, { styletron } from './View';
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
<View

View File

@@ -0,0 +1,37 @@
/* eslint-disable react/prop-types */
import classnames from 'classnames';
import React from 'react';
import { injectStylePrefixed } from 'styletron-utils';
import { styletron } from './View';
const Dot = ({ size, x, y, children, color }) => (
<div
className={classnames(
styles.root,
injectStylePrefixed(styletron, {
borderBottomColor: color,
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
left: `${x}px`,
top: `${y}px`
})
)}
>
{children}
</div>
);
const styles = {
root: injectStylePrefixed(styletron, {
position: 'absolute',
cursor: 'pointer',
width: 0,
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0
})
};
export default Dot;

View File

@@ -0,0 +1,9 @@
import Box from './Box';
import Dot from './Dot';
import View from './View';
export default {
Box,
Dot,
View
};

View File

@@ -0,0 +1,99 @@
import aphrodite from './implementations/aphrodite';
import cssModules from './implementations/css-modules';
import emotion from './implementations/emotion';
import jss from './implementations/jss';
import glamor from './implementations/glamor';
import inlineStyles from './implementations/inline-styles';
import radium from './implementations/radium';
import reactNativeWeb from './implementations/react-native-web';
import reactxp from './implementations/reactxp';
import styledComponents from './implementations/styled-components';
import styletron from './implementations/styletron';
import renderDeepTree from './cases/renderDeepTree';
import renderSierpinskiTriangle from './cases/renderSierpinskiTriangle';
// import renderTweet from './cases/renderTweet';
import renderWideTree from './cases/renderWideTree';
const testMatrix = {
'inline-styles': [
() => renderDeepTree('inline-styles', inlineStyles),
() => renderWideTree('inline-styles', inlineStyles),
() => renderSierpinskiTriangle('inline-styles', inlineStyles)
],
'css-modules': [
() => renderDeepTree('css-modules', cssModules),
() => renderWideTree('css-modules', cssModules)
],
'react-native-web': [
() => renderDeepTree('react-native-web', reactNativeWeb),
() => renderWideTree('react-native-web', reactNativeWeb),
() => renderSierpinskiTriangle('react-native-web', reactNativeWeb)
// () => renderTweet('react-native-web', reactNativeWeb)
],
aphrodite: [
() => renderDeepTree('aphrodite', aphrodite),
() => renderWideTree('aphrodite', aphrodite)
],
emotion: [
() => renderDeepTree('emotion', emotion),
() => renderWideTree('emotion', emotion),
() => renderSierpinskiTriangle('emotion', emotion)
],
glamor: [
() => renderDeepTree('glamor', glamor),
() => renderWideTree('glamor', glamor)
// disabled: glamor starts to lock up the browser
// () => renderSierpinskiTriangle('glamor', glamor)
],
jss: [() => renderDeepTree('jss', jss), () => renderWideTree('jss', jss)],
radium: [
() => renderDeepTree('radium', radium),
() => renderWideTree('radium', radium),
() => renderSierpinskiTriangle('radium', radium)
],
reactxp: [
() => renderDeepTree('reactxp', reactxp),
() => renderWideTree('reactxp', reactxp),
() => renderSierpinskiTriangle('reactxp', reactxp)
],
'styled-components': [
() => renderDeepTree('styled-components', styledComponents),
() => renderWideTree('styled-components', styledComponents),
() => renderSierpinskiTriangle('styled-components', styledComponents)
],
styletron: [
() => renderDeepTree('styletron', styletron),
() => renderWideTree('styletron', styletron),
() => renderSierpinskiTriangle('styletron', styletron)
]
};
const allTests = Object.keys(testMatrix).reduce((acc, curr) => {
testMatrix[curr].forEach(test => {
acc.push(test);
});
return acc;
}, []);
const tests = [];
if (window.location.search) {
window.location.search
.slice(1)
.split(',')
.forEach(implementation => {
if (Array.isArray(testMatrix[implementation])) {
tests.push(...testMatrix[implementation]);
} else {
throw new Error(`Benchmark for ${implementation} not found`);
}
});
} else {
tests.push(...allTests);
}
tests.push(() => () => Promise.resolve(console.log('Done')));
tests.reduce((promise, test) => promise.then(test()), Promise.resolve());

View File

@@ -1,9 +0,0 @@
import Box from './components/Box/jss';
import View from './components/View/jss';
const api = {
Box,
View
};
export default api;

View File

@@ -1,9 +0,0 @@
import Box from './components/Box/radium';
import View from './components/View/radium';
const api = {
Box,
View
};
export default api;

View File

@@ -1,9 +0,0 @@
import Box from './components/Box/react-native';
import Tweet from './components/Tweet';
import { View } from 'react-native';
export default {
Box,
Tweet,
View
};

View File

@@ -1,7 +0,0 @@
import Box from './components/Box/styled-components';
import View from './components/View/styled-components';
export default {
Box,
View
};

View File

@@ -1,7 +0,0 @@
import Box from './components/Box/styletron';
import View from './components/View/styletron';
export default {
Box,
View
};

View File

@@ -1,10 +1,10 @@
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const path = require('path');
const webpack = require('webpack');
module.exports = {
context: __dirname,
entry: './index',
entry: ['babel-polyfill', './src/index'],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'performance.bundle.js'
@@ -34,10 +34,10 @@ module.exports = {
]
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false
}),
// new BundleAnalyzerPlugin({
// analyzerMode: 'static',
// openAnalyzer: false
// }),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),

View File

@@ -2674,6 +2674,22 @@ currently-unhandled@^0.4.1:
dependencies:
array-find-index "^1.0.1"
d3-color@1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.0.3.tgz#bc7643fca8e53a8347e2fbdaffa236796b58509b"
d3-interpolate@1:
version "1.1.6"
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.1.6.tgz#2cf395ae2381804df08aa1bf766b7f97b5f68fb6"
dependencies:
d3-color "1"
d3-scale-chromatic@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.1.1.tgz#811406e8e09dab78a49dac4a32047d5d3edd0c44"
dependencies:
d3-interpolate "1"
d@1:
version "1.0.0"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"