Compare commits

..

28 Commits
0.3.0 ... 0.3.3

Author SHA1 Message Date
Nicolas Gallagher
e4e6147081 0.3.3 2018-01-19 14:05:46 -08:00
Nicolas Gallagher
3e1b68d801 Add Moto G4 example benchmark results 2018-01-19 13:59:13 -08:00
Giuseppe Gurgone
1b493c9914 Add styled-jsx to benchmarks
Close #782
2018-01-19 12:51:21 -08:00
hushicai
6ecdc1a517 [fix] babel-plugin VariableDeclaration case
Convert VariableDeclarations, e.g.,

var ReactNative = require('react-native');

Close #781
2018-01-19 00:09:43 -08:00
Nicolas Gallagher
619079cedf Move babel-plugin npm badge to correct README 2018-01-18 10:24:02 -08:00
Javi Velasco
bbf7674b43 Add react-fela to benchmarks
Close #779
2018-01-18 10:03:13 -08:00
Nicolas Gallagher
f163e4f16f Use full URLs for README links 2018-01-18 09:50:14 -08:00
Nicolas Gallagher
bd8c2d6f24 Add npm badge to babel-plugin-react-native-web README 2018-01-18 09:49:54 -08:00
Nicolas Gallagher
3906b6b41b Add benchmarks app link to README 2018-01-17 19:38:58 -08:00
Nicolas Gallagher
753ef963f6 0.3.2 2018-01-17 19:18:50 -08:00
Nicolas Gallagher
0721245b3e Patch release script
Disable husky on version commit. Current publish script doesn't account
for attempts to republish the same version.
2018-01-17 19:15:23 -08:00
Nicolas Gallagher
b7adfd5f32 [fix] Picker.Item label text
Render the label as a child of 'option' rather than using the 'label'
attribute.

Fix #774
2018-01-17 17:59:05 -08:00
Nicolas Gallagher
a9342daee2 Add release script for benchmarks 2018-01-17 17:40:13 -08:00
Nicolas Gallagher
ed0cafac7c 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.
2018-01-17 17:21:53 -08:00
Nicolas Gallagher
6e6fd4b5d0 Add note about Object.assign polyfill to docs 2018-01-16 17:14:17 -08:00
Nicolas Gallagher
5cd533e6cc 0.3.1 2018-01-16 11:15:45 -08:00
Nicolas Gallagher
d5e8d85ce9 Fix example in babel plugin README 2018-01-16 11:11:50 -08:00
Nicolas Gallagher
e234568a34 Fix source links in documentation 2018-01-11 12:59:44 -08:00
Nicolas Gallagher
19cf0711bc [add] StyleSheet support for 'overscrollBehavior'
An experimental CSS property to control the behavior when the scroll
position of a scroll container reaches the edge of the scrollport. This
allows web apps to get closer to native scrolling behaviour and
performance.

https://wicg.github.io/overscroll-behavior/
https://developers.google.com/web/updates/2017/11/overscroll-behavior

Fix #765
2018-01-11 12:58:43 -08:00
Johannes
067e3f346f [fix] KeyboardAvoidingView missing 'this' binding
Close #762
2018-01-11 12:53:54 -08:00
Nicolas Gallagher
2117e44e9d [fix] limit Image loader deferral to 1000ms
This patch introduces a limit on how long image loading is deferred, and
mitigates an issue with lengthy delays to 'requestIdleCallback' in the
Chrome browser.

Fix #759
2018-01-11 12:08:30 -08:00
Nicolas Gallagher
902ba22877 Update to flow-bin@0.63.1 2018-01-09 17:49:52 -08:00
Nicolas Gallagher
60c2cd65df Update to lint-staged@6.0.0 2018-01-09 17:36:19 -08:00
Nicolas Gallagher
fde29326f1 Update to lerna@2.6.0 2018-01-09 17:35:53 -08:00
Nicolas Gallagher
44d795437e Update to storybook@3.3.6 2018-01-09 17:34:28 -08:00
Nicolas Gallagher
03598d869b Update to babel-plugin-tester@5.0.0 2018-01-09 17:31:52 -08:00
Nicolas Gallagher
a3e44a5c60 Update to enzyme@3.3.0 2018-01-09 17:27:38 -08:00
Nicolas Gallagher
02b124eceb Update yarn.lock 2018-01-08 18:53:05 -08:00
109 changed files with 2834 additions and 1151 deletions

View File

@@ -1,5 +1,5 @@
[version]
^0.61.0
^0.63.0
[ignore]
<PROJECT_ROOT>/.*/__tests__/.*
@@ -14,4 +14,4 @@
<PROJECT_ROOT>/types
[options]
unsafe.enable_getters_and_setters=true

View File

@@ -74,7 +74,7 @@ yarn compile --watch
To run the interactive storybook:
```
yarn docs:start
yarn website
```
When you're also making changes to the 'react-native-web' source files, run this command in another process:
@@ -88,7 +88,7 @@ yarn compile --watch
To run the performance benchmarks in a browser (opening `./packages/benchmarks/index.html`):
```
yarn benchmark
yarn benchmarks
```
### New Features

View File

@@ -6,10 +6,11 @@
[React Native][react-native-url] to the Web.
* **High-quality user interfaces**: React Native for Web makes it easy to
create [fast](packages/benchmarks/README.md), adaptive web UIs in JavaScript.
It provides native-like interactions, support for multiple input modes (touch,
mouse, keyboard), optimized vendor-prefixed styles, built-in support for RTL
layout, built-in accessibility, and integrates with React Dev Tools.
create [fast](https://github.com/necolas/react-native-web/blob/master/packages/benchmarks/README.md),
adaptive web UIs in JavaScript. It provides native-like interactions, support
for multiple input modes (touch, mouse, keyboard), optimized vendor-prefixed
styles, built-in support for RTL layout, built-in accessibility, and integrates
with React Dev Tools.
* **Write once, render anywhere**: React Native for Web interoperates with
existing React DOM components and is compatible with the majority of the
@@ -54,17 +55,18 @@ yarn add --dev babel-plugin-react-native-web
### Guides
* [Getting started](website/guides/getting-started.md)
* [Style](website/guides/style.md)
* [Accessibility](website/guides/accessibility.md)
* [Internationalization](website/guides/internationalization.md)
* [Direct manipulation](website/guides/direct-manipulation.md)
* [Advanced use](website/guides/advanced.md)
* [Getting started](https://github.com/necolas/react-native-web/blob/master/website/guides/getting-started.md)
* [Style](https://github.com/necolas/react-native-web/blob/master/website/guides/style.md)
* [Accessibility](https://github.com/necolas/react-native-web/blob/master/website/guides/accessibility.md)
* [Internationalization](https://github.com/necolas/react-native-web/blob/master/website/guides/internationalization.md)
* [Direct manipulation](https://github.com/necolas/react-native-web/blob/master/website/guides/direct-manipulation.md)
* [Advanced use](https://github.com/necolas/react-native-web/blob/master/website/guides/advanced.md)
## Examples
There are several examples [on the website][website-url] and in the [website's
source code](./website). Here is an example to get you started:
source code](https://github.com/necolas/react-native-web/blob/master/website).
Here is an example to get you started:
```js
import React from 'react';
@@ -132,6 +134,6 @@ React Native for Web is [BSD licensed](./LICENSE).
[ci-url]: https://travis-ci.org/necolas/react-native-web
[website-url]: https://necolas.github.io/react-native-web/storybook/
[react-native-url]: https://facebook.github.io/react-native/
[contributing-url]: ./.github/CONTRIBUTING.md
[contributing-url]: https://github.com/necolas/react-native-web/blob/master/.github/CONTRIBUTING.md
[good-first-issue-url]: https://github.com/necolas/react-native-web/labels/good%20first%20issue
[code-of-conduct]: https://code.facebook.com/codeofconduct

View File

@@ -1,6 +1,6 @@
{
"lerna": "2.5.1",
"version": "0.3.0",
"version": "0.3.3",
"npmClient": "yarn",
"useWorkspaces": true,
"packages": [

View File

@@ -2,11 +2,12 @@
"private": true,
"name": "react-native-web-monorepo",
"scripts": {
"benchmark": "cd packages/benchmarks && yarn benchmark",
"clean": "del ./packages/*/dist",
"compile": "yarn clean && cd packages/react-native-web && babel src --out-dir dist --ignore \"**/__tests__\"",
"docs:start": "cd website && yarn start",
"docs:release": "cd website && yarn release",
"benchmarks": "cd packages/benchmarks && yarn build",
"benchmarks:release": "cd packages/benchmarks && yarn release",
"website": "cd website && yarn start",
"website:release": "cd website && yarn release",
"flow": "flow",
"fmt": "find packages scripts types website -name '*.js' | grep -v -E '(node_modules|dist|vendor)' | xargs yarn fmt:cmd",
"fmt:cmd": "prettier --write",
@@ -16,7 +17,7 @@
"precommit": "lint-staged",
"prerelease": "yarn test && yarn compile",
"release": "node ./scripts/release/publish.js",
"postrelease": "yarn docs:release",
"postrelease": "yarn website:release && yarn benchmarks:release",
"test": "yarn flow && yarn lint:check && yarn jest"
},
"devDependencies": {
@@ -33,18 +34,18 @@
"babel-preset-react-native": "^4.0.0",
"caniuse-api": "^2.0.0",
"del-cli": "^1.1.0",
"enzyme": "^3.2.0",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.0",
"enzyme-to-json": "^3.2.2",
"eslint": "^4.12.1",
"eslint-config-prettier": "^2.9.0",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-react": "^7.5.1",
"flow-bin": "^0.61.0",
"flow-bin": "^0.63.1",
"husky": "^0.14.3",
"jest": "^21.2.1",
"lerna": "^2.5.1",
"lint-staged": "^4.1.3",
"lerna": "^2.6.0",
"lint-staged": "^6.0.0",
"prettier": "^1.8.2",
"raf": "^3.4.0",
"react": "^16.2.0",

View File

@@ -1,5 +1,7 @@
# babel-plugin-react-native-web
[![npm version][package-badge]][package-url] [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
A Babel plugin that will alias `react-native` to `react-native-web` and exclude
any modules not required by your app (keeping bundle size down).
@@ -34,6 +36,9 @@ import { StyleSheet, View } from 'react-native';
**After**
```js
import StyleSheet from 'react-native-web/dist/apis/StyleSheet';
import View from 'react-native-web/dist/components/View';
import StyleSheet from 'react-native-web/dist/exports/StyleSheet';
import View from 'react-native-web/dist/exports/View';
```
[package-badge]: https://img.shields.io/npm/v/babel-plugin-react-native-web.svg?style=flat
[package-url]: https://yarnpkg.com/en/package/babel-plugin-react-native-web

View File

@@ -1,10 +1,10 @@
{
"name": "babel-plugin-react-native-web",
"version": "0.3.0",
"version": "0.3.3",
"description": "Babel plugin for React Native for Web",
"main": "index.js",
"devDependencies": {
"babel-plugin-tester": "^4.0.0"
"babel-plugin-tester": "^5.0.0"
},
"author": "Nicolas Gallagher",
"license": "MIT",

View File

@@ -66,6 +66,24 @@ import * as ReactNativeModules from 'react-native-web/dist/index';
"
`;
exports[`require "react-native" 1`] = `
"
const ReactNative = require('react-native');
const { View } = require('react-native');
const { StyleSheet, TouchableOpacity } = require('react-native');
↓ ↓ ↓ ↓ ↓ ↓
const ReactNative = require('react-native-web/dist/index');
const View = require('react-native-web/dist/exports/View');
const StyleSheet = require('react-native-web/dist/exports/StyleSheet');
const TouchableOpacity = require('react-native-web/dist/exports/TouchableOpacity');
"
`;
exports[`require "react-native-web" 1`] = `
"
const ReactNative = require('react-native-web');
@@ -74,7 +92,7 @@ const { ColorPropType, StyleSheet, View, TouchableOpacity, processColor } = requ
↓ ↓ ↓ ↓ ↓ ↓
const ReactNative = require('react-native-web');
const ReactNative = require('react-native-web/dist/index');
const createElement = require('react-native-web/dist/exports/createElement');

View File

@@ -30,6 +30,13 @@ export { ColorPropType, StyleSheet, Text, createElement } from 'react-native';`,
export { ColorPropType, StyleSheet, Text, createElement } from 'react-native-web';`,
snapshot: true
},
{
title: 'require "react-native"',
code: `const ReactNative = require('react-native');
const { View } = require('react-native');
const { StyleSheet, TouchableOpacity } = require('react-native');`,
snapshot: true
},
{
title: 'require "react-native-web"',
code: `const ReactNative = require('react-native-web');

View File

@@ -8,7 +8,7 @@ const isReactNativeRequire = (t, node) => {
}
const { id, init } = declarations[0];
return (
t.isObjectPattern(id) &&
(t.isObjectPattern(id) || t.isIdentifier(id)) &&
t.isCallExpression(init) &&
t.isIdentifier(init.callee) &&
init.callee.name === 'require' &&
@@ -84,21 +84,35 @@ module.exports = function({ types: t }) {
VariableDeclaration(path, state) {
if (isReactNativeRequire(t, path.node)) {
const { id } = path.node.declarations[0];
const imports = id.properties
.map(identifier => {
const distLocation = getDistLocation(identifier.key.name);
if (distLocation) {
return t.variableDeclaration(path.node.kind, [
t.variableDeclarator(
t.identifier(identifier.value.name),
t.callExpression(t.identifier('require'), [t.stringLiteral(distLocation)])
)
]);
}
})
.filter(Boolean);
if (t.isObjectPattern(id)) {
const imports = id.properties
.map(identifier => {
const distLocation = getDistLocation(identifier.key.name);
if (distLocation) {
return t.variableDeclaration(path.node.kind, [
t.variableDeclarator(
t.identifier(identifier.value.name),
t.callExpression(t.identifier('require'), [t.stringLiteral(distLocation)])
)
]);
}
})
.filter(Boolean);
path.replaceWithMultiple(imports);
path.replaceWithMultiple(imports);
} else if (t.isIdentifier(id)) {
const name = id.name;
const importIndex = t.variableDeclaration(path.node.kind, [
t.variableDeclarator(
t.identifier(name),
t.callExpression(t.identifier('require'), [
t.stringLiteral('react-native-web/dist/index')
])
)
]);
path.replaceWith(importIndex);
}
}
}
}

View File

@@ -1,56 +1,92 @@
# benchmarks
To run these benchmarks:
Try the [benchmarks app](https://necolas.github.io/react-native-web/benchmarks) online.
To run the benchmarks locally:
```
yarn benchmark
open ./packages/benchmarks/dist/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_.
## Results
No benchmark will run for more than 20 seconds.
Typical render timings*: mean ± two standard deviations.
## Example results
| Implementation | Deep tree (ms) | Wide tree (ms) | Triangle (ms) |
### MacBook Pro (2011)
MacBook Pro (13-inch, Early 2011); 2.3 GHz Intel Core i5; 8 GB 1333 MHz DDR3 RAM. Google Chrome 63.
Typical render timings*: mean ± standard deviations.
| Implementation | Mount deep tree (ms) | Mount wide tree (ms) | Dynamic update (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.2` | `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) | Dynamic update (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-fela@5.0.0` | `27.58` `±04.26` | `39.54` `±05.46` | `10.93` `±01.69` |
| `react-jss@8.2.1` | `27.31` 07.87` | `40.74` 10.67` | - |
| `styled-jsx@2.2.1` | `27.46` 07.85` | `41.47` 11.53` | `29.16` 09.98` |
| `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
significantly faster than alternatives.
### Moto G4
*MacBook Pro (13-inch, Early 2011); 2.3 GHz Intel Core i5; 8 GB 1333 MHz DDR3. Google Chrome 62.
Moto G4 (Android 7); Octa-core (4x1.5 GHz & 4x1.2 Ghz); 2 GB RAM. Google Chrome 63
‡Glamor essentially crashes the browser tab.
Typical render timings*: mean ± standard deviations.
| Implementation | Mount deep tree (ms) | Mount wide tree (ms) | Dynamic update (ms) |
| :--- | ---: | ---: | ---: |
| `css-modules` | `56.18` `±19.54` | `75.95` `±23.55` | - |
| `react-native-web@0.3.2` | `68.53` `±21.00` | `101.03` `±25.32` | `60.57` `±09.07` |
| `inline-styles` | `140.32` `±23.91` | `208.55` `±35.25` | `20.36` `±04.92` |
Other libraries
| Implementation | Mount deep tree (ms) | Mount wide tree (ms) | Dynamic update (ms) |
| :--- | ---: | ---: | ---: |
| `aphrodite@1.2.5` | `58.77` `±19.73` | `85.83` `±24.64` | - |
| `glamor@2.20.40` | `81.05` `±15.87` | `104.02` `±20.92` | ‡ |
| `emotion@8.0.12` | `77.12` `±19.61` | `112.04` `±24.43` | ‡ `80.40` `±40.56` |
| `styletron-react@3.0.3` | `91.00` `±17.95` | `130.49` `±20.06` | `39.70` `±06.85` |
| `react-fela@5.0.0` | `101.36` `±19.55` | `142.18` `±21.87` | `43.64` `±12.24` |
| `styled-jsx@2.2.1` | `101.60` `±25.26` | `144.12` `±30.79` | `115.63` `±32.77` |
| `react-jss@8.2.1` | `112.46` `±32.07` | `165.96` `±42.54` | - |
| `styled-components@2.4.0` | `159.85` `±24.30` | `231.00` `±31.34` | `53.86` `±13.40` |
| `reactxp@0.51.0-alpha.9` | `182.05` `±30.72` | `261.25` `±35.54` | `58.20` `±08.62` |
| `radium@0.21.0` | `323.93` `±41.46` | `464.70` `±53.93` | `59.13` `±09.76` |
‡Glamor essentially crashes the browser tab. Emotion gets slower every iteration.

View File

@@ -3,9 +3,14 @@
<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>
<script src="dist/performance.bundle.js"></script>
<script src="./bundle.js"></script>
</body>
</html>

View File

@@ -1,33 +1,36 @@
{
"private": true,
"name": "benchmarks",
"version": "0.3.0",
"version": "0.3.3",
"scripts": {
"benchmark": "webpack --config ./webpack.config.js && open index.html"
"build": "mkdir -p dist && cp -f index.html dist/index.html && webpack --config ./webpack.config.js",
"release": "yarn build && git checkout gh-pages && rm -rf ../../benchmarks && mv dist ../../benchmarks && git add -A && git commit -m \"Benchmarks deploy\" && git push origin gh-pages && git checkout -"
},
"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",
"fela": "5.0.0",
"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.0",
"reactxp": "^0.46.6",
"styled-components": "^2.3.2",
"styletron-client": "^3.0.0-rc.5",
"styletron-utils": "^3.0.0-rc.3"
"react-fela": "5.0.0",
"react-jss": "8.2.1",
"react-native-web": "^0.3.2",
"reactxp": "0.51.0-alpha.9",
"styled-components": "2.4.0",
"styled-jsx": "2.2.1",
"styletron-client": "3.0.2",
"styletron-react": "3.0.3"
},
"devDependencies": {
"babel-plugin-react-native-web": "^0.3.0",
"css-loader": "^0.28.7",
"babel-plugin-react-native-web": "^0.3.3",
"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

@@ -0,0 +1,46 @@
import { createComponent } from 'react-fela';
import View from './View';
const Box = createComponent(
({ color, fixed = false, layout = 'column', outer = false }) => ({
...styles[`color${color}`],
...(fixed && styles.fixed),
...(layout === 'row' && styles.row),
...(outer && styles.outer)
}),
View
);
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,28 @@
/* eslint-disable react/prop-types */
import { createComponent } from 'react-fela';
const Dot = createComponent(
({ 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`
}),
'div'
);
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 { createRenderer } from 'fela';
import { Provider as FelaProvider } from 'react-fela';
import View from './View';
const renderer = createRenderer();
class Provider extends React.Component {
render() {
return (
<FelaProvider renderer={renderer}>
<View>{this.props.children}</View>
</FelaProvider>
);
}
}
export default Provider;

View File

@@ -0,0 +1,24 @@
/* eslint-disable react/prop-types */
import { createComponent } from 'react-fela';
const View = createComponent(
() => ({
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'
}),
'div'
);
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,46 @@
/* eslint-disable react/prop-types */
import classnames from 'classnames';
import React from 'react';
import View from './View';
const getColor = color => {
switch (color) {
case 0:
return '#14171A';
case 1:
return '#AAB8C2';
case 2:
return '#E6ECF0';
case 3:
return '#FFAD1F';
case 4:
return '#F45D22';
case 5:
return '#E0245E';
default:
return 'transparent';
}
};
const Box = props => {
const { className, children: styles } = (
<scope className={classnames('Box', props.fixed && 'fixed')}>
<style jsx>{`
.Box {
align-self: flex-start;
flex-direction: ${props.layout === 'column' ? 'column' : 'row'};
padding: ${props.outer ? '4px' : '0'};
background-color: ${getColor(props.color)};
}
.fixed {
height: 6px;
width: 6px;
}
`}</style>
</scope>
).props;
return <View className={className}>{[props.children, styles]}</View>;
};
export default Box;

View File

@@ -0,0 +1,36 @@
/* eslint-disable react/prop-types */
import React from 'react';
import View from './View';
const Dot = props => {
const { className, children: styles } = (
<scope className="Dot">
<style jsx>{`
.Dot {
position: absolute;
cursor: pointer;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-top-width: 0;
transform: translate(50%, 50%);
}
`}</style>
<style jsx>{`
.Dot {
margin-left: ${props.x}px;
margin-top: ${props.y}px;
border-right-width: ${props.size / 2}px;
border-bottom-width: ${props.size / 2}px;
border-left-width: ${props.size / 2}px;
border-bottom-color: ${props.color};
}
`}</style>
</scope>
).props;
return <View className={className}>{[props.children, styles]}</View>;
};
export default Dot;

View File

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

View File

@@ -0,0 +1,32 @@
/* eslint-disable react/prop-types */
import React from 'react';
class View extends React.Component {
render() {
const { children, className, ...props } = this.props;
return (
<div {...props} className={`initial ${className}`}>
{children}
<style jsx>{`
.initial {
align-items: stretch;
border-width: 0;
border-style: solid;
box-sizing: border-box;
display: flex;
flex-basis: auto;
flex-direction: column;
flex-shrink: 0;
margin: 0;
padding: 0;
position: relative;
min-height: 0;
min-width: 0;
}
`}</style>
</div>
);
}
}
export default View;

View File

@@ -0,0 +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

@@ -0,0 +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

@@ -7,10 +7,10 @@ const appDirectory = path.resolve(__dirname);
module.exports = {
context: __dirname,
entry: ['babel-polyfill', './src/index'],
entry: './src/index',
output: {
path: path.resolve(appDirectory, 'dist'),
filename: 'performance.bundle.js'
filename: 'bundle.js'
},
module: {
rules: [
@@ -30,9 +30,9 @@ module.exports = {
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheDirectory: false,
presets: babelPreset,
plugins: ['react-native-web']
plugins: ['react-native-web', 'styled-jsx/babel']
}
}
}
@@ -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

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.3.0",
"version": "0.3.2",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [

View File

@@ -87,6 +87,9 @@ type State = {
shouldDisplaySource: boolean
};
const getAssetTimeout = source =>
typeof source === 'object' && source.timeout ? source.timeout : 1000;
class Image extends Component<*, State> {
static displayName = 'Image';
@@ -246,12 +249,16 @@ class Image extends Component<*, State> {
}
_createImageLoader() {
const { source } = this.props;
this._destroyImageLoader();
this._loadRequest = requestIdleCallback(() => {
const uri = resolveAssetSource(this.props.source);
this._imageRequestId = ImageLoader.load(uri, this._onLoad, this._onError);
this._onLoadStart();
});
this._loadRequest = requestIdleCallback(
() => {
const uri = resolveAssetSource(source);
this._imageRequestId = ImageLoader.load(uri, this._onLoad, this._onError);
this._onLoadStart();
},
{ timeout: getAssetTimeout(source) }
);
}
_destroyImageLoader() {

View File

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

View File

@@ -20,6 +20,6 @@ export default class PickerItem extends Component<Props> {
render() {
const { label, testID, value } = this.props;
return createElement('option', { label, testID, value });
return createElement('option', { testID, value }, label);
}
}

View File

@@ -1,5 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/Picker prop "children" items 1`] = `
<option
value="value-1"
>
label-1
</option>
`;
exports[`components/Picker prop "children" renders items 1`] = `
<select
className="rn-fontFamily-poiln3 rn-fontSize-7cikom rn-marginTop-1mnahxq rn-marginRight-61z16t rn-marginBottom-p1pxzi rn-marginLeft-11wrixw"

View File

@@ -16,6 +16,12 @@ describe('components/Picker', () => {
const component = shallow(picker);
expect(component).toMatchSnapshot();
});
test('items', () => {
const pickerItem = <Picker.Item label="label-1" value="value-1" />;
const component = shallow(pickerItem);
expect(component).toMatchSnapshot();
});
});
describe('prop "enabled"', () => {

View File

@@ -34,5 +34,9 @@ Object {
"marginLeft": "10px",
"marginRight": "10px",
"marginTop": "50px",
"overflowX": "hidden",
"overflowY": "hidden",
"overscrollBehaviorX": "contain",
"overscrollBehaviorY": "contain",
}
`;

View File

@@ -143,7 +143,9 @@ describe('apis/StyleSheet/createReactDOMStyle', () => {
borderWidth: 0,
marginTop: 50,
marginVertical: 25,
margin: 10
margin: 10,
overflow: 'hidden',
overscrollBehavior: 'contain'
};
expect(createReactDOMStyle(style)).toMatchSnapshot();

Some files were not shown because too many files have changed in this diff Show More