Rewrite benchmarks app

Reorganizes and rewrites the benchmarks. Each implementation is now
self-contained and the benchmarks can be run using a GUI. The benchmarks
themselves have been changed so that individual tests render over a
shorter time frame and more samples are taken.
This commit is contained in:
Nicolas Gallagher
2018-01-17 16:57:16 -08:00
parent 6e6fd4b5d0
commit ed0cafac7c
74 changed files with 1632 additions and 904 deletions

View File

@@ -4,48 +4,55 @@ To run these benchmarks:
```
yarn benchmark
open ./packages/benchmarks/index.html
```
To run benchmarks for individual implementations append `?<name>,<name>` to the
URL, e.g., `?css-modules,react-native-web`.
Develop against these benchmarks:
```
yarn compile --watch
yarn benchmark --watch
```
## Notes
These benchmarks are crude approximations of extreme cases that libraries may
These benchmarks are 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 case looks at the
rendering large trees of styled elements. The dynamic case 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.
inline styles) do not include a corresponding benchmark.
The components used in the render benchmarks are simple enough to be
implemented by multiple UI or style libraries. The benchmark implementations
and the features of the style libraries are _only approximately equivalent in
functionality_.
No benchmark will run for more than 20 seconds.
## Results
Typical render timings*: mean ± two standard deviations.
| Implementation | Deep tree (ms) | Wide tree (ms) | Triangle (ms) |
| Implementation | Mount deep tree (ms) | Mount wide tree (ms) | Update tree (ms) |
| :--- | ---: | ---: | ---: |
| `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` |
| `css-modules` | `15.23` 04.31` | `21.27` 07.03` | - |
| `react-native-web@0.3.1` | `17.52` 04.44` | `24.14` 04.39` | `15.03` `±02.22` |
| `inline-styles` | `50.06` 06.70` | `76.38` 09.58` | `06.43` 02.02` |
Other libraries
| Implementation | Deep tree (ms) | Wide tree (ms) | Triangle (ms) |
| Implementation | Mount deep tree (ms) | Mount wide tree (ms) | Update tree (ms) |
| :--- | ---: | ---: | ---: |
| `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` |
| `aphrodite@1.2.5` | `17.27` 05.96` | `24.89` 08.36` | - |
| `glamor@2.20.40` | `21.59` 05.38` | `27.93` 07.56` | |
| `emotion@8.0.12` | `21.07` 04.16` | `31.40` 09.40` | ‡ `19.80` `±13.56` |
| `styletron-react@3.0.3` | `23.55` 05.14` | `34.26` 07.58` | `10.39` 02.94` |
| `react-jss@8.2.1` | `27.31` 07.87` | `40.74` 10.67` | - |
| `styled-components@2.4.0` | `43.89` 06.99` | `63.26` 09.02` | `16.17` 03.71` |
| `reactxp@0.51.0-alpha.9` | `51.86` 07.21` | `78.80` 11.85` | `15.04` 03.92` |
| `radium@0.21.0` | `101.06` 13.00` | `144.46` 16.94` | `17.44` 03.59` |
These results indicate that render times when using `react-native-web`,
`css-modules`, `aphrodite`, and `styletron` are roughly equivalent and
@@ -53,4 +60,4 @@ 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 the browser tab.
‡Glamor essentially crashes the browser tab. Emotion gets slower every iteration.

View File

@@ -3,6 +3,11 @@
<head>
<meta charset="UTF-8">
<title>Performance tests</title>
<meta name="viewport" content="width=device-width">
<style>
html, body { height: 100%; width: 100%; overflow: hidden; }
.root { height: 100%; overflow: hidden; }
</style>
</head>
<body>
<div class="root"></div>

View File

@@ -3,31 +3,30 @@
"name": "benchmarks",
"version": "0.3.1",
"scripts": {
"benchmark": "webpack --config ./webpack.config.js && open index.html"
"benchmark": "webpack --config ./webpack.config.js"
},
"dependencies": {
"aphrodite": "^1.2.5",
"babel-polyfill": "^6.26.0",
"aphrodite": "1.2.5",
"classnames": "^2.2.5",
"d3-scale-chromatic": "^1.1.1",
"emotion": "^8.0.12",
"glamor": "^2.20.40",
"marky": "^1.2.0",
"radium": "^0.19.6",
"emotion": "8.0.12",
"glamor": "2.20.40",
"radium": "0.21.0",
"react": "^16.2.0",
"react-component-benchmark": "^0.0.4",
"react-dom": "^16.2.0",
"react-jss": "^8.2.0",
"react-native-web": "^0.3.1",
"reactxp": "^0.46.6",
"styled-components": "^2.3.2",
"styletron-client": "^3.0.0-rc.5",
"styletron-utils": "^3.0.0-rc.3"
"react-jss": "8.2.1",
"react-native-web": "0.3.1",
"reactxp": "0.51.0-alpha.9",
"styled-components": "2.4.0",
"styletron-client": "3.0.2",
"styletron-react": "3.0.3"
},
"devDependencies": {
"babel-plugin-react-native-web": "^0.3.1",
"css-loader": "^0.28.7",
"css-loader": "^0.28.9",
"style-loader": "^0.19.1",
"webpack": "^3.10.0",
"webpack-bundle-analyzer": "^2.9.1"
"webpack-bundle-analyzer": "^2.9.2"
}
}

View File

@@ -0,0 +1,293 @@
/* eslint-disable react/prop-types */
import Benchmark from './Benchmark';
import { Picker, StyleSheet, ScrollView, TouchableOpacity, View } from 'react-native';
import React, { Component } from 'react';
import Button from './Button';
import { IconClear, IconEye } from './Icons';
import ReportCard from './ReportCard';
import Text from './Text';
import Layout from './Layout';
import { colors } from './theme';
const Overlay = () => <View style={[StyleSheet.absoluteFill, { zIndex: 2 }]} />;
export default class App extends Component {
static displayName = '@app/App';
constructor(props, context) {
super(props, context);
const currentBenchmarkName = Object.keys(props.tests)[0];
this.state = {
currentBenchmarkName,
currentLibraryName: 'react-native-web',
status: 'idle',
results: []
};
}
render() {
const { tests } = this.props;
const { currentBenchmarkName, status, currentLibraryName, results } = this.state;
const currentImplementation = tests[currentBenchmarkName][currentLibraryName];
const { Component, Provider, getComponentProps, sampleCount } = currentImplementation;
return (
<Layout
actionPanel={
<View>
<View style={styles.pickers}>
<View style={styles.pickerContainer}>
<Text style={styles.pickerTitle}>Library</Text>
<Text style={{ fontWeight: 'bold' }}>{currentLibraryName}</Text>
<Picker
enabled={status !== 'running'}
onValueChange={this._handleChangeLibrary}
selectedValue={currentLibraryName}
style={styles.picker}
>
{Object.keys(tests[currentBenchmarkName]).map(libraryName => (
<Picker.Item key={libraryName} label={libraryName} value={libraryName} />
))}
</Picker>
</View>
<View style={{ width: 1, backgroundColor: colors.fadedGray }} />
<View style={styles.pickerContainer}>
<Text style={styles.pickerTitle}>Benchmark</Text>
<Text>{currentBenchmarkName}</Text>
<Picker
enabled={status !== 'running'}
onValueChange={this._handleChangeBenchmark}
selectedValue={currentBenchmarkName}
style={styles.picker}
>
{Object.keys(tests).map(test => (
<Picker.Item key={test} label={test} value={test} />
))}
</Picker>
</View>
</View>
<View style={{ flexDirection: 'row', height: 50 }}>
<View style={styles.grow}>
<Button
onPress={this._handleStart}
style={styles.button}
title={status === 'running' ? 'Running…' : 'Run'}
/>
</View>
</View>
{status === 'running' ? <Overlay /> : null}
</View>
}
listPanel={
<View style={styles.listPanel}>
<View style={styles.grow}>
<View style={styles.listBar}>
<View style={styles.iconClearContainer}>
<TouchableOpacity onPress={this._handleClear}>
<IconClear />
</TouchableOpacity>
</View>
</View>
<ScrollView ref={this._setScrollRef} style={styles.grow}>
{results.map((r, i) => (
<ReportCard
benchmarkName={r.benchmarkName}
key={i}
libraryName={r.libraryName}
libraryVersion={r.libraryVersion}
mean={r.mean}
sampleCount={r.sampleCount}
stdDev={r.stdDev}
/>
))}
{status === 'running' ? (
<ReportCard
benchmarkName={currentBenchmarkName}
libraryName={currentLibraryName}
/>
) : null}
</ScrollView>
</View>
{status === 'running' ? <Overlay /> : null}
</View>
}
viewPanel={
<View style={styles.viewPanel}>
<View style={styles.iconEyeContainer}>
<TouchableOpacity onPress={this._handleVisuallyHideBenchmark}>
<IconEye style={styles.iconEye} />
</TouchableOpacity>
</View>
<Provider>
{status === 'running' ? (
<React.Fragment>
<View ref={this._setBenchWrapperRef}>
<Benchmark
component={Component}
getComponentProps={getComponentProps}
onComplete={this._createHandleComplete({
sampleCount,
benchmarkName: currentBenchmarkName,
libraryName: currentLibraryName
})}
ref={this._setBenchRef}
sampleCount={sampleCount}
timeout={20000}
type={Component.benchmarkType}
/>
</View>
</React.Fragment>
) : (
<Component {...getComponentProps({ cycle: 10 })} />
)}
</Provider>
{status === 'running' ? <Overlay /> : null}
</View>
}
/>
);
}
_handleChangeBenchmark = value => {
this.setState(() => ({ currentBenchmarkName: value }));
};
_handleChangeLibrary = value => {
this.setState(() => ({ currentLibraryName: value }));
};
_handleStart = () => {
this.setState(
() => ({ status: 'running' }),
() => {
if (this._shouldHideBenchmark && this._benchWrapperRef) {
this._benchWrapperRef.setNativeProps({ style: { opacity: 0 } });
}
this._benchmarkRef.start();
this._scrollToEnd();
}
);
};
// hide the benchmark as it is performed (no flashing on screen)
_handleVisuallyHideBenchmark = () => {
this._shouldHideBenchmark = !this._shouldHideBenchmark;
if (this._benchWrapperRef) {
this._benchWrapperRef.setNativeProps({
style: { opacity: this._shouldHideBenchmark ? 0 : 1 }
});
}
};
_createHandleComplete = ({ benchmarkName, libraryName, sampleCount }) => results => {
this.setState(
state => ({
results: state.results.concat([
{
...results,
benchmarkName,
libraryName,
libraryVersion: this.props.tests[benchmarkName][libraryName].version
}
]),
status: 'complete'
}),
this._scrollToEnd
);
// console.log(results);
// console.log(results.samples.map(sample => sample.elapsed.toFixed(1)).join('\n'));
};
_handleClear = () => {
this.setState(() => ({ results: [] }));
};
_setBenchRef = ref => {
this._benchmarkRef = ref;
};
_setBenchWrapperRef = ref => {
this._benchWrapperRef = ref;
};
_setScrollRef = ref => {
this._scrollRef = ref;
};
// scroll the most recent result into view
_scrollToEnd = () => {
window.requestAnimationFrame(() => {
if (this._scrollRef) {
this._scrollRef.scrollToEnd();
}
});
};
}
const styles = StyleSheet.create({
viewPanel: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
backgroundColor: 'black'
},
iconEye: {
color: 'white',
height: 32
},
iconEyeContainer: {
position: 'absolute',
top: 10,
right: 10,
zIndex: 1
},
iconClearContainer: {
height: '100%',
marginLeft: 5
},
grow: {
flex: 1
},
listPanel: {
flex: 1,
width: '100%',
marginHorizontal: 'auto'
},
listBar: {
padding: 5,
alignItems: 'center',
flexDirection: 'row',
backgroundColor: colors.fadedGray,
borderBottomWidth: 1,
borderBottomColor: colors.mediumGray,
justifyContent: 'flex-end'
},
pickers: {
flexDirection: 'row'
},
pickerContainer: {
flex: 1,
padding: 5
},
pickerTitle: {
fontSize: 12,
color: colors.deepGray
},
picker: {
...StyleSheet.absoluteFillObject,
opacity: 0,
width: '100%'
},
button: {
borderRadius: 0,
fontSize: 32,
flex: 1
}
});

View File

@@ -0,0 +1,221 @@
// @flow
/* global $Values */
import * as Timing from './timing';
import React, { Component } from 'react';
import { getMean, getMedian, getStdDev } from './math';
import type { BenchResultsType, FullSampleTimingType, SampleTimingType } from './types';
export const BenchmarkType = {
MOUNT: 'mount',
UPDATE: 'update',
UNMOUNT: 'unmount'
};
const emptyObject = {};
const shouldRender = (cycle: number, type: $Values<typeof BenchmarkType>): boolean => {
switch (type) {
// Render every odd iteration (first, third, etc)
// Mounts and unmounts the component
case BenchmarkType.MOUNT:
case BenchmarkType.UNMOUNT:
return !((cycle + 1) % 2);
// Render every iteration (updates previously rendered module)
case BenchmarkType.UPDATE:
return true;
default:
return false;
}
};
const shouldRecord = (cycle: number, type: $Values<typeof BenchmarkType>): boolean => {
switch (type) {
// Record every odd iteration (when mounted: first, third, etc)
case BenchmarkType.MOUNT:
return !((cycle + 1) % 2);
// Record every iteration
case BenchmarkType.UPDATE:
return true;
// Record every even iteration (when unmounted)
case BenchmarkType.UNMOUNT:
return !(cycle % 2);
default:
return false;
}
};
const isDone = (
cycle: number,
sampleCount: number,
type: $Values<typeof BenchmarkType>
): boolean => {
switch (type) {
case BenchmarkType.MOUNT:
return cycle >= sampleCount * 2 - 1;
case BenchmarkType.UPDATE:
return cycle >= sampleCount - 1;
case BenchmarkType.UNMOUNT:
return cycle >= sampleCount * 2;
default:
return true;
}
};
const sortNumbers = (a: number, b: number): number => a - b;
type BenchmarkPropsType = {
component: typeof React.Component,
getComponentProps?: Function,
onComplete: (x: BenchResultsType) => void,
sampleCount: number,
timeout: number,
type: $Values<typeof BenchmarkType>
};
type BenchmarkStateType = {
getComponentProps: Function,
cycle: number,
running: boolean
};
/**
* Benchmark
* TODO: documentation
*/
export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkStateType> {
_startTime: number;
_samples: Array<SampleTimingType>;
static displayName = 'Benchmark';
static defaultProps = {
getComponentProps: () => emptyObject,
sampleCount: 50,
timeout: 10000, // 10 seconds
type: BenchmarkType.MOUNT
};
static Type = BenchmarkType;
constructor(props: BenchmarkPropsType, context?: {}) {
super(props, context);
this.state = {
getComponentProps: props.getComponentProps,
cycle: 0,
running: false
};
this._startTime = 0;
this._samples = [];
}
componentWillReceiveProps(nextProps: BenchmarkPropsType) {
this.setState(state => ({ getComponentProps: nextProps.getComponentProps }));
}
componentWillUpdate(nextProps: BenchmarkPropsType, nextState: BenchmarkStateType) {
if (nextState.running && !this.state.running) {
this._startTime = Timing.now();
}
}
componentDidUpdate() {
const { sampleCount, timeout, type } = this.props;
const { cycle, running } = this.state;
if (running && shouldRecord(cycle, type)) {
this._samples[cycle].end = Timing.now();
}
if (running) {
const now = Timing.now();
if (!isDone(cycle, sampleCount, type) && now - this._startTime < timeout) {
this._handleCycleComplete();
} else {
this._handleComplete(now);
}
}
}
componentWillUnmount() {
if (this.raf) {
window.cancelAnimationFrame(this.raf);
}
}
render() {
const { component: Component, type } = this.props;
const { getComponentProps, cycle, running } = this.state;
if (running && shouldRecord(cycle, type)) {
this._samples[cycle] = { start: Timing.now() };
}
return running && shouldRender(cycle, type) ? (
<Component {...getComponentProps({ cycle })} />
) : null;
}
start() {
this._samples = [];
this.setState(() => ({ running: true, cycle: 0 }));
}
_handleCycleComplete() {
const { getComponentProps, type } = this.props;
const { cycle } = this.state;
// Calculate the component props outside of the time recording (render)
// so that it doesn't skew results
const getNextProps =
type === BenchmarkType.UPDATE
? obj => ({ ...getComponentProps(obj), 'data-test': cycle })
: getComponentProps;
this.raf = window.requestAnimationFrame(() => {
this.setState((state: BenchmarkStateType) => ({
getComponentProps: getNextProps,
cycle: state.cycle + 1
}));
});
}
getSamples(): Array<FullSampleTimingType> {
return this._samples.reduce(
(
memo: Array<FullSampleTimingType>,
{ start, end: endTime }: SampleTimingType
): Array<FullSampleTimingType> => {
const end = endTime || 0;
memo.push({ start, end, elapsed: end - start });
return memo;
},
[]
);
}
_handleComplete(endTime: number) {
const { onComplete } = this.props;
const samples = this.getSamples();
this.setState(() => ({ running: false, cycle: 0 }));
const runTime = endTime - this._startTime;
const sortedElapsedTimes = samples
.map(({ elapsed }: { elapsed: number }): number => elapsed)
.sort(sortNumbers);
const mean = getMean(sortedElapsedTimes);
const stdDev = getStdDev(sortedElapsedTimes);
onComplete({
startTime: this._startTime,
endTime,
runTime,
sampleCount: samples.length,
samples: samples,
max: sortedElapsedTimes[sortedElapsedTimes.length - 1],
min: sortedElapsedTimes[0],
median: getMedian(sortedElapsedTimes),
mean,
stdDev
});
}
}

View File

@@ -0,0 +1,27 @@
// @flow
type ValuesType = Array<number>;
export const getStdDev = (values: ValuesType): number => {
const avg = getMean(values);
const squareDiffs = values.map((value: number) => {
const diff = value - avg;
return diff * diff;
});
return Math.sqrt(getMean(squareDiffs));
};
export const getMean = (values: ValuesType): number => {
const sum = values.reduce((sum: number, value: number) => sum + value, 0);
return sum / values.length;
};
export const getMedian = (values: ValuesType): number => {
if (values.length === 1) {
return values[0];
}
const numbers = values.sort((a: number, b: number) => a - b);
return (numbers[(numbers.length - 1) >> 1] + numbers[numbers.length >> 1]) / 2;
};

View File

@@ -0,0 +1,18 @@
// @flow
const NS_PER_MS = 1e6;
const MS_PER_S = 1e3;
// Returns a high resolution time (if possible) in milliseconds
export function now(): number {
if (window && window.performance) {
return window.performance.now();
} else if (process && process.hrtime) {
const [seconds, nanoseconds] = process.hrtime();
const secInMS = seconds * MS_PER_S;
const nSecInMS = nanoseconds / NS_PER_MS;
return secInMS + nSecInMS;
} else {
return Date.now();
}
}

View File

@@ -0,0 +1,28 @@
// @flow
export type BenchResultsType = {
startTime: number,
endTime: number,
runTime: number,
sampleCount: number,
samples: Array<FullSampleTimingType>,
max: number,
min: number,
median: number,
mean: number,
stdDev: number,
p70: number,
p95: number,
p99: number
};
export type SampleTimingType = {
start: number,
end?: number,
elapsed?: number
};
export type FullSampleTimingType = {
start: number,
end: number,
elapsed: number
};

View File

@@ -0,0 +1,70 @@
import { ColorPropType, StyleSheet, TouchableHighlight, Text } from 'react-native';
import React, { Component } from 'react';
import { bool, func, string } from 'prop-types';
export default class Button extends Component<*> {
static displayName = '@app/Button';
static propTypes = {
accessibilityLabel: string,
color: ColorPropType,
disabled: bool,
onPress: func.isRequired,
style: TouchableHighlight.propTypes.style,
testID: string,
textStyle: Text.propTypes.style,
title: string.isRequired
};
render() {
const {
accessibilityLabel,
color,
disabled,
onPress,
style,
textStyle,
testID,
title
} = this.props;
return (
<TouchableHighlight
accessibilityLabel={accessibilityLabel}
accessibilityRole="button"
disabled={disabled}
onPress={onPress}
style={[
styles.button,
style,
color && { backgroundColor: color },
disabled && styles.buttonDisabled
]}
testID={testID}
>
<Text style={[styles.text, textStyle, disabled && styles.textDisabled]}>{title}</Text>
</TouchableHighlight>
);
}
}
const styles = StyleSheet.create({
button: {
backgroundColor: '#2196F3',
borderRadius: 0,
justifyContent: 'center'
},
text: {
color: '#fff',
fontWeight: '500',
padding: 8,
textAlign: 'center',
textTransform: 'uppercase'
},
buttonDisabled: {
backgroundColor: '#dfdfdf'
},
textDisabled: {
color: '#a1a1a1'
}
});

View File

@@ -0,0 +1,55 @@
import React, { Fragment } from 'react';
import { createElement, StyleSheet, Text } from 'react-native';
const styles = StyleSheet.create({
root: {
display: 'inline-block',
fill: 'currentcolor',
height: '1.25em',
maxWidth: '100%',
position: 'relative',
userSelect: 'none',
textAlignVertical: 'text-bottom'
}
});
const createIcon = children => {
const Icon = props =>
createElement(
'svg',
{
style: StyleSheet.compose(styles.root, props.style),
width: 24,
height: 24,
viewBox: '0 0 24 24'
},
children
);
Icon.propTypes = {
style: Text.propTypes.style
};
return Icon;
};
export const IconClear = createIcon(
<Fragment>
<path d="M0 0h24v24H0z" id="bounds" opacity="0" />
<path d="M12 1.25C6.072 1.25 1.25 6.072 1.25 12S6.072 22.75 12 22.75 22.75 17.928 22.75 12 17.928 1.25 12 1.25zm0 1.5c2.28 0 4.368.834 5.982 2.207L4.957 17.982C3.584 16.368 2.75 14.282 2.75 12c0-5.1 4.15-9.25 9.25-9.25zm0 18.5c-2.28 0-4.368-.834-5.982-2.207L19.043 6.018c1.373 1.614 2.207 3.7 2.207 5.982 0 5.1-4.15 9.25-9.25 9.25z" />
</Fragment>
);
export const IconEye = createIcon(
<Fragment>
<path d="M0 0h24v24H0z" id="bounds" opacity="0" />
<path d="M14.548 11.634c-1.207 0-2.188-.98-2.188-2.188 0-.664.302-1.25.77-1.653-.363-.097-.736-.165-1.13-.165-2.416 0-4.375 1.96-4.375 4.376S9.585 16.38 12 16.38c2.418 0 4.377-1.96 4.377-4.376 0-.4-.07-.78-.17-1.146-.402.47-.992.776-1.66.776z" />
<path d="M12 19.79c-7.228 0-10.12-6.724-10.24-7.01-.254-.466-.254-1.105.035-1.642C1.88 10.923 4.772 4.2 12 4.2s10.12 6.723 10.24 7.01c.254.465.254 1.104-.035 1.64-.085.216-2.977 6.94-10.205 6.94zm0-14c-6.154 0-8.668 5.787-8.772 6.033-.068.135-.068.208-.033.273.137.316 2.65 6.104 8.805 6.104 6.18 0 8.747-5.973 8.772-6.033.07-.136.07-.21.034-.274-.138-.316-2.652-6.103-8.806-6.103z" />
</Fragment>
);
export const IconCopy = createIcon(
<Fragment>
<path d="M0 0h24v24H0z" id="bounds" opacity="0" />
<path d="M11.47 14.53c.146.146.338.22.53.22s.384-.073.53-.22l5-5c.293-.293.293-.768 0-1.06s-.768-.294-1.06 0l-3.72 3.72V2c0-.414-.337-.75-.75-.75s-.75.336-.75.75v10.19L7.53 8.47c-.293-.293-.768-.293-1.06 0s-.294.768 0 1.06l5 5z" />
<path d="M21.25 13.25c-.414 0-.75.336-.75.75v5.652c0 .437-.355.792-.792.792H4.292c-.437 0-.792-.355-.792-.792V14c0-.414-.336-.75-.75-.75S2 13.586 2 14v5.652c0 1.264 1.028 2.292 2.292 2.292h15.416c1.264 0 2.292-1.028 2.292-2.292V14c0-.414-.336-.75-.75-.75z" />
</Fragment>
);

View File

@@ -0,0 +1,63 @@
import { colors } from './theme';
import { element } from 'prop-types';
import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
export default class Layout extends Component {
static propTypes = {
actionPanel: element,
listPanel: element,
viewPanel: element
};
state = {
widescreen: false
};
render() {
const { viewPanel, actionPanel, listPanel } = this.props;
const { widescreen } = this.state;
return (
<View onLayout={this._handleLayout} style={[styles.root, widescreen && styles.row]}>
<View style={widescreen ? styles.grow : styles.stackPanel}>{viewPanel}</View>
<View style={styles.grow}>
<View style={styles.grow}>{listPanel}</View>
<View style={styles.divider} />
<View>{actionPanel}</View>
</View>
</View>
);
}
_handleLayout = ({ nativeEvent }) => {
const { layout } = nativeEvent;
const { width } = layout;
if (width >= 740) {
this.setState(() => ({ widescreen: true }));
} else {
this.setState(() => ({ widescreen: false }));
}
};
}
const styles = StyleSheet.create({
root: {
height: '100%'
},
row: {
flexDirection: 'row'
},
divider: {
height: 10,
backgroundColor: colors.fadedGray,
borderBottomWidth: 1,
borderTopWidth: 1,
borderColor: colors.mediumGray
},
grow: {
flex: 1
},
stackPanel: {
height: '33.33%'
}
});

View File

@@ -0,0 +1,71 @@
/* eslint-disable react/prop-types */
import Text from './Text';
import { StyleSheet, View } from 'react-native';
import React, { Fragment } from 'react';
const fmt = (time: number) => {
const i = Number(Math.round(time + 'e2') + 'e-2').toFixed(2);
return 10 / i > 1 ? `0${i}` : i;
};
const ReportCard = ({ benchmarkName, libraryName, sampleCount, mean, stdDev, libraryVersion }) => {
const sampleCountText = sampleCount != null ? `(${sampleCount})` : '';
return (
<View style={styles.root}>
<View style={styles.left}>
<Text numberOfLines={1} style={styles.bold}>
{`${libraryName}${libraryVersion ? '@' + libraryVersion : ''}`}
</Text>
<Text numberOfLines={1}>
{benchmarkName} {sampleCountText}
</Text>
</View>
<View style={styles.right}>
{mean ? (
<Fragment>
<Text style={[styles.bold, styles.monoFont]}>
{fmt(mean)} ±{fmt(stdDev)} ms
</Text>
<Text style={[styles.monoFont, styles.centerText]}>
<Text style={styles.smallText}>Σ = </Text>
<Text>{Math.round(mean * sampleCount)} ms</Text>
</Text>
</Fragment>
) : (
<Text style={styles.bold}>In progress</Text>
)}
</View>
</View>
);
};
const styles = StyleSheet.create({
root: {
flexDirection: 'row',
alignItems: 'center',
padding: 5,
borderBottomWidth: 1,
borderBottomColor: '#eee'
},
bold: {
fontWeight: 'bold'
},
smallText: { fontSize: 12 },
monoFont: {
fontFamily: 'monospace'
},
centerText: {
display: 'flex',
alignItems: 'center'
},
left: {
width: '50%'
},
right: {
flex: 1,
alignItems: 'flex-end'
}
});
export default ReportCard;

View File

@@ -0,0 +1,34 @@
/* eslint-disable react/prop-types */
/**
* @flow
*/
import { bool } from 'prop-types';
import React from 'react';
import { StyleSheet, Text } from 'react-native';
import { colors } from './theme';
class AppText extends React.Component {
static displayName = '@app/Text';
static contextTypes = {
isInAParentText: bool
};
render() {
const { style, ...rest } = this.props;
const { isInAParentText } = this.context;
return <Text {...rest} style={[!isInAParentText && styles.baseText, style]} />;
}
}
const styles = StyleSheet.create({
baseText: {
color: colors.textBlack,
fontSize: '1rem',
lineHeight: '1.3125em'
}
});
export default AppText;

View File

@@ -0,0 +1,101 @@
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import { Dimensions, Platform } from 'react-native';
const baseFontSize = 14;
const baseUnit = 1.3125;
const createPlatformLength = multiplier =>
Platform.select({ web: `${multiplier}rem`, default: multiplier * baseFontSize });
/**
* Exported variables
*/
export const borderRadii = {
normal: Platform.select({ web: '0.35rem', default: 5 }),
infinite: '9999px'
};
export const breakpoints = {
small: 360,
medium: 600,
large: 800,
xLarge: 1100
};
/**
* Color palette
* DO NOT add new colors unless they are part of @design's color palette.
* DO NOT use colors that are not specified here.
* source: go/uicolors
*/
export const colors = {
// Primary
blue: '#1DA1F2',
purple: '#794BC4',
green: '#17BF63',
yellow: '#FFAD1F',
orange: '#F45D22',
red: '#E0245E',
// Text and interface grays
textBlack: '#14171A',
deepGray: '#657786',
mediumGray: '#AAB8C2',
lightGray: '#CCD6DD',
fadedGray: '#E6ECF0',
faintGray: '#F5F8FA',
white: '#FFF',
textBlue: '#1B95E0'
};
export const fontFamilies = {
normal: 'System',
japan: Platform.select({
web:
'Arial, "ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", Osaka, "メイリオ", Meiryo, " Pゴシック", "MS PGothic", sans-serif',
default: 'System'
}),
rtl: Platform.select({ web: 'Tahoma, Arial, sans-serif', default: 'System' })
};
export const fontSizes = {
// font scale
small: createPlatformLength(0.85),
normal: createPlatformLength(1),
large: createPlatformLength(1.25),
xLarge: createPlatformLength(1.5),
jumbo: createPlatformLength(2)
};
export const lineHeight = Platform.select({ web: `${baseUnit}` });
export const spaces = {
// This set of space variables should be used for margin, padding
micro: createPlatformLength(baseUnit * 0.125),
xxSmall: createPlatformLength(baseUnit * 0.25),
xSmall: createPlatformLength(baseUnit * 0.5),
small: createPlatformLength(baseUnit * 0.75),
medium: createPlatformLength(baseUnit),
large: createPlatformLength(baseUnit * 1.5),
xLarge: createPlatformLength(baseUnit * 2),
xxLarge: createPlatformLength(baseUnit * 2.5),
jumbo: createPlatformLength(baseUnit * 3)
};
// On web, change the root font-size at specific breakpoints to scale the UI
// for larger viewports.
if (Platform.OS === 'web' && canUseDOM) {
const { medium, large } = breakpoints;
const htmlElement = document.documentElement;
const setFontSize = width => {
const fontSize = width > medium ? (width > large ? '18px' : '17px') : '16px';
if (htmlElement) {
htmlElement.style.fontSize = fontSize;
}
};
setFontSize(Dimensions.get('window').width);
Dimensions.addEventListener('change', dimensions => {
setFontSize(dimensions.window.width);
});
}

View File

@@ -1,98 +0,0 @@
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.duration;
};
const mean = values => {
const sum = values.reduce((sum, value) => sum + value, 0);
return sum / values.length;
};
const median = values => {
if (!Array.isArray(values)) {
return 0;
}
if (values.length === 1) {
return values[0];
}
const numbers = [...values].sort((a, b) => a - b);
return (numbers[(numbers.length - 1) >> 1] + numbers[numbers.length >> 1]) / 2;
};
const standardDeviation = values => {
const avg = mean(values);
const squareDiffs = values.map(value => {
const diff = value - avg;
return diff * diff;
});
const meanSquareDiff = mean(squareDiffs);
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();
teardown();
const done = () => {
log(name, description, durations);
resolve();
};
const a = () => {
setup();
window.requestAnimationFrame(b);
};
const b = () => {
const duration = measure('mean', task);
durations.push(duration);
window.requestAnimationFrame(c);
};
const c = () => {
teardown();
window.requestAnimationFrame(d);
};
const d = () => {
i += 1;
if (i < runs) {
window.requestAnimationFrame(a);
} else {
window.requestAnimationFrame(done);
}
};
window.requestAnimationFrame(a);
});
};
export default benchmark;

View File

@@ -1,58 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
class DeepTree extends Component {
static propTypes = {
breadth: PropTypes.number.isRequired,
components: PropTypes.object,
depth: PropTypes.number.isRequired,
id: PropTypes.number.isRequired,
wrap: PropTypes.number.isRequired
};
/* necessary for reactxp to work without errors */
static childContextTypes = {
focusManager: PropTypes.object
};
getChildContext() {
return {
focusManager: {
addFocusableComponent() {},
removeFocusableComponent() {},
restrictFocusWithin() {},
removeFocusRestriction() {},
limitFocusWithin() {},
removeFocusLimitation() {}
}
};
}
render() {
const { breadth, components, depth, id, wrap } = this.props;
const { Box } = components;
let result = (
<Box color={id % 3} components={components} layout={depth % 2 === 0 ? 'column' : 'row'} outer>
{depth === 0 && <Box color={id % 3 + 3} components={components} fixed />}
{depth !== 0 &&
Array.from({ length: breadth }).map((el, i) => (
<DeepTree
breadth={breadth}
components={components}
depth={depth - 1}
id={i}
key={i}
wrap={wrap}
/>
))}
</Box>
);
for (let i = 0; i < wrap; i++) {
result = <Box components={components}>{result}</Box>;
}
return result;
}
}
export default DeepTree;

View File

@@ -1,17 +1,22 @@
import PropTypes from 'prop-types';
import { BenchmarkType } from 'react-component-benchmark';
import { number, object } from 'prop-types';
import React from 'react';
import { interpolatePurples, interpolateBuPu, interpolateRdPu } from 'd3-scale-chromatic';
const targetSize = 25;
const targetSize = 10;
class SierpinskiTriangle extends React.Component {
static displayName = 'SierpinskiTriangle';
static benchmarkType = BenchmarkType.UPDATE;
static propTypes = {
Dot: PropTypes.node,
depth: PropTypes.number,
renderCount: PropTypes.number,
s: PropTypes.number,
x: PropTypes.number,
y: PropTypes.number
components: object,
depth: number,
renderCount: number,
s: number,
x: number,
y: number
};
static defaultProps = {
@@ -20,64 +25,66 @@ class SierpinskiTriangle extends React.Component {
};
render() {
const { x, y, depth, renderCount, Dot } = this.props;
const { components, x, y, depth, renderCount } = this.props;
let { s } = this.props;
const { Dot } = components;
if (s <= targetSize) {
let fn;
switch (depth) {
case 1:
fn = interpolatePurples;
break;
case 2:
fn = interpolateBuPu;
break;
case 3:
default:
fn = interpolateRdPu;
if (Dot) {
if (s <= targetSize) {
let fn;
switch (depth) {
case 1:
fn = interpolatePurples;
break;
case 2:
fn = interpolateBuPu;
break;
case 3:
default:
fn = interpolateRdPu;
}
// introduce randomness to ensure that repeated runs don't produce the same colors
const color = fn(renderCount * Math.random() / 20);
return (
<Dot color={color} size={targetSize} x={x - targetSize / 2} y={y - targetSize / 2} />
);
}
return (
<Dot
color={fn(renderCount / 20)}
size={targetSize}
x={x - targetSize / 2}
y={y - targetSize / 2}
s /= 2;
return [
<SierpinskiTriangle
components={components}
depth={1}
key={1}
renderCount={renderCount}
s={s}
x={x}
y={y - s / 2}
/>,
<SierpinskiTriangle
components={components}
depth={2}
key={2}
renderCount={renderCount}
s={s}
x={x - s}
y={y + s / 2}
/>,
<SierpinskiTriangle
components={components}
depth={3}
key={3}
renderCount={renderCount}
s={s}
x={x + s}
y={y + s / 2}
/>
);
];
} else {
return <span style={{ color: 'white' }}>No implementation available</span>;
}
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}
/>
];
}
}

View File

@@ -0,0 +1,45 @@
import { BenchmarkType } from 'react-component-benchmark';
import { number, object } from 'prop-types';
import React, { Component } from 'react';
class Tree extends Component {
static displayName = 'Tree';
static benchmarkType = BenchmarkType.MOUNT;
static propTypes = {
breadth: number.isRequired,
components: object,
depth: number.isRequired,
id: number.isRequired,
wrap: number.isRequired
};
render() {
const { breadth, components, depth, id, wrap } = this.props;
const { Box } = components;
let result = (
<Box color={id % 3} layout={depth % 2 === 0 ? 'column' : 'row'} outer>
{depth === 0 && <Box color={id % 3 + 3} fixed />}
{depth !== 0 &&
Array.from({ length: breadth }).map((el, i) => (
<Tree
breadth={breadth}
components={components}
depth={depth - 1}
id={i}
key={i}
wrap={wrap}
/>
))}
</Box>
);
for (let i = 0; i < wrap; i++) {
result = <Box>{result}</Box>;
}
return result;
}
}
export default Tree;

View File

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

View File

@@ -1,112 +0,0 @@
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,112 +0,0 @@
import createRenderBenchmark from '../createRenderBenchmark';
import React from 'react';
const tweet1 = {
favorite_count: 30,
favorited: true,
id: '834889712556875776',
lang: 'en',
retweet_count: 6,
retweeted: false,
textParts: [
{
prefix: '',
text: 'Living burrito to burrito '
},
{
emoji: 'https://abs-0.twimg.com/emoji/v2/svg/1f32f.svg',
isEmoji: true,
prefix: '',
text: '🌯'
},
{
emoji: 'https://abs-0.twimg.com/emoji/v2/svg/1f32f.svg',
isEmoji: true,
prefix: '',
text: '🌯'
},
{
emoji: 'https://abs-0.twimg.com/emoji/v2/svg/1f32f.svg',
isEmoji: true,
prefix: '',
text: '🌯'
}
],
timestamp: 'Feb 23',
user: {
fullName: 'Nicolas',
screenName: 'necolas',
profileImageUrl: 'https://pbs.twimg.com/profile_images/804365942360719360/dQnPejph_normal.jpg'
}
};
const tweet2 = {
favorite_count: 84,
favorited: false,
id: '730896800060579840',
lang: 'en',
media: {
source: {
uri: 'https://pbs.twimg.com/media/CiSqvsJVEAAtLZ1.jpg',
width: 600,
height: 338
}
},
retweet_count: 4,
retweeted: true,
textParts: [
{
prefix: '',
text: 'Presenting '
},
{
displayUrl: 'mobile.twitter.com',
expandedUrl: 'https://mobile.twitter.com',
isEntity: true,
isUrl: true,
linkRelation: 'nofollow',
prefix: '',
text: '',
textDirection: 'ltr',
url: 'https://t.co/4hRCAxiUUG'
},
{
prefix: '',
text: ' with '
},
{
isEntity: true,
isMention: true,
prefix: '@',
text: 'davidbellona',
textDirection: 'ltr',
url: '/davidbellona'
},
{
prefix: '',
text: " at Twitter's all hands meeting "
}
],
timestamp: 'May 12',
user: {
fullName: 'Nicolas',
screenName: 'necolas',
profileImageUrl: 'https://pbs.twimg.com/profile_images/804365942360719360/dQnPejph_normal.jpg'
}
};
const renderTweet = (label, { Tweet }) =>
createRenderBenchmark({
name: `[${label}] Tweet`,
runs: 10,
getElement() {
return (
<div style={{ width: 500 }}>
<Tweet tweet={tweet1} />
<Tweet tweet={tweet2} />
</div>
);
}
});
export default renderTweet;

View File

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

View File

@@ -1,24 +0,0 @@
import benchmark from './benchmark';
import ReactDOM from 'react-dom';
const node = document.querySelector('.root');
const createRenderBenchmark = ({ description, getElement, name, runs }) => () => {
const setup = () => {};
const teardown = () => {
ReactDOM.unmountComponentAtNode(node);
};
return benchmark({
name,
description,
runs,
setup,
teardown,
task: () => {
ReactDOM.render(getElement(), node);
}
});
};
export default createRenderBenchmark;

View File

@@ -0,0 +1,38 @@
/* @flow */
import { type Component } from 'react';
import packageJson from '../package.json';
const context = require.context('./implementations/', true, /index\.js$/);
const { dependencies } = packageJson;
type ComponentsType = {
Box: Component,
Dot: Component,
Provider: Component,
View: Component
};
type ImplementationType = {
components: ComponentsType,
name: string,
version: string
};
const toImplementations = (context: Object): Array<ImplementationType> =>
context.keys().map(path => {
const components = context(path).default;
const name = path.split('/')[1];
const version = dependencies[name] || '';
return { components, name, version };
});
const toObject = (impls: Array<ImplementationType>): Object =>
impls.reduce((acc, impl) => {
acc[impl.name] = impl;
return acc;
}, {});
const map = toObject(toImplementations(context));
export default map;

View File

@@ -17,32 +17,33 @@ const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other
const styles = StyleSheet.create({
outer: {
alignSelf: 'flex-start',
padding: 4
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#222'
backgroundColor: '#14171A'
},
color1: {
backgroundColor: '#666'
backgroundColor: '#AAB8C2'
},
color2: {
backgroundColor: '#999'
backgroundColor: '#E6ECF0'
},
color3: {
backgroundColor: 'blue'
backgroundColor: '#FFAD1F'
},
color4: {
backgroundColor: 'orange'
backgroundColor: '#F45D22'
},
color5: {
backgroundColor: 'red'
backgroundColor: '#E0245E'
},
fixed: {
width: 20,
height: 20
width: 6,
height: 6
}
});

View File

@@ -0,0 +1,2 @@
import View from './View';
export default View;

View File

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

View File

@@ -0,0 +1,2 @@
import View from './View';
export default View;

View File

@@ -1,4 +1,5 @@
.outer {
align-self: flex-start;
padding: 4px;
}
@@ -7,30 +8,30 @@
}
.color0 {
background-color: #222;
background-color: #14171A;
}
.color1 {
background-color: #666;
background-color: #AAB8C2;
}
.color2 {
background-color: #999;
background-color: #E6ECF0;
}
.color3 {
background-color: blue;
background-color: #FFAD1F;
}
.color4 {
background-color: orange;
background-color: #F45D22;
}
.color5 {
background-color: red;
background-color: #E0245E;
}
.fixed {
width: 20px;
height: 20px;
width: 6px;
height: 6px;
}

View File

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

View File

@@ -16,32 +16,33 @@ const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other
const styles = {
outer: {
alignSelf: 'flex-start',
padding: 4
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#222'
backgroundColor: '#14171A'
},
color1: {
backgroundColor: '#666'
backgroundColor: '#AAB8C2'
},
color2: {
backgroundColor: '#999'
backgroundColor: '#E6ECF0'
},
color3: {
backgroundColor: 'blue'
backgroundColor: '#FFAD1F'
},
color4: {
backgroundColor: 'orange'
backgroundColor: '#F45D22'
},
color5: {
backgroundColor: 'red'
backgroundColor: '#E0245E'
},
fixed: {
width: 20,
height: 20
width: 6,
height: 6
}
};

View File

@@ -9,8 +9,8 @@ const Dot = ({ size, x, y, children, color }) => (
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
left: `${x}px`,
top: `${y}px`
marginLeft: `${x}px`,
marginTop: `${y}px`
})}
>
{children}
@@ -25,7 +25,8 @@ const styles = {
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0
borderTopWidth: 0,
transform: 'translate(50%, 50%)'
}
};

View File

@@ -0,0 +1,2 @@
import View from './View';
export default View;

View File

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

View File

@@ -16,32 +16,33 @@ const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other
const styles = {
outer: {
alignSelf: 'flex-start',
padding: 4
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#222'
backgroundColor: '#14171A'
},
color1: {
backgroundColor: '#666'
backgroundColor: '#AAB8C2'
},
color2: {
backgroundColor: '#999'
backgroundColor: '#E6ECF0'
},
color3: {
backgroundColor: 'blue'
backgroundColor: '#FFAD1F'
},
color4: {
backgroundColor: 'orange'
backgroundColor: '#F45D22'
},
color5: {
backgroundColor: 'red'
backgroundColor: '#E0245E'
},
fixed: {
width: 20,
height: 20
width: 6,
height: 6
}
};

View File

@@ -9,8 +9,8 @@ const Dot = ({ size, x, y, children, color }) => (
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
left: `${x}px`,
top: `${y}px`
marginLeft: `${x}px`,
marginTop: `${y}px`
})}
>
{children}
@@ -25,7 +25,8 @@ const styles = {
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0
borderTopWidth: 0,
transform: 'translate(50%, 50%)'
}
};

View File

@@ -0,0 +1,2 @@
import View from './View';
export default View;

View File

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

View File

@@ -16,32 +16,33 @@ const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other
const styles = {
outer: {
alignSelf: 'flex-start',
padding: 4
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#222'
backgroundColor: '#14171A'
},
color1: {
backgroundColor: '#666'
backgroundColor: '#AAB8C2'
},
color2: {
backgroundColor: '#999'
backgroundColor: '#E6ECF0'
},
color3: {
backgroundColor: 'blue'
backgroundColor: '#FFAD1F'
},
color4: {
backgroundColor: 'orange'
backgroundColor: '#F45D22'
},
color5: {
backgroundColor: 'red'
backgroundColor: '#E0245E'
},
fixed: {
width: 20,
height: 20
width: 6,
height: 6
}
};

View File

@@ -10,8 +10,8 @@ const Dot = ({ size, x, y, children, color }) => (
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
left: `${x}px`,
top: `${y}px`
marginLeft: `${x}px`,
marginTop: `${y}px`
}
}}
>
@@ -27,7 +27,8 @@ const styles = {
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0
borderTopWidth: 0,
transform: 'translate(50%, 50%)'
}
};

View File

@@ -0,0 +1,2 @@
import View from './View';
export default View;

View File

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

View File

@@ -17,32 +17,33 @@ const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other
const styles = {
outer: {
alignSelf: 'flex-start',
padding: 4
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#222'
backgroundColor: '#14171A'
},
color1: {
backgroundColor: '#666'
backgroundColor: '#AAB8C2'
},
color2: {
backgroundColor: '#999'
backgroundColor: '#E6ECF0'
},
color3: {
backgroundColor: 'blue'
backgroundColor: '#FFAD1F'
},
color4: {
backgroundColor: 'orange'
backgroundColor: '#F45D22'
},
color5: {
backgroundColor: 'red'
backgroundColor: '#E0245E'
},
fixed: {
width: 20,
height: 20
width: 6,
height: 6
}
};

View File

@@ -11,8 +11,8 @@ const Dot = ({ size, x, y, children, color }) => (
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
left: `${x}px`,
top: `${y}px`
marginLeft: `${x}px`,
marginTop: `${y}px`
}
]}
>
@@ -28,7 +28,8 @@ const styles = {
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0
borderTopWidth: 0,
transform: 'translate(50%, 50%)'
}
};

View File

@@ -0,0 +1,2 @@
import View from './View';
export default View;

View File

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

View File

@@ -18,32 +18,33 @@ const Box = ({ classes, color, fixed = false, layout = 'column', outer = false,
const styles = {
outer: {
alignSelf: 'flex-start',
padding: 4
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#222'
backgroundColor: '#14171A'
},
color1: {
backgroundColor: '#666'
backgroundColor: '#AAB8C2'
},
color2: {
backgroundColor: '#999'
backgroundColor: '#E6ECF0'
},
color3: {
backgroundColor: 'blue'
backgroundColor: '#FFAD1F'
},
color4: {
backgroundColor: 'orange'
backgroundColor: '#F45D22'
},
color5: {
backgroundColor: 'red'
backgroundColor: '#E0245E'
},
fixed: {
width: 20,
height: 20
width: 6,
height: 6
}
};

View File

@@ -0,0 +1,2 @@
import View from './View';
export default View;

View File

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

View File

@@ -16,32 +16,33 @@ const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other
const styles = StyleSheet.create({
outer: {
alignSelf: 'flex-start',
padding: 4
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#222'
backgroundColor: '#14171A'
},
color1: {
backgroundColor: '#666'
backgroundColor: '#AAB8C2'
},
color2: {
backgroundColor: '#999'
backgroundColor: '#E6ECF0'
},
color3: {
backgroundColor: 'blue'
backgroundColor: '#FFAD1F'
},
color4: {
backgroundColor: 'orange'
backgroundColor: '#F45D22'
},
color5: {
backgroundColor: 'red'
backgroundColor: '#E0245E'
},
fixed: {
width: 20,
height: 20
width: 6,
height: 6
}
});

View File

@@ -11,8 +11,8 @@ const Dot = ({ size, x, y, children, color }) =>
borderRightWidth: size / 2,
borderBottomWidth: size / 2,
borderLeftWidth: size / 2,
left: x,
top: y
marginLeft: x,
marginTop: y
}
]
});
@@ -25,7 +25,8 @@ const styles = StyleSheet.create({
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0
borderTopWidth: 0,
transform: [{ translateX: '50%' }, { translateY: '50%' }]
}
});

View File

@@ -0,0 +1,2 @@
import { View } from 'react-native';
export default View;

View File

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

View File

@@ -16,32 +16,33 @@ const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other
const styles = {
outer: Styles.createViewStyle({
alignSelf: 'flex-start',
padding: 4
}),
row: Styles.createViewStyle({
flexDirection: 'row'
}),
color0: Styles.createViewStyle({
backgroundColor: '#222'
backgroundColor: '#14171A'
}),
color1: Styles.createViewStyle({
backgroundColor: '#666'
backgroundColor: '#AAB8C2'
}),
color2: Styles.createViewStyle({
backgroundColor: '#999'
backgroundColor: '#E6ECF0'
}),
color3: Styles.createViewStyle({
backgroundColor: 'blue'
backgroundColor: '#FFAD1F'
}),
color4: Styles.createViewStyle({
backgroundColor: 'orange'
backgroundColor: '#F45D22'
}),
color5: Styles.createViewStyle({
backgroundColor: 'red'
backgroundColor: '#E0245E'
}),
fixed: Styles.createViewStyle({
width: 20,
height: 20
width: 6,
height: 6
})
};

View File

@@ -12,8 +12,8 @@ const Dot = ({ size, x, y, children, color }) => (
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
left: `${x}px`,
top: `${y}px`
marginLeft: `${x}px`,
marginTop: `${y}px`
}
]}
/>
@@ -27,7 +27,8 @@ const styles = {
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0
borderTopWidth: 0,
transform: 'translate(50%, 50%)'
})
};

View File

@@ -0,0 +1,31 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { object } from 'prop-types';
import { View } from 'reactxp';
class Provider extends React.Component {
/* this mock context is necessary for reactxp to work without errors… ¯\_(ツ)_/¯ */
static childContextTypes = {
focusManager: object
};
getChildContext() {
return {
focusManager: {
addFocusableComponent() {},
removeFocusableComponent() {},
restrictFocusWithin() {},
removeFocusRestriction() {},
limitFocusWithin() {},
removeFocusLimitation() {}
}
};
}
render() {
return <View style={{ overflow: 'visible' }}>{this.props.children}</View>;
}
}
export default Provider;

View File

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

View File

@@ -4,28 +4,28 @@ import View from './View';
const getColor = color => {
switch (color) {
case 0:
return '#222';
return '#14171A';
case 1:
return '#666';
return '#AAB8C2';
case 2:
return '#999';
return '#E6ECF0';
case 3:
return 'blue';
return '#FFAD1F';
case 4:
return 'orange';
return '#F45D22';
case 5:
return 'red';
return '#E0245E';
default:
return 'transparent';
}
};
const Box = styled(View)`
align-self: flex-start;
flex-direction: ${props => (props.layout === 'column' ? 'column' : 'row')};
padding: ${props => (props.outer ? '4px' : '0')};
height: ${props => (props.fixed ? '20px' : 'auto')};
width: ${props => (props.fixed ? '20px' : 'auto')};
background-color: ${props => getColor(props.color)};
${props => props.fixed && 'height:6px;'} ${props =>
props.fixed && 'width:6px;'} background-color: ${props => getColor(props.color)};
`;
export default Box;

View File

@@ -4,8 +4,8 @@ import View from './View';
const Dot = styled(View).attrs({
style: props => ({
left: `${props.x}px`,
top: `${props.y}px`,
marginLeft: `${props.x}px`,
marginTop: `${props.y}px`,
borderRightWidth: `${props.size / 2}px`,
borderBottomWidth: `${props.size / 2}px`,
borderLeftWidth: `${props.size / 2}px`,
@@ -19,6 +19,7 @@ const Dot = styled(View).attrs({
border-color: transparent;
border-style: solid;
border-top-width: 0;
transform: translate(50%, 50%);
`;
export default Dot;

View File

@@ -0,0 +1,2 @@
import View from './View';
export default View;

View File

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

View File

@@ -0,0 +1,47 @@
/* eslint-disable react/prop-types */
import { styled } from 'styletron-react';
import View from './View';
const Box = styled(
View,
({ color, fixed = false, layout = 'column', outer = false, ...other }) => ({
...styles[`color${color}`],
...(fixed && styles.fixed),
...(layout === 'row' && styles.row),
...(outer && styles.outer)
})
);
const styles = {
outer: {
alignSelf: 'flex-start',
padding: '4px'
},
row: {
flexDirection: 'row'
},
color0: {
backgroundColor: '#14171A'
},
color1: {
backgroundColor: '#AAB8C2'
},
color2: {
backgroundColor: '#E6ECF0'
},
color3: {
backgroundColor: '#FFAD1F'
},
color4: {
backgroundColor: '#F45D22'
},
color5: {
backgroundColor: '#E0245E'
},
fixed: {
width: '6px',
height: '6px'
}
};
export default Box;

View File

@@ -0,0 +1,25 @@
/* eslint-disable react/prop-types */
import { styled } from 'styletron-react';
const Dot = styled('div', ({ size, x, y, children, color }) => ({
...staticStyle,
borderBottomColor: color,
borderRightWidth: `${size / 2}px`,
borderBottomWidth: `${size / 2}px`,
borderLeftWidth: `${size / 2}px`,
marginLeft: `${x}px`,
marginTop: `${y}px`
}));
const staticStyle = {
position: 'absolute',
cursor: 'pointer',
width: 0,
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0,
transform: 'translate(50%, 50%)'
};
export default Dot;

View File

@@ -0,0 +1,19 @@
/* eslint-disable react/prop-types */
import React from 'react';
import Styletron from 'styletron-client';
import { StyletronProvider } from 'styletron-react';
import View from './View';
const styletron = new Styletron();
class Provider extends React.Component {
render() {
return (
<StyletronProvider styletron={styletron}>
<View>{this.props.children}</View>
</StyletronProvider>
);
}
}
export default Provider;

View File

@@ -0,0 +1,26 @@
/* eslint-disable react/prop-types */
import { styled } from 'styletron-react';
const View = styled('div', ({ style }) => ({
...viewStyle,
style
}));
const viewStyle = {
alignItems: 'stretch',
borderWidth: '0px',
borderStyle: 'solid',
boxSizing: 'border-box',
display: 'flex',
flexBasis: 'auto',
flexDirection: 'column',
flexShrink: '0',
margin: '0px',
padding: '0px',
position: 'relative',
// fix flexbox bugs
minHeight: '0px',
minWidth: '0px'
};
export default View;

View File

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

View File

@@ -1,49 +0,0 @@
/* eslint-disable react/prop-types */
import { injectStylePrefixed } from 'styletron-utils';
import React from 'react';
import View, { styletron } 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: injectStylePrefixed(styletron, {
padding: '4px'
}),
row: injectStylePrefixed(styletron, {
flexDirection: 'row'
}),
color0: injectStylePrefixed(styletron, {
backgroundColor: '#222'
}),
color1: injectStylePrefixed(styletron, {
backgroundColor: '#666'
}),
color2: injectStylePrefixed(styletron, {
backgroundColor: '#999'
}),
color3: injectStylePrefixed(styletron, {
backgroundColor: 'blue'
}),
color4: injectStylePrefixed(styletron, {
backgroundColor: 'orange'
}),
color5: injectStylePrefixed(styletron, {
backgroundColor: 'red'
}),
fixed: injectStylePrefixed(styletron, {
width: '20px',
height: '20px'
})
};
export default Box;

View File

@@ -1,37 +0,0 @@
/* 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

@@ -1,33 +0,0 @@
/* eslint-disable react/prop-types */
import classnames from 'classnames';
import Styletron from 'styletron-client';
import { injectStylePrefixed } from 'styletron-utils';
import React from 'react';
export const styletron = new Styletron();
class View extends React.Component {
render() {
const { style, ...other } = this.props;
return <div {...other} className={classnames(viewStyle, ...style)} />;
}
}
const viewStyle = injectStylePrefixed(styletron, {
alignItems: 'stretch',
borderWidth: '0px',
borderStyle: 'solid',
boxSizing: 'border-box',
display: 'flex',
flexBasis: 'auto',
flexDirection: 'column',
flexShrink: '0',
margin: '0px',
padding: '0px',
position: 'relative',
// fix flexbox bugs
minHeight: '0px',
minWidth: '0px'
});
export default View;

View File

@@ -1,97 +1,56 @@
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 App from './app/App';
import impl from './impl';
import Tree from './cases/Tree';
import SierpinskiTriangle from './cases/SierpinskiTriangle';
import renderDeepTree from './cases/renderDeepTree';
import renderSierpinskiTriangle from './cases/renderSierpinskiTriangle';
import renderWideTree from './cases/renderWideTree';
import React from 'react';
import ReactDOM from 'react-dom';
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)
],
const implementations = impl;
const packageNames = Object.keys(implementations);
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 createTestBlock = fn => {
return packageNames.reduce((testSetups, packageName) => {
const { name, components, version } = implementations[packageName];
const { Component, getComponentProps, sampleCount, Provider, benchmarkType } = fn(components);
testSetups[packageName] = {
Component,
getComponentProps,
sampleCount,
Provider,
benchmarkType,
version,
name
};
return testSetups;
}, {});
};
const allTests = Object.keys(testMatrix).reduce((acc, curr) => {
testMatrix[curr].forEach(test => {
acc.push(test);
});
return acc;
}, []);
const tests = {
'Mount deep tree': createTestBlock(components => ({
benchmarkType: 'mount',
Component: Tree,
getComponentProps: () => ({ breadth: 2, components, depth: 7, id: 0, wrap: 1 }),
Provider: components.Provider,
sampleCount: 50
})),
'Mount wide tree': createTestBlock(components => ({
benchmarkType: 'mount',
Component: Tree,
getComponentProps: () => ({ breadth: 6, components, depth: 3, id: 0, wrap: 2 }),
Provider: components.Provider,
sampleCount: 50
})),
'Update dynamic styles': createTestBlock(components => ({
benchmarkType: 'update',
Component: SierpinskiTriangle,
getComponentProps: ({ cycle }) => {
return { components, s: 200, renderCount: cycle, x: 0, y: 0 };
},
Provider: components.Provider,
sampleCount: 100
}))
};
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());
ReactDOM.render(<App tests={tests} />, document.querySelector('.root'));

View File

@@ -30,7 +30,7 @@ module.exports = {
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheDirectory: false,
presets: babelPreset,
plugins: ['react-native-web']
}
@@ -49,14 +49,10 @@ module.exports = {
new webpack.optimize.UglifyJsPlugin({
compress: {
dead_code: true,
drop_console: true,
screw_ie8: true,
warnings: false
}
})
],
resolve: {
alias: {
'react-native': 'react-native-web'
}
}
]
};

View File

@@ -42,7 +42,7 @@ class KeyboardAvoidingView extends Component<*> {
onLayout = (event: ViewLayoutEvent) => {
this.frame = event.nativeEvent.layout;
}
};
render() {
const {

156
yarn.lock
View File

@@ -430,7 +430,7 @@ anymatch@^1.3.0:
micromatch "^2.1.5"
normalize-path "^2.0.0"
aphrodite@^1.2.5:
aphrodite@1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/aphrodite/-/aphrodite-1.2.5.tgz#8358c36c80bb03aee9b97165aaa70186225b4983"
dependencies:
@@ -659,7 +659,7 @@ babel-cli@^6.26.0:
optionalDependencies:
chokidar "^1.6.1"
babel-code-frame@^6.11.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
dependencies:
@@ -2610,25 +2610,6 @@ css-in-js-utils@^2.0.0:
dependencies:
hyphenate-style-name "^1.0.2"
css-loader@^0.28.7:
version "0.28.7"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.7.tgz#5f2ee989dd32edd907717f953317656160999c1b"
dependencies:
babel-code-frame "^6.11.0"
css-selector-tokenizer "^0.7.0"
cssnano ">=2.6.1 <4"
icss-utils "^2.1.0"
loader-utils "^1.0.2"
lodash.camelcase "^4.3.0"
object-assign "^4.0.1"
postcss "^5.0.6"
postcss-modules-extract-imports "^1.0.0"
postcss-modules-local-by-default "^1.0.1"
postcss-modules-scope "^1.0.0"
postcss-modules-values "^1.1.0"
postcss-value-parser "^3.3.0"
source-list-map "^2.0.0"
css-loader@^0.28.8:
version "0.28.8"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.8.tgz#ff36381464dea18fe60f2601a060ba6445886bd5"
@@ -2648,6 +2629,25 @@ css-loader@^0.28.8:
postcss-value-parser "^3.3.0"
source-list-map "^2.0.0"
css-loader@^0.28.9:
version "0.28.9"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.9.tgz#68064b85f4e271d7ce4c48a58300928e535d1c95"
dependencies:
babel-code-frame "^6.26.0"
css-selector-tokenizer "^0.7.0"
cssnano "^3.10.0"
icss-utils "^2.1.0"
loader-utils "^1.0.2"
lodash.camelcase "^4.3.0"
object-assign "^4.1.1"
postcss "^5.0.6"
postcss-modules-extract-imports "^1.2.0"
postcss-modules-local-by-default "^1.2.0"
postcss-modules-scope "^1.1.0"
postcss-modules-values "^1.3.0"
postcss-value-parser "^3.3.0"
source-list-map "^2.0.0"
css-select@^1.1.0, css-select@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
@@ -2687,7 +2687,7 @@ cssesc@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4"
"cssnano@>=2.6.1 <4", cssnano@^3.10.0:
cssnano@^3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38"
dependencies:
@@ -3107,7 +3107,7 @@ emotion-utils@^8.0.12:
version "8.0.12"
resolved "https://registry.yarnpkg.com/emotion-utils/-/emotion-utils-8.0.12.tgz#5e0fd72db3008f26ce4f80b1972df08841df2168"
emotion@^8.0.12:
emotion@8.0.12:
version "8.0.12"
resolved "https://registry.yarnpkg.com/emotion/-/emotion-8.0.12.tgz#03de11ce26b1b2401c334b94d438652124c514c6"
dependencies:
@@ -3323,6 +3323,12 @@ eslint-config-prettier@^2.9.0:
dependencies:
get-stdin "^5.0.1"
eslint-plugin-cup@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-cup/-/eslint-plugin-cup-1.0.0.tgz#6ceced9a06d29e6e7bdc76ca9e398c9bf53072be"
dependencies:
globals "^10.0.0"
eslint-plugin-promise@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.6.0.tgz#54b7658c8f454813dc2a870aff8152ec4969ba75"
@@ -3925,7 +3931,7 @@ gitconfiglocal@^1.0.0:
dependencies:
ini "^1.3.2"
glamor@^2.20.40:
glamor@2.20.40, glamor@^2.20.40:
version "2.20.40"
resolved "https://registry.yarnpkg.com/glamor/-/glamor-2.20.40.tgz#f606660357b7cf18dface731ad1a2cfa93817f05"
dependencies:
@@ -4394,7 +4400,7 @@ inline-style-prefixer@^2.0.5:
bowser "^1.0.0"
hyphenate-style-name "^1.0.1"
inline-style-prefixer@^3.0.1, inline-style-prefixer@^3.0.3, inline-style-prefixer@^3.0.6:
inline-style-prefixer@^3.0.1, inline-style-prefixer@^3.0.6:
version "3.0.8"
resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz#8551b8e5b4d573244e66a34b04f7d32076a2b534"
dependencies:
@@ -5594,10 +5600,6 @@ marked@^0.3.9:
version "0.3.12"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.12.tgz#7cf25ff2252632f3fe2406bde258e94eee927519"
marky@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.0.tgz#9617ed647bbbea8f45d19526da33dec70606df42"
math-expression-evaluator@^1.2.14:
version "1.2.17"
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
@@ -6501,27 +6503,33 @@ postcss-minify-selectors@^2.0.4:
postcss "^5.0.14"
postcss-selector-parser "^2.0.0"
postcss-modules-extract-imports@^1.0.0, postcss-modules-extract-imports@^1.1.0:
postcss-modules-extract-imports@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.1.0.tgz#b614c9720be6816eaee35fb3a5faa1dba6a05ddb"
dependencies:
postcss "^6.0.1"
postcss-modules-local-by-default@^1.0.1, postcss-modules-local-by-default@^1.2.0:
postcss-modules-extract-imports@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz#66140ecece38ef06bf0d3e355d69bf59d141ea85"
dependencies:
postcss "^6.0.1"
postcss-modules-local-by-default@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069"
dependencies:
css-selector-tokenizer "^0.7.0"
postcss "^6.0.1"
postcss-modules-scope@^1.0.0, postcss-modules-scope@^1.1.0:
postcss-modules-scope@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90"
dependencies:
css-selector-tokenizer "^0.7.0"
postcss "^6.0.1"
postcss-modules-values@^1.1.0, postcss-modules-values@^1.3.0:
postcss-modules-values@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20"
dependencies:
@@ -6776,7 +6784,16 @@ querystring@0.2.0, querystring@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
radium@^0.19.0, radium@^0.19.6:
radium@0.21.0:
version "0.21.0"
resolved "https://registry.yarnpkg.com/radium/-/radium-0.21.0.tgz#15ad5f7dccdc9b5a4d09e3c8f23dddcd8717f15b"
dependencies:
array-find "^1.0.0"
exenv "^1.2.1"
inline-style-prefixer "^4.0.0"
prop-types "^15.5.8"
radium@^0.19.0:
version "0.19.6"
resolved "https://registry.yarnpkg.com/radium/-/radium-0.19.6.tgz#b86721d08dbd303b061a4ae2ebb06cc6e335ae72"
dependencies:
@@ -6855,6 +6872,10 @@ react-art@^16.2.0:
object-assign "^4.1.1"
prop-types "^15.6.0"
react-component-benchmark@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/react-component-benchmark/-/react-component-benchmark-0.0.4.tgz#eee3585e2203645ed968a40020f607d89bc82f64"
react-deep-force-update@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-1.1.1.tgz#bcd31478027b64b3339f108921ab520b4313dc2c"
@@ -6912,9 +6933,9 @@ react-inspector@^2.2.2:
babel-runtime "^6.26.0"
is-dom "^1.0.9"
react-jss@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/react-jss/-/react-jss-8.2.0.tgz#8440f08aef27d408ba31f63df09167ed22a5b99b"
react-jss@8.2.1:
version "8.2.1"
resolved "https://registry.yarnpkg.com/react-jss/-/react-jss-8.2.1.tgz#b2905063ba8b950f095a139a75232d851c79e15e"
dependencies:
hoist-non-react-statics "^2.3.1"
jss "^9.3.2"
@@ -7011,9 +7032,9 @@ react@^16.2.0:
object-assign "^4.1.1"
prop-types "^15.6.0"
reactxp@^0.46.6:
version "0.46.6"
resolved "https://registry.yarnpkg.com/reactxp/-/reactxp-0.46.6.tgz#166a503a7147f3a1e29efc4469bda32603471a5f"
reactxp@0.51.0-alpha.9:
version "0.51.0-alpha.9"
resolved "https://registry.yarnpkg.com/reactxp/-/reactxp-0.51.0-alpha.9.tgz#471ee6e7be65fe2461143eb6f49413cbb321b327"
dependencies:
"@types/lodash" "^4.14.78"
"@types/react" "^16.0.0"
@@ -7839,7 +7860,7 @@ style-loader@^0.19.1:
loader-utils "^1.0.2"
schema-utils "^0.3.0"
styled-components@^2.3.2:
styled-components@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-2.4.0.tgz#086d0fd483d54638837fca3ea546a030b94adf75"
dependencies:
@@ -7852,21 +7873,38 @@ styled-components@^2.3.2:
stylis "^3.4.0"
supports-color "^3.2.3"
styletron-client@^3.0.0-rc.5:
version "3.0.0-rc.5"
resolved "https://registry.yarnpkg.com/styletron-client/-/styletron-client-3.0.0-rc.5.tgz#275ca0b5f971d244f0e42079ad570be9c31a2a70"
styletron-client@3.0.2, styletron-client@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/styletron-client/-/styletron-client-3.0.2.tgz#9b2853e8b94e6e94d70166b8403f27ab2d10c514"
dependencies:
styletron-core "^3.0.0-rc.3"
styletron-core "^3.0.2"
styletron-core@^3.0.0-rc.3:
version "3.0.0-rc.3"
resolved "https://registry.yarnpkg.com/styletron-core/-/styletron-core-3.0.0-rc.3.tgz#9468e275d9085d2e5d6d6468cc6d8733dbfa3cba"
styletron-core@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/styletron-core/-/styletron-core-3.0.2.tgz#67513feed50fb39753f72e547d00dfdb619ae79f"
styletron-utils@^3.0.0-rc.3:
version "3.0.0-rc.3"
resolved "https://registry.yarnpkg.com/styletron-utils/-/styletron-utils-3.0.0-rc.3.tgz#21fef2099f1c368e6ff2b8f76bf7a64bb547b760"
styletron-react@3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/styletron-react/-/styletron-react-3.0.3.tgz#67d2932db82972e0ea60544b7d5b8c6d5aecd3a3"
dependencies:
inline-style-prefixer "^3.0.3"
"@types/react" "*"
eslint-plugin-cup "^1.0.0"
prop-types "^15.6.0"
styletron-client "^3.0.2"
styletron-server "^3.0.2"
styletron-utils "^3.0.2"
styletron-server@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/styletron-server/-/styletron-server-3.0.2.tgz#0b989c833ed48437d68b728f65e9406fb8802de6"
dependencies:
styletron-core "^3.0.2"
styletron-utils@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/styletron-utils/-/styletron-utils-3.0.2.tgz#3bd18f60d8a8534e19d500ffa052a2f1c6c31198"
dependencies:
inline-style-prefixer "^4.0.0"
stylis-rule-sheet@^0.0.5:
version "0.0.5"
@@ -8410,9 +8448,9 @@ webidl-conversions@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
webpack-bundle-analyzer@^2.9.1:
version "2.9.1"
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.9.1.tgz#c2c8e03e8e5768ed288b39ae9e27a8b8d7b9d476"
webpack-bundle-analyzer@^2.9.2:
version "2.9.2"
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-2.9.2.tgz#63ed86eb71cc4cda86f68e685a84530ba0126449"
dependencies:
acorn "^5.1.1"
chalk "^1.1.3"
@@ -8424,7 +8462,7 @@ webpack-bundle-analyzer@^2.9.1:
lodash "^4.17.4"
mkdirp "^0.5.1"
opener "^1.4.3"
ws "^3.3.1"
ws "^4.0.0"
webpack-dev-middleware@^1.12.2:
version "1.12.2"
@@ -8588,9 +8626,9 @@ write@^0.2.1:
dependencies:
mkdirp "^0.5.1"
ws@^3.3.1:
version "3.3.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
ws@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-4.0.0.tgz#bfe1da4c08eeb9780b986e0e4d10eccd7345999f"
dependencies:
async-limiter "~1.0.0"
safe-buffer "~5.1.0"