mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-30 17:34:05 +08:00
Compare commits
198 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae7aa818fb | ||
|
|
306cf67932 | ||
|
|
619c2048be | ||
|
|
b9f9a4f8d7 | ||
|
|
58bc18c2f5 | ||
|
|
073940fc4e | ||
|
|
5c462303de | ||
|
|
5fb92da317 | ||
|
|
cafe10d851 | ||
|
|
b28581f44e | ||
|
|
9333e7e887 | ||
|
|
b28cbbb37e | ||
|
|
a53372ceb3 | ||
|
|
239a43978f | ||
|
|
b4e4bfbb3c | ||
|
|
893963a799 | ||
|
|
e5adc5a37c | ||
|
|
6d908189a7 | ||
|
|
31db333ba3 | ||
|
|
9fe089ca21 | ||
|
|
a314d5b2e4 | ||
|
|
fb845ebf44 | ||
|
|
0d0c7e6e27 | ||
|
|
f37003a079 | ||
|
|
f1fc2a9e37 | ||
|
|
92794cdc9f | ||
|
|
b754776373 | ||
|
|
155b34e495 | ||
|
|
00c9dc4236 | ||
|
|
b66aba1a06 | ||
|
|
17f8a674b8 | ||
|
|
b8080ba775 | ||
|
|
7265736545 | ||
|
|
399f465e59 | ||
|
|
9ee89bc7f7 | ||
|
|
748b2d0f3f | ||
|
|
fb4635e013 | ||
|
|
73b459e770 | ||
|
|
a41af0f65f | ||
|
|
96eecc0da3 | ||
|
|
69d5373222 | ||
|
|
538ab88eda | ||
|
|
21b3f39c0b | ||
|
|
998e275e65 | ||
|
|
31d428a649 | ||
|
|
240cf7e05f | ||
|
|
2ad710d83a | ||
|
|
dcce72b66e | ||
|
|
083769d642 | ||
|
|
a53dba8c62 | ||
|
|
670d43ba04 | ||
|
|
73a731f2da | ||
|
|
1542f1f369 | ||
|
|
6f58d7abe7 | ||
|
|
7e0fbf9691 | ||
|
|
865034e8f7 | ||
|
|
6e96ee4f3c | ||
|
|
16d98b49f0 | ||
|
|
d04721c75a | ||
|
|
8512709251 | ||
|
|
efeaea70a9 | ||
|
|
a403244e67 | ||
|
|
985c1d63b6 | ||
|
|
9d8d4057f6 | ||
|
|
ec8843fe90 | ||
|
|
935970156c | ||
|
|
e4e6147081 | ||
|
|
3e1b68d801 | ||
|
|
1b493c9914 | ||
|
|
6ecdc1a517 | ||
|
|
619079cedf | ||
|
|
bbf7674b43 | ||
|
|
f163e4f16f | ||
|
|
bd8c2d6f24 | ||
|
|
3906b6b41b | ||
|
|
753ef963f6 | ||
|
|
0721245b3e | ||
|
|
b7adfd5f32 | ||
|
|
a9342daee2 | ||
|
|
ed0cafac7c | ||
|
|
6e6fd4b5d0 | ||
|
|
5cd533e6cc | ||
|
|
d5e8d85ce9 | ||
|
|
e234568a34 | ||
|
|
19cf0711bc | ||
|
|
067e3f346f | ||
|
|
2117e44e9d | ||
|
|
902ba22877 | ||
|
|
60c2cd65df | ||
|
|
fde29326f1 | ||
|
|
44d795437e | ||
|
|
03598d869b | ||
|
|
a3e44a5c60 | ||
|
|
02b124eceb | ||
|
|
91472bc3d6 | ||
|
|
7f45c52ce7 | ||
|
|
b6ef1d3a36 | ||
|
|
fd6ccbcfb3 | ||
|
|
17614e348b | ||
|
|
c26ef0eb3b | ||
|
|
b78206d2f4 | ||
|
|
69e0396fb1 | ||
|
|
6d9154196e | ||
|
|
87fdd6c73b | ||
|
|
209bd3aee1 | ||
|
|
46e77d0b00 | ||
|
|
6f10f6be9c | ||
|
|
0d0fdc15ac | ||
|
|
bff3f50ae0 | ||
|
|
85aaa39206 | ||
|
|
b85a7062be | ||
|
|
af47d5f414 | ||
|
|
41d90e0238 | ||
|
|
86263a2fa0 | ||
|
|
f6d1caab9d | ||
|
|
1776891736 | ||
|
|
f52a851972 | ||
|
|
3026465ae3 | ||
|
|
14d87f4b30 | ||
|
|
5881f07323 | ||
|
|
b545fe47a7 | ||
|
|
4da4dd57c4 | ||
|
|
3e12ddfb2b | ||
|
|
3ecf5d2ed2 | ||
|
|
0a5acdb996 | ||
|
|
a712a58eba | ||
|
|
6de892c92b | ||
|
|
495defd69b | ||
|
|
1a20fcfce6 | ||
|
|
ed1e45a43d | ||
|
|
556dc8926e | ||
|
|
66cf45b90b | ||
|
|
d1e49e06e6 | ||
|
|
8bf28dbe43 | ||
|
|
9ae95d0797 | ||
|
|
321051b723 | ||
|
|
5f68542529 | ||
|
|
82c044ee33 | ||
|
|
9bcc67e73a | ||
|
|
f1ce6c2acb | ||
|
|
034108a2a0 | ||
|
|
f96d7b868f | ||
|
|
0dfe319d41 | ||
|
|
b7e970f4e6 | ||
|
|
02e62ad5d6 | ||
|
|
541d2458fb | ||
|
|
b1e860ab40 | ||
|
|
e8eab9b3ec | ||
|
|
6bc76c3c92 | ||
|
|
2acd8e477c | ||
|
|
ff2b0c9bdc | ||
|
|
79208720d1 | ||
|
|
fca04c4125 | ||
|
|
5b5b72cc19 | ||
|
|
217ad97bfd | ||
|
|
3e3cfc5325 | ||
|
|
da86ea98fc | ||
|
|
5f3e422b5c | ||
|
|
1f1f89b062 | ||
|
|
0f79960b85 | ||
|
|
117ce59f27 | ||
|
|
214121480e | ||
|
|
6261536f57 | ||
|
|
a748b7e606 | ||
|
|
92952ee746 | ||
|
|
c22a9aff7d | ||
|
|
dd8a3c8d59 | ||
|
|
4a1abee1df | ||
|
|
92ef3ffbb8 | ||
|
|
899763bc34 | ||
|
|
c69ad3c2d6 | ||
|
|
9a5b932139 | ||
|
|
ba96e457b4 | ||
|
|
bdfe943bd5 | ||
|
|
3870445b7e | ||
|
|
5395a3e8bc | ||
|
|
45b3d8b0df | ||
|
|
f1ee3c003a | ||
|
|
606181406c | ||
|
|
b537400f38 | ||
|
|
0a4fdc155e | ||
|
|
0b8e59974b | ||
|
|
22eebea633 | ||
|
|
5dd414f9aa | ||
|
|
72c72f6530 | ||
|
|
de970f9dbb | ||
|
|
e8d6c5b4dd | ||
|
|
e91a5ae13e | ||
|
|
b08bfb9ad5 | ||
|
|
5353011ee4 | ||
|
|
5faa3af19a | ||
|
|
c8461c9c11 | ||
|
|
3112e2ba56 | ||
|
|
fee03e101c | ||
|
|
c730a20a26 | ||
|
|
f7ed60ac67 | ||
|
|
417716391a | ||
|
|
ee5e3cb3ca |
4
.eslintignore
Normal file
4
.eslintignore
Normal file
@@ -0,0 +1,4 @@
|
||||
coverage
|
||||
dist
|
||||
node_modules
|
||||
packages/**/vendor/*
|
||||
@@ -56,6 +56,7 @@
|
||||
"no-dupe-class-members": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-duplicate-imports": 2,
|
||||
"no-empty-character-class": 2,
|
||||
"no-empty-pattern": 2,
|
||||
"no-eval": 2,
|
||||
|
||||
14
.flowconfig
14
.flowconfig
@@ -1,14 +1,16 @@
|
||||
[version]
|
||||
^0.63.0
|
||||
|
||||
[ignore]
|
||||
.*/__tests__/.*
|
||||
.*/benchmarks/.*
|
||||
.*/docs/.*
|
||||
.*/node_modules/animated/*
|
||||
<PROJECT_ROOT>/.*/__tests__/.*
|
||||
<PROJECT_ROOT>/packages/.*/dist/.*
|
||||
<PROJECT_ROOT>/website/.*
|
||||
.*/node_modules/babel-plugin-transform-react-remove-prop-types/*
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
types
|
||||
<PROJECT_ROOT>/types
|
||||
|
||||
[options]
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
|
||||
83
.github/CONTRIBUTING.md
vendored
83
.github/CONTRIBUTING.md
vendored
@@ -17,7 +17,7 @@ Fork, then clone the repo:
|
||||
git clone https://github.com/your-username/react-native-web.git
|
||||
```
|
||||
|
||||
Install dependencies (requires [yarn](https://yarnpkg.com/en/docs/install):
|
||||
Install dependencies (requires [yarn](https://yarnpkg.com/en/docs/install)):
|
||||
|
||||
```
|
||||
yarn
|
||||
@@ -25,6 +25,12 @@ yarn
|
||||
|
||||
## Automated tests
|
||||
|
||||
To run the linter:
|
||||
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
To run flow:
|
||||
|
||||
```
|
||||
@@ -40,62 +46,57 @@ yarn jest
|
||||
…in watch mode:
|
||||
|
||||
```
|
||||
yarn jest:watch
|
||||
yarn jest --watch
|
||||
```
|
||||
|
||||
To run all automated tests:
|
||||
To run all these automated tests:
|
||||
|
||||
```
|
||||
yarn test
|
||||
```
|
||||
|
||||
## Visual tests
|
||||
|
||||
To run the interactive storybook:
|
||||
|
||||
```
|
||||
yarn docs:start
|
||||
```
|
||||
|
||||
To generate a static build of the storybook:
|
||||
|
||||
```
|
||||
yarn docs:build
|
||||
```
|
||||
|
||||
To run the performance benchmarks in a browser (opening `./benchmarks/index.html`):
|
||||
|
||||
```
|
||||
yarn benchmark
|
||||
```
|
||||
|
||||
## Compile and build
|
||||
|
||||
To compile the source code to `dist`:
|
||||
To compile the `react-native-web` source code:
|
||||
|
||||
```
|
||||
yarn compile
|
||||
```
|
||||
|
||||
To create a UMD bundle of the library:
|
||||
…in watch mode:
|
||||
|
||||
```
|
||||
yarn build
|
||||
yarn compile --watch
|
||||
```
|
||||
|
||||
### Pre-commit
|
||||
## Website and visual tests
|
||||
|
||||
To format and lint code before commit:
|
||||
To run the interactive storybook:
|
||||
|
||||
```
|
||||
yarn precommit
|
||||
yarn website
|
||||
```
|
||||
|
||||
To format and lint the entire project:
|
||||
When you're also making changes to the 'react-native-web' source files, run this command in another process:
|
||||
|
||||
```
|
||||
yarn fmt
|
||||
yarn lint
|
||||
yarn compile --watch
|
||||
```
|
||||
|
||||
## Benchmarks
|
||||
|
||||
To run the benchmarks locally:
|
||||
|
||||
```
|
||||
yarn benchmarks
|
||||
open ./packages/benchmarks/dist/index.html
|
||||
```
|
||||
|
||||
To develop against these benchmarks:
|
||||
|
||||
```
|
||||
yarn compile --watch
|
||||
yarn benchmarks --watch
|
||||
```
|
||||
|
||||
### New Features
|
||||
@@ -106,13 +107,12 @@ that we won't want to accept.
|
||||
|
||||
## Pull requests
|
||||
|
||||
**Before submitting a pull request,** please make sure the following is done:
|
||||
**Before submitting a pull request**, please make sure the following is done:
|
||||
|
||||
1. Fork the repository and create your branch from `master`.
|
||||
2. If you've added code that should be tested, add tests!
|
||||
3. If you've changed APIs, update the documentation.
|
||||
4. Ensure the tests pass (`yarn test`).
|
||||
5. Lint and format your code (`yarn fmt && yarn lint`).
|
||||
|
||||
You can now submit a pull request, referencing any issues it addresses.
|
||||
|
||||
@@ -123,3 +123,18 @@ After you have submitted your pull request, we'll try to get back to you as
|
||||
soon as possible. We may suggest some changes or improvements.
|
||||
|
||||
Thank you for contributing!
|
||||
|
||||
## Releases
|
||||
|
||||
To commit, publish, and push a final version:
|
||||
|
||||
```
|
||||
yarn release <version>
|
||||
```
|
||||
|
||||
Release candidates or versions that you'd like to publish to npm, but do not
|
||||
want to produce a commit and push it to GitHub:
|
||||
|
||||
```
|
||||
yarn release <version> --skip-git
|
||||
```
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
coverage
|
||||
node_modules
|
||||
dist
|
||||
|
||||
20
.travis.yml
20
.travis.yml
@@ -1,9 +1,17 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- "6"
|
||||
before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- "8"
|
||||
|
||||
before_install:
|
||||
# Install Yarn
|
||||
- curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
- export PATH=$HOME/.yarn/bin:$PATH
|
||||
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
script:
|
||||
- npm run lint
|
||||
- npm test
|
||||
- yarn test
|
||||
|
||||
45
LICENSE
45
LICENSE
@@ -1,31 +1,22 @@
|
||||
BSD License
|
||||
MIT License
|
||||
|
||||
For React Native software
|
||||
Copyright (c) 2015-present, Nicolas Gallagher.
|
||||
Copyright (c) 2015-present, Facebook, Inc.
|
||||
|
||||
Copyright (c) 2015-present, Nicolas Gallagher. All rights reserved.
|
||||
Copyright (c) 2015-present, Facebook, Inc. All rights reserved.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name Facebook nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
210
README.md
210
README.md
@@ -1,127 +1,141 @@
|
||||
# React Native for Web
|
||||
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![npm version][npm-image]][npm-url]
|
||||
[![npm version][package-badge]][package-url] [![Build Status][ci-badge]][ci-url] [](https://reactjs.org/docs/how-to-contribute.html#your-first-pull-request)
|
||||
|
||||
"React Native for Web" brings the platform-agnostic Components and APIs of
|
||||
[React Native][react-native-url] to the Web.
|
||||
|
||||
Browse the [interactive
|
||||
documentation](https://necolas.github.io/react-native-web/storybook/) or [try
|
||||
it out](https://glitch.com/edit/#!/react-native-web-playground) on Glitch.
|
||||
* **High-quality user interfaces**: React Native for Web makes it easy to
|
||||
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.
|
||||
|
||||
## Features
|
||||
* **Write once, render anywhere**: React Native for Web interoperates with
|
||||
existing React DOM components and is compatible with the majority of the
|
||||
React Native API. You can develop new components for native and web without
|
||||
rewriting existing code. React Native for Web can also render to HTML and
|
||||
critical CSS on the server using Node.js.
|
||||
|
||||
* Interoperability with ReactDOM components.
|
||||
* Native-like touch handling.
|
||||
* Built-in integration with web accessibility APIs.
|
||||
* Built-in support for LTR and RTL layouts.
|
||||
* Built-in expressive and reliable subset of CSS.
|
||||
* Optimized, vendor-prefixed CSS with [good runtime performance](benchmarks/README.md).
|
||||
* Server-side rendering of HTML and critical CSS.
|
||||
* Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
|
||||
Who is using React Native for Web? [Twitter](https://mobile.twitter.com),
|
||||
[Major League Soccer](https://matchcenter.mlssoccer.com), Playstation, Uber, [The
|
||||
Times](https://github.com/newsuk/times-components), [React Native's
|
||||
documentation](http://facebook.github.io/react-native/).
|
||||
|
||||
Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
|
||||
|
||||
## Quick start
|
||||
|
||||
Install in your existing app using `yarn` or `npm`:
|
||||
The easiest way to get started with React Native for Web is to use this
|
||||
[ready-to-go project on Glitch](https://glitch.com/edit/#!/react-native-web-playground).
|
||||
You don’t need to install anything to try it out.
|
||||
|
||||
```
|
||||
yarn add react react-dom react-native-web
|
||||
```
|
||||
|
||||
Add the `react-native-web/babel` plugin to your Babel configuration. This will
|
||||
alias `react-native` to `react-native-web` and exclude any modules not required
|
||||
by the app.
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": [
|
||||
"react-native-web/babel"
|
||||
],
|
||||
"presets": [
|
||||
"react-native"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
(For React/ReactDOM 15.4 – 15.6 support, install `react-native-web@<0.1.0`)
|
||||
|
||||
See the [Getting Started](docs/guides/getting-started.md) guide for more details.
|
||||
If you are unfamiliar with setting up a React web project, please follow the
|
||||
recommendations in the [React documentation](https://reactjs.org/).
|
||||
|
||||
## Documentation
|
||||
|
||||
The [interactive
|
||||
documentation](https://necolas.github.io/react-native-web/storybook/) shows all
|
||||
the supported APIs and Components.
|
||||
You can find the React Native for Web API documentation [on the
|
||||
website][website-url].
|
||||
|
||||
Guides:
|
||||
Please refer to the [React Native documentation][react-native-url] for more
|
||||
design details, and for information about the [Gesture Responder
|
||||
system](https://facebook.github.io/react-native/docs/gesture-responder-system.html)
|
||||
and [animations](https://facebook.github.io/react-native/docs/animations.html).
|
||||
|
||||
* [Getting started](docs/guides/getting-started.md)
|
||||
* [Style](docs/guides/style.md)
|
||||
* [Accessibility](docs/guides/accessibility.md)
|
||||
* [Direct manipulation](docs/guides/direct-manipulation.md)
|
||||
* [Internationalization](docs/guides/internationalization.md)
|
||||
* [Advanced use](docs/guides/advanced.md)
|
||||
* [Known issues](docs/guides/known-issues.md)
|
||||
### Installation
|
||||
|
||||
## Example code
|
||||
Install using `yarn` or `npm`:
|
||||
|
||||
```js
|
||||
import React from 'react'
|
||||
import { AppRegistry, Image, StyleSheet, Text, View } from 'react-native'
|
||||
|
||||
// Components
|
||||
const Card = ({ children }) => <View style={styles.card}>{children}</View>
|
||||
const Title = ({ children }) => <Text style={styles.title}>{children}</Text>
|
||||
const Photo = ({ uri }) => <Image source={{ uri }} style={styles.image} />
|
||||
const App = () => (
|
||||
<Card>
|
||||
<Title>App Card</Title>
|
||||
<Photo uri="/some-photo.jpg" />
|
||||
</Card>
|
||||
)
|
||||
|
||||
// Styles
|
||||
const styles = StyleSheet.create({
|
||||
card: {
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
title: {
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
image: {
|
||||
height: 40,
|
||||
marginVertical: 10,
|
||||
width: 40
|
||||
}
|
||||
})
|
||||
|
||||
// App registration and rendering
|
||||
AppRegistry.registerComponent('MyApp', () => App)
|
||||
AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-root') })
|
||||
```
|
||||
yarn add react react-dom react-native-web
|
||||
yarn add --dev babel-plugin-react-native-web
|
||||
```
|
||||
|
||||
## Starter kits
|
||||
### Guides
|
||||
|
||||
* [Glitch](https://glitch.com/edit/#!/react-native-web-playground)
|
||||
* [create-react-app](https://github.com/facebookincubator/create-react-app)
|
||||
* [re-start](https://github.com/react-everywhere/re-start)
|
||||
* [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)
|
||||
|
||||
## Related projects
|
||||
## Examples
|
||||
|
||||
* [react-primitives](https://github.com/lelandrichardson/react-primitives/)
|
||||
* [react-sketchapp](https://github.com/airbnb/react-sketchapp)
|
||||
* [reactxp](https://github.com/microsoft/reactxp)
|
||||
* [react-native-web-player](https://github.com/dabbott/react-native-web-player)
|
||||
There are several examples [on the website][website-url] and in the [website's
|
||||
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';
|
||||
import { AppRegistry, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.box}>
|
||||
<Text style={styles.text}>Hello, world!</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
box: { padding: 10 },
|
||||
text: { fontWeight: 'bold' }
|
||||
});
|
||||
|
||||
AppRegistry.registerComponent('App', () => App);
|
||||
AppRegistry.runApplication('App', { rootTag: document.getElementById('react-root') });
|
||||
```
|
||||
|
||||
This example will render the `App` into a container on the page.
|
||||
|
||||
You'll notice that there is no reference to `react-dom`; the `App` component is
|
||||
defined using the platform-agnostic APIs and Components introduced by React
|
||||
Native. This allows the app to be rendered to web and native platforms.
|
||||
|
||||
## Contributing
|
||||
|
||||
The main purpose of this repository is to help evolve React web and native
|
||||
development towards the platform-agnostic design of React Native, and in the
|
||||
process make it faster and easier to build high-quality experiences for the web
|
||||
with React. Development happens in the open on GitHub, and we are grateful for
|
||||
contributing bugfixes and improvements. Read below to learn how you can take
|
||||
part in improving React Native for Web.
|
||||
|
||||
### Code of conduct
|
||||
|
||||
Facebook has adopted a [Code of Conduct][code-of-conduct] that this project
|
||||
expects all participants to adhere to. Please read the full text so that you
|
||||
can understand what actions will and will not be tolerated.
|
||||
|
||||
### Contributing guide
|
||||
|
||||
Read the [contributing guide][contributing-url] to learn about the
|
||||
development process, how to propose bugfixes and improvements, and how to build
|
||||
and test your changes to React Native for Web.
|
||||
|
||||
### Good first issues
|
||||
|
||||
To help you get you familiar with the contribution process, there is a list of
|
||||
[good first issues][good-first-issue-url] that contain bugs which have a
|
||||
relatively limited scope. This is a great place to get started.
|
||||
|
||||
## License
|
||||
|
||||
React Native for Web is [BSD licensed](LICENSE).
|
||||
React Native for Web is [MIT licensed](./LICENSE). By contributing to React
|
||||
Native for Web, you agree that your contributions will be licensed under its
|
||||
MIT license.
|
||||
|
||||
[npm-image]: https://badge.fury.io/js/react-native-web.svg
|
||||
[npm-url]: https://npmjs.org/package/react-native-web
|
||||
[package-badge]: https://img.shields.io/npm/v/react-native-web.svg?style=flat
|
||||
[package-url]: https://yarnpkg.com/en/package/react-native-web
|
||||
[ci-badge]: https://travis-ci.org/necolas/react-native-web.svg?branch=master
|
||||
[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/
|
||||
[travis-image]: https://travis-ci.org/necolas/react-native-web.svg?branch=master
|
||||
[travis-url]: https://travis-ci.org/necolas/react-native-web
|
||||
[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
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`1. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
import { View } from 'react-native';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
import View from 'react-native-web/dist/components/View';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`2. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
import { Switch, Text, View as MyView } from 'react-native';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
import Switch from 'react-native-web/dist/components/Switch';
|
||||
import Text from 'react-native-web/dist/components/Text';
|
||||
import MyView from 'react-native-web/dist/components/View';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`3. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
import { createElement, Switch, StyleSheet } from 'react-native';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
import createElement from 'react-native-web/dist/modules/createElement';
|
||||
import Switch from 'react-native-web/dist/components/Switch';
|
||||
import StyleSheet from 'react-native-web/dist/apis/StyleSheet';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`4. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
import { InvalidThing, TouchableOpacity } from 'react-native';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
import { InvalidThing } from 'react-native-web';
|
||||
import TouchableOpacity from 'react-native-web/dist/components/Touchable/TouchableOpacity';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`5. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
import * as RNW from 'react-native';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
import * as RNW from 'react-native-web';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`6. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
const { View } = require('react-native');
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
const View = require('react-native-web/dist/components/View');
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`7. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
let { Switch, Text, View: MyView } = require('react-native');
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
let Switch = require('react-native-web/dist/components/Switch');
|
||||
|
||||
let Text = require('react-native-web/dist/components/Text');
|
||||
|
||||
let MyView = require('react-native-web/dist/components/View');
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`8. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
var { createElement, Switch, StyleSheet } = require('react-native');
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
var createElement = require('react-native-web/dist/modules/createElement');
|
||||
|
||||
var Switch = require('react-native-web/dist/components/Switch');
|
||||
|
||||
var StyleSheet = require('react-native-web/dist/apis/StyleSheet');
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`9. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
const { InvalidThing, TouchableOpacity } = require('react-native');
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
const TouchableOpacity = require('react-native-web/dist/components/Touchable/TouchableOpacity');
|
||||
"
|
||||
`;
|
||||
@@ -1,39 +0,0 @@
|
||||
const plugin = require('..');
|
||||
const pluginTester = require('babel-plugin-tester');
|
||||
|
||||
pluginTester({
|
||||
plugin,
|
||||
snapshot: true,
|
||||
tests: [
|
||||
// import react-native
|
||||
"import { View } from 'react-native';",
|
||||
"import { Switch, Text, View as MyView } from 'react-native';",
|
||||
"import { createElement, Switch, StyleSheet } from 'react-native';",
|
||||
"import { InvalidThing, TouchableOpacity } from 'react-native';",
|
||||
"import * as RNW from 'react-native';",
|
||||
|
||||
// import react-native-web
|
||||
// "import { View } from 'react-native-web';",
|
||||
// "import { Switch, Text, View as MyView } from 'react-native-web';",
|
||||
// "import { createElement, Switch, StyleSheet } from 'react-native-web';",
|
||||
// "import { InvalidThing, TouchableOpacity } from 'react-native-web';",
|
||||
// "import * as RNW from 'react-native-web';",
|
||||
|
||||
// require react-native
|
||||
"const { View } = require('react-native');",
|
||||
"let { Switch, Text, View: MyView } = require('react-native');",
|
||||
"var { createElement, Switch, StyleSheet } = require('react-native');",
|
||||
"const { InvalidThing, TouchableOpacity } = require('react-native');",
|
||||
|
||||
// require react-native-web
|
||||
// "const { View } = require('react-native-web');",
|
||||
// "let { Switch, Text, View: MyView } = require('react-native-web');",
|
||||
// "var { createElement, Switch, StyleSheet } = require('react-native-web');",
|
||||
// "const { InvalidThing, TouchableOpacity } = require('react-native-web');",
|
||||
{
|
||||
code: "const RNW = require('react-native');",
|
||||
output: "const RNW = require('react-native');",
|
||||
snapshot: false
|
||||
}
|
||||
]
|
||||
});
|
||||
148
babel/index.js
148
babel/index.js
@@ -1,148 +0,0 @@
|
||||
const getDistLocation = importName => {
|
||||
const root = 'react-native-web/dist';
|
||||
|
||||
switch (importName) {
|
||||
// apis
|
||||
case 'Animated':
|
||||
case 'AppRegistry':
|
||||
case 'AppState':
|
||||
case 'AsyncStorage':
|
||||
case 'BackAndroid':
|
||||
case 'Clipboard':
|
||||
case 'Dimensions':
|
||||
case 'Easing':
|
||||
case 'I18nManager':
|
||||
case 'InteractionManager':
|
||||
case 'Keyboard':
|
||||
case 'Linking':
|
||||
case 'NetInfo':
|
||||
case 'PanResponder':
|
||||
case 'PixelRatio':
|
||||
case 'Platform':
|
||||
case 'StyleSheet':
|
||||
case 'UIManager':
|
||||
case 'Vibration': {
|
||||
return `${root}/apis/${importName}`;
|
||||
}
|
||||
|
||||
// components
|
||||
case 'ActivityIndicator':
|
||||
case 'Button':
|
||||
case 'FlatList':
|
||||
case 'Image':
|
||||
case 'KeyboardAvoidingView':
|
||||
case 'ListView':
|
||||
case 'Modal':
|
||||
case 'Picker':
|
||||
case 'ProgressBar':
|
||||
case 'RefreshControl':
|
||||
case 'ScrollView':
|
||||
case 'SectionList':
|
||||
case 'Slider':
|
||||
case 'StatusBar':
|
||||
case 'Switch':
|
||||
case 'Text':
|
||||
case 'TextInput':
|
||||
case 'View':
|
||||
case 'VirtualizedList': {
|
||||
return `${root}/components/${importName}`;
|
||||
}
|
||||
|
||||
case 'Touchable':
|
||||
case 'TouchableHighlight':
|
||||
case 'TouchableNativeFeedback':
|
||||
case 'TouchableOpacity':
|
||||
case 'TouchableWithoutFeedback': {
|
||||
return `${root}/components/Touchable/${importName}`;
|
||||
}
|
||||
|
||||
// modules
|
||||
case 'createElement':
|
||||
case 'findNodeHandle':
|
||||
case 'NativeModules':
|
||||
case 'processColor':
|
||||
case 'render':
|
||||
case 'unmountComponentAtNode': {
|
||||
return `${root}/modules/${importName}`;
|
||||
}
|
||||
|
||||
// propTypes
|
||||
case 'ColorPropType':
|
||||
case 'EdgeInsetsPropType':
|
||||
case 'PointPropType':
|
||||
case 'TextPropTypes':
|
||||
case 'ViewPropTypes': {
|
||||
return `${root}/propTypes/${importName}`;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const isReactNativeRequire = (t, node) => {
|
||||
const { declarations } = node;
|
||||
if (declarations.length > 1) {
|
||||
return false;
|
||||
}
|
||||
const { id, init } = declarations[0];
|
||||
return (
|
||||
t.isObjectPattern(id) &&
|
||||
t.isCallExpression(init) &&
|
||||
t.isIdentifier(init.callee) &&
|
||||
init.callee.name === 'require' &&
|
||||
init.arguments.length === 1 &&
|
||||
init.arguments[0].value === 'react-native'
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = function({ types: t }) {
|
||||
return {
|
||||
name: 'Rewrite react-native paths for react-native-web',
|
||||
visitor: {
|
||||
ImportDeclaration(path) {
|
||||
const { source, specifiers } = path.node;
|
||||
if (source.value === 'react-native' && specifiers.length) {
|
||||
const imports = specifiers
|
||||
.map(specifier => {
|
||||
if (t.isImportSpecifier(specifier)) {
|
||||
const importName = specifier.imported.name;
|
||||
const distLocation = getDistLocation(importName);
|
||||
|
||||
if (distLocation) {
|
||||
return t.importDeclaration(
|
||||
[t.importDefaultSpecifier(t.identifier(specifier.local.name))],
|
||||
t.stringLiteral(distLocation)
|
||||
);
|
||||
}
|
||||
}
|
||||
return t.importDeclaration([specifier], t.stringLiteral('react-native-web'));
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
path.replaceWithMultiple(imports);
|
||||
}
|
||||
},
|
||||
VariableDeclaration(path) {
|
||||
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);
|
||||
|
||||
path.replaceWithMultiple(imports);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
# Performance
|
||||
|
||||
To run these benchmarks:
|
||||
|
||||
```
|
||||
npm run build:performance
|
||||
open ./performance/index.html
|
||||
```
|
||||
|
||||
Append `?fastest` to the URL to include the fastest "other libraries", and
|
||||
`?all` to include all the "other libraries".
|
||||
|
||||
## Notes
|
||||
|
||||
The components used in the render benchmarks are simple enough to be
|
||||
implemented by multiple UI or style libraries. The implementations are not
|
||||
equivalent in functionality. For example, React Native for Web's stylesheet is
|
||||
unique in that it also converts React Native styles to DOM styles, has
|
||||
deterministic resolution, and supports RTL layout.
|
||||
|
||||
`react-native-web/stylesheet` is a comparative baseline that implements a
|
||||
simple `View` without much of React Native's functionality.
|
||||
|
||||
## Benchmark results
|
||||
|
||||
Typical render timings*: mean ± two standard deviations.
|
||||
|
||||
| Implementation | Deep tree (ms) | Wide tree (ms) | Tweets (ms) |
|
||||
| :--- | ---: | ---: | ---: |
|
||||
| `css-modules` | `88.83` `±18.63` | `198.79` `±22.98` | |
|
||||
| `react-native-web/stylesheet@0.0.121` | `91.17` `±19.29` | `209.67` `±32.38` | |
|
||||
| `react-native-web@0.0.121` | `124.21` `±16.84` | `264.55` `±38.75` | `16.90` `±7.30ms` |
|
||||
|
||||
Other libraries
|
||||
|
||||
| Implementation | Deep tree (ms) | Wide tree (ms) |
|
||||
| :--- | ---: | ---: |
|
||||
| `aphrodite@1.2.3` | `91.73` `±41.63` | `197.72` `±44.90` |
|
||||
| `styletron@2.5.1` | `94.73` `±37.58` | `201.81` `±57.93` |
|
||||
| `glamor@2.20.40` | `146.60` `±26.73` | `277.46` `±29.17` |
|
||||
| `emotion@7.2.2` | `150.79` `±38.29` | `282.18` `±41.79` |
|
||||
| `react-jss@7.1.0` | `201.83` `±34.65` | `428.61` `±47.8` |
|
||||
| `reactxp@0.42.1` | `262.69` `±24.14` | `595.20` `±66.17` |
|
||||
| `styled-components@2.1.2` | `280.59` `±31.77` | `599.00` `±62.99` |
|
||||
| `styled-components/primitives@2.1.2` | `291.74` `±48.96` | `606.57` `±78.18` |
|
||||
| `radium@0.19.4` | `563.94` `±69.91` | `1139.18` `±152.59` |
|
||||
|
||||
These results indicate that style render performance is not a significant
|
||||
differentiating factor between `aphrodite`, `css-modules`, `react-native-web`,
|
||||
and `styletron`.
|
||||
|
||||
*MacBook Pro (13-inch, Early 2015); 3.1 GHz Intel Core i7; 16 GB 1867 MHz DDR3. Google Chrome 58 (2x CPU slowdown).
|
||||
@@ -1,97 +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);
|
||||
};
|
||||
|
||||
const benchmark = ({ name, description, setup, teardown, task, runs }) => {
|
||||
return new Promise(resolve => {
|
||||
const durations = [];
|
||||
let i = 0;
|
||||
|
||||
setup();
|
||||
const first = measure('first', task);
|
||||
teardown();
|
||||
|
||||
const done = () => {
|
||||
const stdDev = standardDeviation(durations);
|
||||
const formattedFirst = fmt(first);
|
||||
const formattedMean = fmt(mean(durations));
|
||||
const formattedMedian = fmt(median(durations));
|
||||
const formattedStdDev = fmt(stdDev);
|
||||
|
||||
console.groupCollapsed(`${name}\n${formattedMean} ±${fmt(2 * stdDev)}`);
|
||||
description && console.log(description);
|
||||
console.log(`First: ${formattedFirst}`);
|
||||
console.log(`Median: ${formattedMedian}`);
|
||||
console.log(`Mean: ${formattedMean}`);
|
||||
console.log(`Standard deviation: ${formattedStdDev}`);
|
||||
console.log(durations);
|
||||
console.groupEnd();
|
||||
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;
|
||||
@@ -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;
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Performance tests</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="root"></div>
|
||||
<script src="dist/performance.bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,69 +0,0 @@
|
||||
import aphrodite from './src/aphrodite';
|
||||
import cssModules from './src/css-modules';
|
||||
import emotion from './src/emotion';
|
||||
import glamor from './src/glamor';
|
||||
import jss from './src/jss';
|
||||
import radium from './src/radium';
|
||||
import reactNative from './src/react-native';
|
||||
import reactNativeStyleSheet from './src/react-native-stylesheet';
|
||||
import styledComponents from './src/styled-components';
|
||||
import styledComponentsPrimitives from './src/styled-components-primitives';
|
||||
import styletron from './src/styletron';
|
||||
import xp from './src/reactxp';
|
||||
|
||||
import renderDeepTree from './tests/renderDeepTree';
|
||||
import renderTweet from './tests/renderTweet';
|
||||
import renderWideTree from './tests/renderWideTree';
|
||||
|
||||
const testAll = window.location.search === '?all';
|
||||
const testFastest = window.location.search === '?fastest';
|
||||
|
||||
const coreTests = [
|
||||
() => renderTweet('react-native-web', reactNative),
|
||||
|
||||
() => renderDeepTree('css-modules', cssModules),
|
||||
() => renderWideTree('css-modules', cssModules),
|
||||
() => renderDeepTree('react-native-web/stylesheet', reactNativeStyleSheet),
|
||||
() => renderWideTree('react-native-web/stylesheet', reactNativeStyleSheet),
|
||||
() => renderDeepTree('react-native-web', reactNative),
|
||||
() => renderWideTree('react-native-web', reactNative)
|
||||
];
|
||||
|
||||
const fastestTests = [
|
||||
() => renderDeepTree('aphrodite', aphrodite),
|
||||
() => renderWideTree('aphrodite', aphrodite),
|
||||
() => renderDeepTree('styletron', styletron),
|
||||
() => renderWideTree('styletron', styletron)
|
||||
];
|
||||
|
||||
/**
|
||||
* Optionally run tests using other libraries
|
||||
*/
|
||||
const restTests = [
|
||||
() => renderDeepTree('emotion', emotion),
|
||||
() => renderWideTree('emotion', emotion),
|
||||
() => renderDeepTree('glamor', glamor),
|
||||
() => renderWideTree('glamor', glamor),
|
||||
() => renderDeepTree('radium', radium),
|
||||
() => renderWideTree('radium', radium),
|
||||
() => renderDeepTree('reactxp', xp),
|
||||
() => renderWideTree('reactxp', xp),
|
||||
() => renderDeepTree('react-jss', jss),
|
||||
() => renderWideTree('react-jss', jss),
|
||||
() => renderDeepTree('styled-components', styledComponents),
|
||||
() => renderWideTree('styled-components', styledComponents),
|
||||
() => renderDeepTree('styled-components/primitives', styledComponentsPrimitives),
|
||||
() => renderWideTree('styled-components/primitives', styledComponentsPrimitives)
|
||||
];
|
||||
|
||||
const tests = [...coreTests];
|
||||
if (testFastest) {
|
||||
tests.push(...fastestTests);
|
||||
}
|
||||
if (testAll) {
|
||||
tests.push(...fastestTests);
|
||||
tests.push(...restTests);
|
||||
}
|
||||
|
||||
// run benchmarks
|
||||
tests.reduce((promise, test) => promise.then(test()), Promise.resolve());
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"name": "benchmarks",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"aphrodite": "^1.2.3",
|
||||
"classnames": "^2.2.5",
|
||||
"emotion": "^7.2.2",
|
||||
"glamor": "^2.20.40",
|
||||
"marky": "^1.2.0",
|
||||
"radium": "^0.19.4",
|
||||
"react-jss": "^7.1.0",
|
||||
"react-primitives": "^0.4.3",
|
||||
"reactxp": "^0.42.1",
|
||||
"styled-components": "^2.1.2",
|
||||
"styletron-client": "^2.5.7",
|
||||
"styletron-utils": "^2.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"css-loader": "^0.28.7",
|
||||
"style-loader": "^0.18.2"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import Box from './components/Box/aphrodite';
|
||||
import View from './components/View/aphrodite';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import StyleSheet from 'react-native/apis/StyleSheet';
|
||||
import View from '../View/react-native-stylesheet';
|
||||
|
||||
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 = StyleSheet.create({
|
||||
outer: {
|
||||
padding: 4
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
color0: {
|
||||
backgroundColor: '#222'
|
||||
},
|
||||
color1: {
|
||||
backgroundColor: '#666'
|
||||
},
|
||||
color2: {
|
||||
backgroundColor: '#999'
|
||||
},
|
||||
color3: {
|
||||
backgroundColor: 'blue'
|
||||
},
|
||||
color4: {
|
||||
backgroundColor: 'orange'
|
||||
},
|
||||
color5: {
|
||||
backgroundColor: 'red'
|
||||
},
|
||||
fixed: {
|
||||
width: 20,
|
||||
height: 20
|
||||
}
|
||||
});
|
||||
|
||||
export default Box;
|
||||
@@ -1,30 +0,0 @@
|
||||
import styled from 'styled-components/primitives';
|
||||
|
||||
const getColor = color => {
|
||||
switch (color) {
|
||||
case 0:
|
||||
return '#222';
|
||||
case 1:
|
||||
return '#666';
|
||||
case 2:
|
||||
return '#999';
|
||||
case 3:
|
||||
return 'blue';
|
||||
case 4:
|
||||
return 'orange';
|
||||
case 5:
|
||||
return 'red';
|
||||
default:
|
||||
return 'transparent';
|
||||
}
|
||||
};
|
||||
|
||||
const Box = styled.View`
|
||||
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)};
|
||||
`;
|
||||
|
||||
export default Box;
|
||||
@@ -1,36 +0,0 @@
|
||||
.outer {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.color0 {
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
.color1 {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
.color2 {
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
.color3 {
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
.color4 {
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
.color5 {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import { injectStylePrefixed } from 'styletron-utils';
|
||||
import React from 'react';
|
||||
import View, { styletron } from '../View/styletron';
|
||||
|
||||
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;
|
||||
@@ -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;
|
||||
@@ -1,35 +0,0 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import StyleSheet from 'react-native/apis/StyleSheet';
|
||||
import registry from 'react-native/apis/StyleSheet/registry';
|
||||
|
||||
const emptyObject = {};
|
||||
|
||||
class View extends React.Component {
|
||||
render() {
|
||||
const { style, ...other } = this.props;
|
||||
const styleProps = registry.resolve([styles.root, style]) || emptyObject;
|
||||
return <div {...other} {...styleProps} />;
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
root: {
|
||||
alignItems: 'stretch',
|
||||
borderWidth: 0,
|
||||
borderStyle: 'solid',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexBasis: 'auto',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 0,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
position: 'relative',
|
||||
// fix flexbox bugs
|
||||
minHeight: 0,
|
||||
minWidth: 0
|
||||
}
|
||||
});
|
||||
|
||||
export default View;
|
||||
@@ -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;
|
||||
@@ -1,9 +0,0 @@
|
||||
import Box from './components/Box/css-modules';
|
||||
import View from './components/View/css-modules';
|
||||
|
||||
const api = {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
|
||||
export default api;
|
||||
@@ -1,7 +0,0 @@
|
||||
import Box from './components/Box/emotion';
|
||||
import View from './components/View/emotion';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import Box from './components/Box/glamor';
|
||||
import View from './components/View/glamor';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
import Box from './components/Box/jss';
|
||||
import View from './components/View/jss';
|
||||
|
||||
const api = {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
|
||||
export default api;
|
||||
@@ -1,9 +0,0 @@
|
||||
import Box from './components/Box/radium';
|
||||
import View from './components/View/radium';
|
||||
|
||||
const api = {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
|
||||
export default api;
|
||||
9
benchmarks/src/react-native-stylesheet.js
vendored
9
benchmarks/src/react-native-stylesheet.js
vendored
@@ -1,9 +0,0 @@
|
||||
import Box from './components/Box/react-native-stylesheet';
|
||||
import View from './components/View/react-native-stylesheet';
|
||||
|
||||
const api = {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
|
||||
export default api;
|
||||
9
benchmarks/src/react-native.js
vendored
9
benchmarks/src/react-native.js
vendored
@@ -1,9 +0,0 @@
|
||||
import Box from './components/Box/react-native';
|
||||
import Tweet from './components/Tweet';
|
||||
import { View } from 'react-native';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
Tweet,
|
||||
View
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import Box from './components/Box/reactxp';
|
||||
import { View } from 'reactxp';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import Box from './components/Box/styled-components';
|
||||
import styled from 'styled-components/primitives';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View: styled.View
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import Box from './components/Box/styled-components';
|
||||
import View from './components/View/styled-components';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import Box from './components/Box/styletron';
|
||||
import View from './components/View/styletron';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
View
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import createRenderBenchmark from '../createRenderBenchmark';
|
||||
import NestedTree from '../src/components/NestedTree';
|
||||
import React from 'react';
|
||||
|
||||
const renderDeepTree = (label, components) =>
|
||||
createRenderBenchmark({
|
||||
name: `Deep tree [${label}]`,
|
||||
runs: 20,
|
||||
getElement() {
|
||||
return <NestedTree breadth={3} components={components} depth={6} id={0} wrap={1} />;
|
||||
}
|
||||
});
|
||||
|
||||
export default renderDeepTree;
|
||||
@@ -1,113 +0,0 @@
|
||||
import createRenderBenchmark from '../createRenderBenchmark';
|
||||
import Tweet from '../src/components/Tweet';
|
||||
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 =>
|
||||
createRenderBenchmark({
|
||||
name: `Tweet [${label}]`,
|
||||
runs: 10,
|
||||
getElement() {
|
||||
return (
|
||||
<div style={{ width: 500 }}>
|
||||
<Tweet tweet={tweet1} />
|
||||
<Tweet tweet={tweet2} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default renderTweet;
|
||||
@@ -1,14 +0,0 @@
|
||||
import createRenderBenchmark from '../createRenderBenchmark';
|
||||
import NestedTree from '../src/components/NestedTree';
|
||||
import React from 'react';
|
||||
|
||||
const renderWideTree = (label, components) =>
|
||||
createRenderBenchmark({
|
||||
name: `Wide tree [${label}]`,
|
||||
runs: 20,
|
||||
getElement() {
|
||||
return <NestedTree breadth={10} components={components} depth={3} id={0} wrap={4} />;
|
||||
}
|
||||
});
|
||||
|
||||
export default renderWideTree;
|
||||
1490
benchmarks/yarn.lock
1490
benchmarks/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -1,211 +0,0 @@
|
||||
# Getting started
|
||||
|
||||
This guide will help you to correctly configure build and test tools to work
|
||||
with React Native for Web. (Alternatively, you can quickly setup a local
|
||||
project using the starter kits listed in the README.)
|
||||
|
||||
It is recommended that your application provide a `Promise` and `Array.from`
|
||||
polyfill.
|
||||
|
||||
## Web packager
|
||||
|
||||
[Webpack](https://webpack.js.org) is a popular build tool for web apps. Below is an
|
||||
example of how to configure a build that uses [Babel](https://babeljs.io/) to
|
||||
compile your JavaScript for the web.
|
||||
|
||||
Create a `web/webpack.config.js` file:
|
||||
|
||||
```js
|
||||
// web/webpack.config.js
|
||||
|
||||
// This is needed for webpack to compile JavaScript.
|
||||
// Many OSS React Native packages are not compiled to ES5 before being
|
||||
// published. If you depend on uncompiled packages they may cause webpack build
|
||||
// errors. To fix this webpack can be configured to compile to the necessary
|
||||
// `node_module`.
|
||||
const babelLoaderConfiguration = {
|
||||
test: /\.js$/,
|
||||
// Add every directory that needs to be compiled by Babel during the build
|
||||
include: [
|
||||
path.resolve(__dirname, 'src'),
|
||||
path.resolve(__dirname, 'node_modules/react-native-uncompiled')
|
||||
],
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
cacheDirectory: true,
|
||||
// This aliases 'react-native' to 'react-native-web' and includes only
|
||||
// the modules needed by the app
|
||||
plugins: ['react-native-web/babel']
|
||||
// The 'react-native' preset is recommended (or use your own .babelrc)
|
||||
presets: ['react-native']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This is needed for webpack to import static images in JavaScript files
|
||||
const imageLoaderConfiguration = {
|
||||
test: /\.(gif|jpe?g|png|svg)$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
name: '[name].[ext]'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// ...the rest of your config
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
babelLoaderConfiguration,
|
||||
imageLoaderConfiguration
|
||||
]
|
||||
},
|
||||
|
||||
plugins: [
|
||||
// `process.env.NODE_ENV === 'production'` must be `true` for production
|
||||
// builds to eliminate development checks and reduce build size. You may
|
||||
// wish to include additional optimizations.
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify('production')
|
||||
})
|
||||
],
|
||||
|
||||
resolve: {
|
||||
// If you're working on a multi-platform React Native app, web-specific
|
||||
// module implementations should be written in files using the extension
|
||||
// `.web.js`.
|
||||
extensions: [ '.web.js', '.js' ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To run in development:
|
||||
|
||||
```
|
||||
./node_modules/.bin/webpack-dev-server -d --config web/webpack.config.js --inline --hot --colors
|
||||
```
|
||||
|
||||
To build for production:
|
||||
|
||||
```
|
||||
./node_modules/.bin/webpack -p --config web/webpack.config.js
|
||||
```
|
||||
|
||||
Please refer to the Webpack documentation for more information on configuration.
|
||||
|
||||
## Web entry
|
||||
|
||||
Create a `index.web.js` file (or simply `index.js` for web-only apps).
|
||||
|
||||
### Client-side rendering
|
||||
|
||||
Rendering using `AppRegistry`:
|
||||
|
||||
```js
|
||||
// index.web.js
|
||||
|
||||
import App from './src/App';
|
||||
import React from 'react';
|
||||
import ReactNative, { AppRegistry } from 'react-native';
|
||||
|
||||
// register the app
|
||||
AppRegistry.registerComponent('App', () => App);
|
||||
|
||||
AppRegistry.runApplication('App', {
|
||||
initialProps: {},
|
||||
rootTag: document.getElementById('react-app')
|
||||
});
|
||||
```
|
||||
|
||||
Rendering within existing web apps is also possible using `ReactNative`:
|
||||
|
||||
```js
|
||||
import AppHeader from './src/AppHeader';
|
||||
import React from 'react';
|
||||
import ReactNative from 'react-native';
|
||||
|
||||
// use .hydrate if hydrating a SSR app
|
||||
ReactNative.render(<AppHeader />, document.getElementById('react-app-header'))
|
||||
```
|
||||
|
||||
And finally, `react-native-web` components will also be rendering within a tree
|
||||
produced by calling `ReactDOM.render` (i.e., an existing web app), but
|
||||
otherwise it is not recommended.
|
||||
|
||||
### Server-side rendering
|
||||
|
||||
Server-side rendering is supported using the `AppRegistry`:
|
||||
|
||||
```js
|
||||
import App from './src/App';
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
import ReactNative, { AppRegistry } from 'react-native'
|
||||
|
||||
// register the app
|
||||
AppRegistry.registerComponent('App', () => App)
|
||||
|
||||
// prerender the app
|
||||
const { element, stylesheets } = AppRegistry.getApplication('App', { initialProps });
|
||||
const initialHTML = ReactDOMServer.renderToString(element);
|
||||
const initialStyles = stylesheets.map((sheet) => ReactDOMServer.renderToStaticMarkup(sheet)).join('\n');
|
||||
|
||||
// construct HTML document
|
||||
const document = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
${initialStyles}
|
||||
</head>
|
||||
<body>
|
||||
${initialHTML}
|
||||
`
|
||||
```
|
||||
|
||||
## Web-specific code
|
||||
|
||||
Minor platform differences can use the `Platform` module.
|
||||
|
||||
```js
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
height: (Platform.OS === 'web') ? 200 : 100,
|
||||
});
|
||||
```
|
||||
|
||||
More significant platform differences should use platform-specific files (see
|
||||
the webpack configuration above for resolving `*.web.js` files):
|
||||
|
||||
For example, with the following files in your project:
|
||||
|
||||
```
|
||||
MyComponent.android.js
|
||||
MyComponent.ios.js
|
||||
MyComponent.web.js
|
||||
```
|
||||
|
||||
And the following import:
|
||||
|
||||
```js
|
||||
import MyComponent from './MyComponent';
|
||||
```
|
||||
|
||||
React Native will automatically import the correct variant for each specific
|
||||
target platform.
|
||||
|
||||
## Testing with Jest
|
||||
|
||||
[Jest](https://facebook.github.io/jest/) also needs to map `react-native` to `react-native-web`.
|
||||
|
||||
```
|
||||
"jest": {
|
||||
"moduleNameMapper": {
|
||||
"react-native": "<rootDir>/node_modules/react-native-web"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Please refer to the Jest documentation for more information.
|
||||
@@ -1,31 +0,0 @@
|
||||
# Known issues
|
||||
|
||||
## Safari flexbox performance
|
||||
|
||||
Safari version prior to 10.1 can suffer from extremely [poor flexbox
|
||||
performance](https://bugs.webkit.org/show_bug.cgi?id=150445). The recommended
|
||||
way to work around this issue (as used on mobile.twitter.com) is to set
|
||||
`display:block` on Views in your element hierarchy that you know don't need
|
||||
flexbox layout.
|
||||
|
||||
## Missing modules and components
|
||||
|
||||
Not all of the views present on iOS/Android are currently available on Web. We
|
||||
are very much interested in the community's feedback on the next set of modules
|
||||
and views.
|
||||
|
||||
Not all the modules or views for iOS/Android can be implemented on Web. In some
|
||||
cases it will be necessary to use a Web counterpart or to guard the use of a
|
||||
module with `Platform.OS` (e.g. `NativeModules`)
|
||||
|
||||
## Missing component props
|
||||
|
||||
There are properties that do not work across all platforms. All web-specific
|
||||
props are annotated with `(web)` in the documentation.
|
||||
|
||||
## Platform parity
|
||||
|
||||
There are some known issues in React Native where APIs could be made more
|
||||
consistent between platforms. For example, React Native for Web includes
|
||||
`ActivityIndicator` and a horizontal `ProgressBar` for Web use, in anticipation
|
||||
of the convergence between the iOS and Android components in React Native.
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build": "yarn && build-storybook -o ./dist -c ./storybook/.storybook",
|
||||
"start": "start-storybook -p 9001 -c ./storybook/.storybook",
|
||||
"release": "yarn build && git checkout gh-pages && rm -rf ../storybook && mv dist ../storybook && git add -A && git commit -m \"Storybook deploy\" && git push origin gh-pages && git checkout -"
|
||||
},
|
||||
"dependencies": {
|
||||
"@storybook/addon-options": "^3.1.6",
|
||||
"@storybook/react": "^3.1.9"
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import sources from '../sources';
|
||||
import React from 'react';
|
||||
import { Image, StyleSheet, Text } from 'react-native';
|
||||
|
||||
const ImageChildrenExample = () => (
|
||||
<Image source={sources.large} style={styles.image}>
|
||||
<Text style={styles.text}>React</Text>
|
||||
</Image>
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
image: {
|
||||
width: 60,
|
||||
height: 60,
|
||||
backgroundColor: 'transparent',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
text: {
|
||||
backgroundColor: 'transparent',
|
||||
color: 'white'
|
||||
}
|
||||
});
|
||||
|
||||
export default ImageChildrenExample;
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,28 +0,0 @@
|
||||
import placeholder from './bunny.png';
|
||||
import staticImage from './uie_thumb_normal@2x.png';
|
||||
|
||||
const dataPng =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
|
||||
const dataSvg =
|
||||
'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>';
|
||||
|
||||
const sources = {
|
||||
animatedGif: {
|
||||
uri:
|
||||
'http://38.media.tumblr.com/9e9bd08c6e2d10561dd1fb4197df4c4e/tumblr_mfqekpMktw1rn90umo1_500.gif'
|
||||
},
|
||||
broken: { uri: 'http://TYPO_ERROR.github.io/image.png' },
|
||||
small: { uri: 'http://facebook.github.io/react/img/logo_small_2x.png' },
|
||||
large: { uri: 'http://facebook.github.io/react/img/logo_og.png' },
|
||||
largeAlt: { uri: 'http://facebook.github.io/origami/public/images/birds.jpg' },
|
||||
placeholder,
|
||||
prefetchable: { uri: 'http://origami.design/public/images/bird-logo.png' },
|
||||
static: staticImage,
|
||||
huge: {
|
||||
uri: 'https://upload.wikimedia.org/wikipedia/commons/d/d7/Chestnut-mandibled_Toucan.jpg'
|
||||
},
|
||||
dataPng,
|
||||
dataSvg
|
||||
};
|
||||
|
||||
export default sources;
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 850 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import { logger } from '../helpers';
|
||||
import React from 'react';
|
||||
import { Text, TouchableHighlight, View } from 'react-native';
|
||||
|
||||
const ViewStyleExample = () => (
|
||||
<View pointerEvents="box-none">
|
||||
<View pointerEvents="box-none">
|
||||
<View pointerEvents="none">
|
||||
<Text onPress={logger}>none</Text>
|
||||
</View>
|
||||
<TouchableHighlight onPress={logger} pointerEvents="auto">
|
||||
<Text>auto</Text>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight onPress={logger} pointerEvents="box-only">
|
||||
<Text>box-only</Text>
|
||||
</TouchableHighlight>
|
||||
<TouchableHighlight onPress={logger} pointerEvents="box-none">
|
||||
<Text>box-none</Text>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
export default ViewStyleExample;
|
||||
@@ -1,4 +0,0 @@
|
||||
import Enzyme from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
159
package.json
159
package.json
@@ -1,115 +1,76 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.1.2",
|
||||
"description": "React Native for Web",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/module.js",
|
||||
"files": [
|
||||
"babel",
|
||||
"dist",
|
||||
"src",
|
||||
"!**/__tests__"
|
||||
],
|
||||
"private": true,
|
||||
"version": "0.5.1",
|
||||
"name": "react-native-web-monorepo",
|
||||
"scripts": {
|
||||
"benchmark": "cd benchmarks && yarn && webpack && open index.html",
|
||||
"build": "yarn compile && webpack --config webpack.config.js --sort-assets-by --progress",
|
||||
"clean-dist": "del ./dist && mkdir dist",
|
||||
"compile": "babel src -d dist --ignore *-test.js",
|
||||
"docs:build": "cd docs && yarn build",
|
||||
"docs:start": "cd docs && yarn && yarn start",
|
||||
"docs:release": "cd docs && yarn release",
|
||||
"clean": "del ./packages/*/dist",
|
||||
"compile": "yarn clean && cd packages/react-native-web && babel src --out-dir dist --ignore \"**/__tests__\"",
|
||||
"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 babel benchmarks docs src -name '*.js' | grep -v -E '(node_modules|dist)' | xargs yarn fmt:cmd",
|
||||
"fmt:cmd": "prettier --print-width=100 --single-quote --write",
|
||||
"jest": "jest",
|
||||
"jest:watch": "yarn test -- --watch",
|
||||
"lint": "yarn lint:cmd -- babel benchmarks docs src",
|
||||
"lint:cmd": "eslint --ignore-path .gitignore --fix",
|
||||
"fmt": "find packages scripts types website -name '*.js' | grep -v -E '(node_modules|dist|vendor)' | xargs yarn fmt:cmd",
|
||||
"fmt:cmd": "prettier --write",
|
||||
"jest": "jest --config ./scripts/jest/config.js",
|
||||
"lint": "yarn lint:check --fix",
|
||||
"lint:check": "eslint packages scripts website",
|
||||
"precommit": "lint-staged",
|
||||
"release": "yarn clean-dist && yarn lint && yarn test && yarn build && npm publish",
|
||||
"test": "flow && jest"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [ "react-native" ],
|
||||
"plugins": [
|
||||
[ "transform-react-remove-prop-types", { "mode": "wrap" } ]
|
||||
]
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom",
|
||||
"timers": "fake",
|
||||
"setupFiles": [ "raf/polyfill" ],
|
||||
"setupTestFrameworkScriptFile": "<rootDir>/jest-setup-framework.js",
|
||||
"snapshotSerializers": [ "enzyme-to-json/serializer" ]
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.js": [
|
||||
"fmt:cmd",
|
||||
"git update-index --again",
|
||||
"lint:cmd"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"animated": "^0.2.0",
|
||||
"array-find-index": "^1.0.2",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"create-react-class": "^15.6.2",
|
||||
"debounce": "1.0.2",
|
||||
"deep-assign": "^2.0.0",
|
||||
"fbjs": "^0.8.16",
|
||||
"hyphenate-style-name": "^1.0.2",
|
||||
"inline-style-prefixer": "^3.0.8",
|
||||
"normalize-css-color": "^1.0.2",
|
||||
"prop-types": "^15.6.0",
|
||||
"react-timer-mixin": "^0.13.3"
|
||||
"prerelease": "yarn test && yarn compile",
|
||||
"release": "node ./scripts/release/publish.js",
|
||||
"postrelease": "yarn website:release && yarn benchmarks:release",
|
||||
"test": "yarn flow && yarn lint:check && yarn jest --runInBand"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-eslint": "^7.2.3",
|
||||
"babel-eslint": "^8.0.3",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-tester": "^4.0.0",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.8",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.10",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-flow": "^6.23.0",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babel-preset-react-native": "^4.0.0",
|
||||
"caniuse-api": "^2.0.0",
|
||||
"del-cli": "^1.1.0",
|
||||
"enzyme": "^3.0.0",
|
||||
"enzyme-adapter-react-16": "^1.0.0",
|
||||
"enzyme-to-json": "next",
|
||||
"eslint": "^4.6.1",
|
||||
"eslint-config-prettier": "^2.4.0",
|
||||
"eslint-plugin-promise": "^3.5.0",
|
||||
"eslint-plugin-react": "^7.3.0",
|
||||
"file-loader": "^0.11.2",
|
||||
"flow-bin": "^0.49.1",
|
||||
"jest": "^21.1.0",
|
||||
"lint-staged": "^4.1.3",
|
||||
"prettier": "^1.7.0",
|
||||
"raf": "^3.3.2",
|
||||
"react": "^16.0.0",
|
||||
"react-dom": "^16.0.0",
|
||||
"react-test-renderer": "^16.0.0",
|
||||
"url-loader": "^0.5.9",
|
||||
"webpack": "^3.6.0",
|
||||
"webpack-bundle-analyzer": "^2.9.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.63.1",
|
||||
"glob": "^7.1.2",
|
||||
"husky": "^0.14.3",
|
||||
"jest": "^21.2.1",
|
||||
"lint-staged": "^6.0.0",
|
||||
"prettier": "^1.8.2",
|
||||
"raf": "^3.4.0",
|
||||
"react": "^16.2.0",
|
||||
"react-dom": "^16.2.0",
|
||||
"react-test-renderer": "^16.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "16.x.x",
|
||||
"react-dom": "16.x.x"
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"website"
|
||||
],
|
||||
"lint-staged": {
|
||||
"**/*.js": [
|
||||
"fmt:cmd",
|
||||
"git update-index --again",
|
||||
"eslint"
|
||||
],
|
||||
"packages/react-native-web/src/index.js": [
|
||||
"node ./scripts/babel/createModuleMap.js"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 100,
|
||||
"singleQuote": true
|
||||
},
|
||||
"author": "Nicolas Gallagher",
|
||||
"license": "BSD-3-Clause",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/necolas/react-native-web.git"
|
||||
},
|
||||
"tags": [
|
||||
"react"
|
||||
],
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-component",
|
||||
"react-native",
|
||||
"web"
|
||||
]
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
44
packages/babel-plugin-react-native-web/README.md
Normal file
44
packages/babel-plugin-react-native-web/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# babel-plugin-react-native-web
|
||||
|
||||
[![npm version][package-badge]][package-url] [](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).
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
yarn add --dev babel-plugin-react-native-web
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
**.babelrc**
|
||||
|
||||
```
|
||||
{
|
||||
"plugins": ["react-native-web"]
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
NOTE: `react-native-web` internal paths are _not stable_ and you must not rely
|
||||
on them. Always use the Babel plugin to optimize your build. What follows is an
|
||||
example of the rewrite performed by the plugin.
|
||||
|
||||
**Before**
|
||||
|
||||
```js
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```js
|
||||
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
|
||||
1
packages/babel-plugin-react-native-web/index.js
Normal file
1
packages/babel-plugin-react-native-web/index.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('./src');
|
||||
15
packages/babel-plugin-react-native-web/package.json
Normal file
15
packages/babel-plugin-react-native-web/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "babel-plugin-react-native-web",
|
||||
"version": "0.5.1",
|
||||
"description": "Babel plugin for React Native for Web",
|
||||
"main": "index.js",
|
||||
"devDependencies": {
|
||||
"babel-plugin-tester": "^5.0.0"
|
||||
},
|
||||
"author": "Nicolas Gallagher",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/necolas/react-native-web.git"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`export from "react-native" 1`] = `
|
||||
"
|
||||
export { View } from 'react-native';
|
||||
export { ColorPropType, StyleSheet, Text, createElement } from 'react-native';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
export { default as View } from 'react-native-web/dist/exports/View';
|
||||
export { default as ColorPropType } from 'react-native-web/dist/exports/ColorPropType';
|
||||
export { default as StyleSheet } from 'react-native-web/dist/exports/StyleSheet';
|
||||
export { default as Text } from 'react-native-web/dist/exports/Text';
|
||||
export { default as createElement } from 'react-native-web/dist/exports/createElement';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`export from "react-native-web" 1`] = `
|
||||
"
|
||||
export { View } from 'react-native-web';
|
||||
export { ColorPropType, StyleSheet, Text, createElement } from 'react-native-web';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
export { default as View } from 'react-native-web/dist/exports/View';
|
||||
export { default as ColorPropType } from 'react-native-web/dist/exports/ColorPropType';
|
||||
export { default as StyleSheet } from 'react-native-web/dist/exports/StyleSheet';
|
||||
export { default as Text } from 'react-native-web/dist/exports/Text';
|
||||
export { default as createElement } from 'react-native-web/dist/exports/createElement';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`import from "native-native" 1`] = `
|
||||
"
|
||||
import ReactNative from 'react-native';
|
||||
import { View } from 'react-native';
|
||||
import { Invalid, View as MyView, ViewPropTypes } from 'react-native';
|
||||
import * as ReactNativeModules from 'react-native';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
import ReactNative from 'react-native-web/dist/index';
|
||||
import View from 'react-native-web/dist/exports/View';
|
||||
import { Invalid } from 'react-native-web/dist/index';
|
||||
import MyView from 'react-native-web/dist/exports/View';
|
||||
import ViewPropTypes from 'react-native-web/dist/exports/ViewPropTypes';
|
||||
import * as ReactNativeModules from 'react-native-web/dist/index';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`import from "react-native-web" 1`] = `
|
||||
"
|
||||
import { createElement } from 'react-native-web';
|
||||
import { ColorPropType, StyleSheet, View, TouchableOpacity, processColor } from 'react-native-web';
|
||||
import * as ReactNativeModules from 'react-native-web';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
import createElement from 'react-native-web/dist/exports/createElement';
|
||||
import ColorPropType from 'react-native-web/dist/exports/ColorPropType';
|
||||
import StyleSheet from 'react-native-web/dist/exports/StyleSheet';
|
||||
import View from 'react-native-web/dist/exports/View';
|
||||
import TouchableOpacity from 'react-native-web/dist/exports/TouchableOpacity';
|
||||
import processColor from 'react-native-web/dist/exports/processColor';
|
||||
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').default;
|
||||
|
||||
const View = require('react-native-web/dist/exports/View').default;
|
||||
|
||||
const StyleSheet = require('react-native-web/dist/exports/StyleSheet').default;
|
||||
|
||||
const TouchableOpacity = require('react-native-web/dist/exports/TouchableOpacity').default;
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`require "react-native-web" 1`] = `
|
||||
"
|
||||
const ReactNative = require('react-native-web');
|
||||
const { createElement } = require('react-native-web');
|
||||
const { ColorPropType, StyleSheet, View, TouchableOpacity, processColor } = require('react-native-web');
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
const ReactNative = require('react-native-web/dist/index').default;
|
||||
|
||||
const createElement = require('react-native-web/dist/exports/createElement').default;
|
||||
|
||||
const ColorPropType = require('react-native-web/dist/exports/ColorPropType').default;
|
||||
|
||||
const StyleSheet = require('react-native-web/dist/exports/StyleSheet').default;
|
||||
|
||||
const View = require('react-native-web/dist/exports/View').default;
|
||||
|
||||
const TouchableOpacity = require('react-native-web/dist/exports/TouchableOpacity').default;
|
||||
|
||||
const processColor = require('react-native-web/dist/exports/processColor').default;
|
||||
"
|
||||
`;
|
||||
@@ -0,0 +1,52 @@
|
||||
const plugin = require('..');
|
||||
const pluginTester = require('babel-plugin-tester');
|
||||
|
||||
const tests = [
|
||||
// import react-native
|
||||
{
|
||||
title: 'import from "native-native"',
|
||||
code: `import ReactNative from 'react-native';
|
||||
import { View } from 'react-native';
|
||||
import { Invalid, View as MyView, ViewPropTypes } from 'react-native';
|
||||
import * as ReactNativeModules from 'react-native';`,
|
||||
snapshot: true
|
||||
},
|
||||
{
|
||||
title: 'import from "react-native-web"',
|
||||
code: `import { createElement } from 'react-native-web';
|
||||
import { ColorPropType, StyleSheet, View, TouchableOpacity, processColor } from 'react-native-web';
|
||||
import * as ReactNativeModules from 'react-native-web';`,
|
||||
snapshot: true
|
||||
},
|
||||
{
|
||||
title: 'export from "react-native"',
|
||||
code: `export { View } from 'react-native';
|
||||
export { ColorPropType, StyleSheet, Text, createElement } from 'react-native';`,
|
||||
snapshot: true
|
||||
},
|
||||
{
|
||||
title: 'export from "react-native-web"',
|
||||
code: `export { View } from 'react-native-web';
|
||||
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');
|
||||
const { createElement } = require('react-native-web');
|
||||
const { ColorPropType, StyleSheet, View, TouchableOpacity, processColor } = require('react-native-web');`,
|
||||
snapshot: true
|
||||
}
|
||||
];
|
||||
|
||||
pluginTester({
|
||||
plugin,
|
||||
tests
|
||||
});
|
||||
128
packages/babel-plugin-react-native-web/src/index.js
Normal file
128
packages/babel-plugin-react-native-web/src/index.js
Normal file
@@ -0,0 +1,128 @@
|
||||
const moduleMap = require('./moduleMap');
|
||||
|
||||
const getDistLocation = importName =>
|
||||
importName && moduleMap[importName] ? `react-native-web/dist/exports/${importName}` : undefined;
|
||||
|
||||
const isReactNativeRequire = (t, node) => {
|
||||
const { declarations } = node;
|
||||
if (declarations.length > 1) {
|
||||
return false;
|
||||
}
|
||||
const { id, init } = declarations[0];
|
||||
return (
|
||||
(t.isObjectPattern(id) || t.isIdentifier(id)) &&
|
||||
t.isCallExpression(init) &&
|
||||
t.isIdentifier(init.callee) &&
|
||||
init.callee.name === 'require' &&
|
||||
init.arguments.length === 1 &&
|
||||
(init.arguments[0].value === 'react-native' || init.arguments[0].value === 'react-native-web')
|
||||
);
|
||||
};
|
||||
|
||||
const isReactNativeModule = ({ source, specifiers }) =>
|
||||
source &&
|
||||
(source.value === 'react-native' || source.value === 'react-native-web') &&
|
||||
specifiers.length;
|
||||
|
||||
module.exports = function({ types: t }) {
|
||||
return {
|
||||
name: 'Rewrite react-native to react-native-web',
|
||||
visitor: {
|
||||
ImportDeclaration(path, state) {
|
||||
const { specifiers } = path.node;
|
||||
if (isReactNativeModule(path.node)) {
|
||||
const imports = specifiers
|
||||
.map(specifier => {
|
||||
if (t.isImportSpecifier(specifier)) {
|
||||
const importName = specifier.imported.name;
|
||||
const distLocation = getDistLocation(importName);
|
||||
|
||||
if (distLocation) {
|
||||
return t.importDeclaration(
|
||||
[t.importDefaultSpecifier(t.identifier(specifier.local.name))],
|
||||
t.stringLiteral(distLocation)
|
||||
);
|
||||
}
|
||||
}
|
||||
return t.importDeclaration(
|
||||
[specifier],
|
||||
t.stringLiteral('react-native-web/dist/index')
|
||||
);
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
path.replaceWithMultiple(imports);
|
||||
}
|
||||
},
|
||||
ExportNamedDeclaration(path, state) {
|
||||
const { specifiers } = path.node;
|
||||
if (isReactNativeModule(path.node)) {
|
||||
const exports = specifiers
|
||||
.map(specifier => {
|
||||
if (t.isExportSpecifier(specifier)) {
|
||||
const exportName = specifier.exported.name;
|
||||
const localName = specifier.local.name;
|
||||
const distLocation = getDistLocation(localName);
|
||||
|
||||
if (distLocation) {
|
||||
return t.exportNamedDeclaration(
|
||||
null,
|
||||
[t.exportSpecifier(t.identifier('default'), t.identifier(exportName))],
|
||||
t.stringLiteral(distLocation)
|
||||
);
|
||||
}
|
||||
}
|
||||
return t.exportNamedDeclaration(
|
||||
null,
|
||||
[specifier],
|
||||
t.stringLiteral('react-native-web/dist/index')
|
||||
);
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
path.replaceWithMultiple(exports);
|
||||
}
|
||||
},
|
||||
VariableDeclaration(path, state) {
|
||||
if (isReactNativeRequire(t, path.node)) {
|
||||
const { id } = path.node.declarations[0];
|
||||
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.memberExpression(
|
||||
t.callExpression(t.identifier('require'), [t.stringLiteral(distLocation)]),
|
||||
t.identifier('default')
|
||||
)
|
||||
)
|
||||
]);
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
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.memberExpression(
|
||||
t.callExpression(t.identifier('require'), [
|
||||
t.stringLiteral('react-native-web/dist/index')
|
||||
]),
|
||||
t.identifier('default')
|
||||
)
|
||||
)
|
||||
]);
|
||||
|
||||
path.replaceWith(importIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
61
packages/babel-plugin-react-native-web/src/moduleMap.js
Normal file
61
packages/babel-plugin-react-native-web/src/moduleMap.js
Normal file
@@ -0,0 +1,61 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
|
||||
module.exports = {
|
||||
ART: true,
|
||||
ActivityIndicator: true,
|
||||
Animated: true,
|
||||
AppRegistry: true,
|
||||
AppState: true,
|
||||
AsyncStorage: true,
|
||||
BackHandler: true,
|
||||
Button: true,
|
||||
CheckBox: true,
|
||||
Clipboard: true,
|
||||
ColorPropType: true,
|
||||
Dimensions: true,
|
||||
Easing: true,
|
||||
EdgeInsetsPropType: true,
|
||||
FlatList: true,
|
||||
I18nManager: true,
|
||||
Image: true,
|
||||
ImageBackground: true,
|
||||
InteractionManager: true,
|
||||
Keyboard: true,
|
||||
KeyboardAvoidingView: true,
|
||||
Linking: true,
|
||||
ListView: true,
|
||||
Modal: true,
|
||||
NativeModules: true,
|
||||
NetInfo: true,
|
||||
PanResponder: true,
|
||||
Picker: true,
|
||||
PixelRatio: true,
|
||||
Platform: true,
|
||||
PointPropType: true,
|
||||
ProgressBar: true,
|
||||
RefreshControl: true,
|
||||
SafeAreaView: true,
|
||||
ScrollView: true,
|
||||
SectionList: true,
|
||||
Slider: true,
|
||||
StatusBar: true,
|
||||
StyleSheet: true,
|
||||
Switch: true,
|
||||
Text: true,
|
||||
TextInput: true,
|
||||
TextPropTypes: true,
|
||||
Touchable: true,
|
||||
TouchableHighlight: true,
|
||||
TouchableNativeFeedback: true,
|
||||
TouchableOpacity: true,
|
||||
TouchableWithoutFeedback: true,
|
||||
UIManager: true,
|
||||
Vibration: true,
|
||||
View: true,
|
||||
ViewPropTypes: true,
|
||||
VirtualizedList: true,
|
||||
createElement: true,
|
||||
findNodeHandle: true,
|
||||
processColor: true,
|
||||
render: true,
|
||||
unmountComponentAtNode: true
|
||||
};
|
||||
70
packages/benchmarks/README.md
Normal file
70
packages/benchmarks/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# benchmarks
|
||||
|
||||
Try the [benchmarks app](https://necolas.github.io/react-native-web/benchmarks) online.
|
||||
|
||||
To run the benchmarks locally:
|
||||
|
||||
```
|
||||
yarn benchmarks
|
||||
open ./packages/benchmarks/dist/index.html
|
||||
```
|
||||
|
||||
Develop against these benchmarks:
|
||||
|
||||
```
|
||||
yarn compile --watch
|
||||
yarn benchmarks --watch
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
These benchmarks are approximations of extreme cases that libraries may
|
||||
encounter. Their purpose is to provide an early-warning signal for performance
|
||||
regressions. Each test report includes the mean and standard deviation of the
|
||||
timings, and approximations of the time spent in scripting (S) and layout (L).
|
||||
|
||||
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.
|
||||
|
||||
### Mount deep/wide tree
|
||||
|
||||
These cases look at the performance of mounting and rendering large trees of
|
||||
elements that use static styles.
|
||||
|
||||
### Update dynamic styles
|
||||
|
||||
This case looks at the performance of repeated style updates to a large mounted
|
||||
tree. Some libraries choose to inject new styles for each "dynamic style",
|
||||
whereas others choose to use inline styles. Libraries without built-in support
|
||||
for dynamic styles (i.e., they rely on user-authored inline styles) are not
|
||||
included.
|
||||
|
||||
## Example results
|
||||
|
||||
### 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) |
|
||||
| :--- | ---: | ---: | ---: |
|
||||
| `css-modules` | `30.19` `±04.84` | `38.25` `±04.85` | - |
|
||||
| `react-native-web@0.4.0` | `36.40` `±04.98` | `51.28` `±05.58` | `19.36` `±02.56` |
|
||||
| `inline-styles` | `64.12` `±07.69` | `94.49` `±11.34` | `09.84` `±02.36` |
|
||||
|
||||
### Moto G4
|
||||
|
||||
Moto G4 (Android 7); Octa-core (4x1.5 GHz & 4x1.2 Ghz); 2 GB RAM. Google Chrome 63.
|
||||
|
||||
Typical render timings: mean ± standard deviations.
|
||||
|
||||
| Implementation | Mount deep tree (ms) | Mount wide tree (ms) | Dynamic update (ms) |
|
||||
| :--- | ---: | ---: | ---: |
|
||||
| `css-modules` | `98.24` `±20.26` | `143.75` `±25.50` | - |
|
||||
| `react-native-web@0.4.0` | `131.46` `±18.96` | `174.70` `±14.88` | `60.87` `±06.32` |
|
||||
| `inline-styles` | `184.58` `±26.23` | `273.86` `±26.23` | `30.28` `±07.44` |
|
||||
16
packages/benchmarks/index.html
Normal file
16
packages/benchmarks/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<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="./bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
37
packages/benchmarks/package.json
Normal file
37
packages/benchmarks/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "benchmarks",
|
||||
"version": "0.5.1",
|
||||
"scripts": {
|
||||
"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",
|
||||
"classnames": "^2.2.5",
|
||||
"d3-scale-chromatic": "^1.2.0",
|
||||
"emotion": "^9.0.1",
|
||||
"fela": "6.1.3",
|
||||
"glamor": "2.20.40",
|
||||
"radium": "^0.22.0",
|
||||
"react": "^16.2.0",
|
||||
"react-component-benchmark": "^0.0.4",
|
||||
"react-dom": "^16.2.0",
|
||||
"react-fela": "6.2.4",
|
||||
"react-jss": "^8.3.3",
|
||||
"react-native-web": "0.5.1",
|
||||
"reactxp": "^0.51.8",
|
||||
"style-loader": "0.20.1",
|
||||
"styled-components": "^3.1.6",
|
||||
"styled-jsx": "^2.2.4",
|
||||
"styletron-client": "3.0.4",
|
||||
"styletron-react": "3.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-react-native-web": "0.5.1",
|
||||
"css-loader": "^0.28.9",
|
||||
"style-loader": "^0.20.2",
|
||||
"webpack": "^3.10.0",
|
||||
"webpack-bundle-analyzer": "^2.9.2"
|
||||
}
|
||||
}
|
||||
298
packages/benchmarks/src/app/App.js
Normal file
298
packages/benchmarks/src/app/App.js
Normal file
@@ -0,0 +1,298 @@
|
||||
/* 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}
|
||||
meanLayout={r.meanLayout}
|
||||
meanScripting={r.meanScripting}
|
||||
runTime={r.runTime}
|
||||
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}
|
||||
forceLayout={true}
|
||||
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,
|
||||
appearance: 'none',
|
||||
opacity: 0,
|
||||
width: '100%'
|
||||
},
|
||||
button: {
|
||||
borderRadius: 0,
|
||||
fontSize: 32,
|
||||
flex: 1
|
||||
}
|
||||
});
|
||||
247
packages/benchmarks/src/app/Benchmark/index.js
Normal file
247
packages/benchmarks/src/app/Benchmark/index.js
Normal file
@@ -0,0 +1,247 @@
|
||||
/* global $Values */
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
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 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,
|
||||
forceLayout?: boolean,
|
||||
getComponentProps: Function,
|
||||
onComplete: (x: BenchResultsType) => void,
|
||||
sampleCount: number,
|
||||
timeout: number,
|
||||
type: $Values<typeof BenchmarkType>
|
||||
};
|
||||
|
||||
type BenchmarkStateType = {
|
||||
componentProps: Object,
|
||||
cycle: number,
|
||||
running: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Benchmark
|
||||
* TODO: documentation
|
||||
*/
|
||||
export default class Benchmark extends Component<BenchmarkPropsType, BenchmarkStateType> {
|
||||
_raf: ?Function;
|
||||
_startTime: number;
|
||||
_samples: Array<SampleTimingType>;
|
||||
|
||||
static displayName = 'Benchmark';
|
||||
|
||||
static defaultProps = {
|
||||
sampleCount: 50,
|
||||
timeout: 10000, // 10 seconds
|
||||
type: BenchmarkType.MOUNT
|
||||
};
|
||||
|
||||
static Type = BenchmarkType;
|
||||
|
||||
constructor(props: BenchmarkPropsType, context?: {}) {
|
||||
super(props, context);
|
||||
const cycle = 0;
|
||||
const componentProps = props.getComponentProps({ cycle });
|
||||
this.state = {
|
||||
componentProps,
|
||||
cycle,
|
||||
running: false
|
||||
};
|
||||
this._startTime = 0;
|
||||
this._samples = [];
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: BenchmarkPropsType) {
|
||||
if (nextProps) {
|
||||
this.setState(state => ({ componentProps: nextProps.getComponentProps(state.cycle) }));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps: BenchmarkPropsType, nextState: BenchmarkStateType) {
|
||||
if (nextState.running && !this.state.running) {
|
||||
this._startTime = Timing.now();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { forceLayout, sampleCount, timeout, type } = this.props;
|
||||
const { cycle, running } = this.state;
|
||||
|
||||
if (running && shouldRecord(cycle, type)) {
|
||||
this._samples[cycle].scriptingEnd = Timing.now();
|
||||
|
||||
// force style recalc that would otherwise happen before the next frame
|
||||
if (forceLayout) {
|
||||
this._samples[cycle].layoutStart = Timing.now();
|
||||
if (document.body) {
|
||||
document.body.offsetWidth;
|
||||
}
|
||||
this._samples[cycle].layoutEnd = 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 { componentProps, cycle, running } = this.state;
|
||||
if (running && shouldRecord(cycle, type)) {
|
||||
this._samples[cycle] = { scriptingStart: Timing.now() };
|
||||
}
|
||||
return running && shouldRender(cycle, type) ? <Component {...componentProps} /> : null;
|
||||
}
|
||||
|
||||
start() {
|
||||
this._samples = [];
|
||||
this.setState(() => ({ running: true, cycle: 0 }));
|
||||
}
|
||||
|
||||
_handleCycleComplete() {
|
||||
const { getComponentProps, type } = this.props;
|
||||
const { cycle } = this.state;
|
||||
|
||||
let componentProps;
|
||||
if (getComponentProps) {
|
||||
// Calculate the component props outside of the time recording (render)
|
||||
// so that it doesn't skew results
|
||||
componentProps = getComponentProps({ cycle });
|
||||
// make sure props always change for update tests
|
||||
if (type === BenchmarkType.UPDATE) {
|
||||
componentProps['data-test'] = cycle;
|
||||
}
|
||||
}
|
||||
|
||||
this._raf = window.requestAnimationFrame(() => {
|
||||
this.setState((state: BenchmarkStateType) => ({
|
||||
cycle: state.cycle + 1,
|
||||
componentProps
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
getSamples(): Array<FullSampleTimingType> {
|
||||
return this._samples.reduce(
|
||||
(
|
||||
memo: Array<FullSampleTimingType>,
|
||||
{ scriptingStart, scriptingEnd, layoutStart, layoutEnd }: SampleTimingType
|
||||
): Array<FullSampleTimingType> => {
|
||||
memo.push({
|
||||
start: scriptingStart,
|
||||
end: layoutEnd || scriptingEnd || 0,
|
||||
scriptingStart,
|
||||
scriptingEnd: scriptingEnd || 0,
|
||||
layoutStart,
|
||||
layoutEnd
|
||||
});
|
||||
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(({ start, end }) => end - start).sort(sortNumbers);
|
||||
const sortedScriptingElapsedTimes = samples
|
||||
.map(({ scriptingStart, scriptingEnd }) => scriptingEnd - scriptingStart)
|
||||
.sort(sortNumbers);
|
||||
const sortedLayoutElapsedTimes = samples
|
||||
.map(({ layoutStart, layoutEnd }) => (layoutEnd || 0) - (layoutStart || 0))
|
||||
.sort(sortNumbers);
|
||||
|
||||
onComplete({
|
||||
startTime: this._startTime,
|
||||
endTime,
|
||||
runTime,
|
||||
sampleCount: samples.length,
|
||||
samples: samples,
|
||||
max: sortedElapsedTimes[sortedElapsedTimes.length - 1],
|
||||
min: sortedElapsedTimes[0],
|
||||
median: getMedian(sortedElapsedTimes),
|
||||
mean: getMean(sortedElapsedTimes),
|
||||
stdDev: getStdDev(sortedElapsedTimes),
|
||||
meanLayout: getMean(sortedLayoutElapsedTimes),
|
||||
meanScripting: getMean(sortedScriptingElapsedTimes)
|
||||
});
|
||||
}
|
||||
}
|
||||
27
packages/benchmarks/src/app/Benchmark/math.js
Normal file
27
packages/benchmarks/src/app/Benchmark/math.js
Normal 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;
|
||||
};
|
||||
18
packages/benchmarks/src/app/Benchmark/timing.js
Normal file
18
packages/benchmarks/src/app/Benchmark/timing.js
Normal 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();
|
||||
}
|
||||
}
|
||||
31
packages/benchmarks/src/app/Benchmark/types.js
Normal file
31
packages/benchmarks/src/app/Benchmark/types.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @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
|
||||
};
|
||||
|
||||
export type SampleTimingType = {
|
||||
scriptingStart: number,
|
||||
scriptingEnd?: number,
|
||||
layoutStart?: number,
|
||||
layoutEnd?: number
|
||||
};
|
||||
|
||||
export type FullSampleTimingType = {
|
||||
start: number,
|
||||
end: number,
|
||||
scriptingStart: number,
|
||||
scriptingEnd: number,
|
||||
layoutStart?: number,
|
||||
layoutEnd?: number
|
||||
};
|
||||
70
packages/benchmarks/src/app/Button.js
Normal file
70
packages/benchmarks/src/app/Button.js
Normal 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'
|
||||
}
|
||||
});
|
||||
55
packages/benchmarks/src/app/Icons.js
Normal file
55
packages/benchmarks/src/app/Icons.js
Normal 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>
|
||||
);
|
||||
68
packages/benchmarks/src/app/Layout.js
Normal file
68
packages/benchmarks/src/app/Layout.js
Normal file
@@ -0,0 +1,68 @@
|
||||
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, styles.layer]}>
|
||||
{viewPanel}
|
||||
</View>
|
||||
<View style={styles.grow}>
|
||||
<View style={[styles.grow, styles.layer]}>{listPanel}</View>
|
||||
<View style={styles.divider} />
|
||||
<View style={styles.layer}>{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%'
|
||||
},
|
||||
layer: {
|
||||
transform: [{ translateZ: '0' }]
|
||||
}
|
||||
});
|
||||
83
packages/benchmarks/src/app/ReportCard.js
Normal file
83
packages/benchmarks/src/app/ReportCard.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/* 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;
|
||||
};
|
||||
|
||||
class ReportCard extends React.PureComponent {
|
||||
render() {
|
||||
const {
|
||||
benchmarkName,
|
||||
libraryName,
|
||||
sampleCount,
|
||||
mean,
|
||||
meanLayout,
|
||||
meanScripting,
|
||||
stdDev,
|
||||
libraryVersion
|
||||
} = this.props;
|
||||
|
||||
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.smallText, styles.monoFont]}>
|
||||
(S/L) {fmt(meanScripting)}/{fmt(meanLayout)} ms
|
||||
</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;
|
||||
30
packages/benchmarks/src/app/Text.js
Normal file
30
packages/benchmarks/src/app/Text.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
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;
|
||||
101
packages/benchmarks/src/app/theme.js
Normal file
101
packages/benchmarks/src/app/theme.js
Normal 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, "MS 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);
|
||||
});
|
||||
}
|
||||
90
packages/benchmarks/src/cases/SierpinskiTriangle.js
Normal file
90
packages/benchmarks/src/cases/SierpinskiTriangle.js
Normal file
@@ -0,0 +1,90 @@
|
||||
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 = 10;
|
||||
|
||||
class SierpinskiTriangle extends React.Component {
|
||||
static displayName = 'SierpinskiTriangle';
|
||||
|
||||
static benchmarkType = BenchmarkType.UPDATE;
|
||||
|
||||
static propTypes = {
|
||||
components: object,
|
||||
depth: number,
|
||||
renderCount: number,
|
||||
s: number,
|
||||
x: number,
|
||||
y: number
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
depth: 0,
|
||||
renderCount: 0
|
||||
};
|
||||
|
||||
render() {
|
||||
const { components, x, y, depth, renderCount } = this.props;
|
||||
let { s } = this.props;
|
||||
const { Dot } = components;
|
||||
|
||||
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} />
|
||||
);
|
||||
}
|
||||
|
||||
s /= 2;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SierpinskiTriangle
|
||||
components={components}
|
||||
depth={1}
|
||||
renderCount={renderCount}
|
||||
s={s}
|
||||
x={x}
|
||||
y={y - s / 2}
|
||||
/>
|
||||
<SierpinskiTriangle
|
||||
components={components}
|
||||
depth={2}
|
||||
renderCount={renderCount}
|
||||
s={s}
|
||||
x={x - s}
|
||||
y={y + s / 2}
|
||||
/>
|
||||
<SierpinskiTriangle
|
||||
components={components}
|
||||
depth={3}
|
||||
renderCount={renderCount}
|
||||
s={s}
|
||||
x={x + s}
|
||||
y={y + s / 2}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
return <span style={{ color: 'white' }}>No implementation available</span>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SierpinskiTriangle;
|
||||
45
packages/benchmarks/src/cases/Tree.js
Normal file
45
packages/benchmarks/src/cases/Tree.js
Normal 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;
|
||||
36
packages/benchmarks/src/impl.js
Normal file
36
packages/benchmarks/src/impl.js
Normal file
@@ -0,0 +1,36 @@
|
||||
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;
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import View from '../View/aphrodite';
|
||||
import View from './View';
|
||||
import { StyleSheet } from 'aphrodite';
|
||||
|
||||
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
import View from './View';
|
||||
export default View;
|
||||
@@ -0,0 +1,9 @@
|
||||
import Box from './Box';
|
||||
import Provider from './Provider';
|
||||
import View from './View';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
Provider,
|
||||
View
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import classnames from 'classnames';
|
||||
import React from 'react';
|
||||
import View from '../View/css-modules';
|
||||
import styles from './styles.css';
|
||||
import View from './View';
|
||||
import styles from './box-styles.css';
|
||||
|
||||
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
|
||||
<View
|
||||
@@ -0,0 +1,2 @@
|
||||
import View from './View';
|
||||
export default View;
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import classnames from 'classnames';
|
||||
import React from 'react';
|
||||
import styles from './styles.css';
|
||||
import styles from './view-styles.css';
|
||||
|
||||
class View extends React.Component {
|
||||
render() {
|
||||
@@ -0,0 +1,37 @@
|
||||
.outer {
|
||||
align-self: flex-start;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.color0 {
|
||||
background-color: #14171A;
|
||||
}
|
||||
|
||||
.color1 {
|
||||
background-color: #AAB8C2;
|
||||
}
|
||||
|
||||
.color2 {
|
||||
background-color: #E6ECF0;
|
||||
}
|
||||
|
||||
.color3 {
|
||||
background-color: #FFAD1F;
|
||||
}
|
||||
|
||||
.color4 {
|
||||
background-color: #F45D22;
|
||||
}
|
||||
|
||||
.color5 {
|
||||
background-color: #E0245E;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import Box from './Box';
|
||||
import Provider from './Provider';
|
||||
import View from './View';
|
||||
|
||||
export default {
|
||||
Box,
|
||||
Provider,
|
||||
View
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import View from '../View/emotion';
|
||||
import View from './View';
|
||||
|
||||
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
|
||||
<View
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
33
packages/benchmarks/src/implementations/emotion/Dot.js
Normal file
33
packages/benchmarks/src/implementations/emotion/Dot.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import { css } from 'emotion';
|
||||
|
||||
const Dot = ({ size, x, y, children, color }) => (
|
||||
<div
|
||||
className={css(styles.root, {
|
||||
borderBottomColor: color,
|
||||
borderRightWidth: `${size / 2}px`,
|
||||
borderBottomWidth: `${size / 2}px`,
|
||||
borderLeftWidth: `${size / 2}px`,
|
||||
marginLeft: `${x}px`,
|
||||
marginTop: `${y}px`
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
const styles = {
|
||||
root: {
|
||||
position: 'absolute',
|
||||
cursor: 'pointer',
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderColor: 'transparent',
|
||||
borderStyle: 'solid',
|
||||
borderTopWidth: 0,
|
||||
transform: 'translate(50%, 50%)'
|
||||
}
|
||||
};
|
||||
|
||||
export default Dot;
|
||||
@@ -0,0 +1,2 @@
|
||||
import View from './View';
|
||||
export default View;
|
||||
@@ -5,7 +5,7 @@ import React from 'react';
|
||||
class View extends React.Component {
|
||||
render() {
|
||||
const { style, ...other } = this.props;
|
||||
return <div {...other} className={css([viewStyle, ...style])} />;
|
||||
return <div {...other} className={css(viewStyle, ...style)} />;
|
||||
}
|
||||
}
|
||||
|
||||
11
packages/benchmarks/src/implementations/emotion/index.js
Normal file
11
packages/benchmarks/src/implementations/emotion/index.js
Normal 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
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import View from '../View/glamor';
|
||||
import View from './View';
|
||||
|
||||
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
|
||||
<View
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
33
packages/benchmarks/src/implementations/glamor/Dot.js
Normal file
33
packages/benchmarks/src/implementations/glamor/Dot.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import { css } from 'glamor';
|
||||
|
||||
const Dot = ({ size, x, y, children, color }) => (
|
||||
<div
|
||||
className={css(styles.root, {
|
||||
borderBottomColor: color,
|
||||
borderRightWidth: `${size / 2}px`,
|
||||
borderBottomWidth: `${size / 2}px`,
|
||||
borderLeftWidth: `${size / 2}px`,
|
||||
marginLeft: `${x}px`,
|
||||
marginTop: `${y}px`
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
const styles = {
|
||||
root: {
|
||||
position: 'absolute',
|
||||
cursor: 'pointer',
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderColor: 'transparent',
|
||||
borderStyle: 'solid',
|
||||
borderTopWidth: 0,
|
||||
transform: 'translate(50%, 50%)'
|
||||
}
|
||||
};
|
||||
|
||||
export default Dot;
|
||||
@@ -0,0 +1,2 @@
|
||||
import View from './View';
|
||||
export default View;
|
||||
11
packages/benchmarks/src/implementations/glamor/index.js
Normal file
11
packages/benchmarks/src/implementations/glamor/index.js
Normal 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
|
||||
};
|
||||
49
packages/benchmarks/src/implementations/inline-styles/Box.js
Normal file
49
packages/benchmarks/src/implementations/inline-styles/Box.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import View from './View';
|
||||
|
||||
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
|
||||
<View
|
||||
{...other}
|
||||
style={{
|
||||
...styles[`color${color}`],
|
||||
...(fixed && styles.fixed),
|
||||
...(layout === 'row' && styles.row),
|
||||
...(outer && styles.outer)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const styles = {
|
||||
outer: {
|
||||
alignSelf: 'flex-start',
|
||||
padding: 4
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
color0: {
|
||||
backgroundColor: '#14171A'
|
||||
},
|
||||
color1: {
|
||||
backgroundColor: '#AAB8C2'
|
||||
},
|
||||
color2: {
|
||||
backgroundColor: '#E6ECF0'
|
||||
},
|
||||
color3: {
|
||||
backgroundColor: '#FFAD1F'
|
||||
},
|
||||
color4: {
|
||||
backgroundColor: '#F45D22'
|
||||
},
|
||||
color5: {
|
||||
backgroundColor: '#E0245E'
|
||||
},
|
||||
fixed: {
|
||||
width: 6,
|
||||
height: 6
|
||||
}
|
||||
};
|
||||
|
||||
export default Box;
|
||||
35
packages/benchmarks/src/implementations/inline-styles/Dot.js
Normal file
35
packages/benchmarks/src/implementations/inline-styles/Dot.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
|
||||
const Dot = ({ size, x, y, children, color }) => (
|
||||
<div
|
||||
style={{
|
||||
...styles.root,
|
||||
...{
|
||||
borderBottomColor: color,
|
||||
borderRightWidth: `${size / 2}px`,
|
||||
borderBottomWidth: `${size / 2}px`,
|
||||
borderLeftWidth: `${size / 2}px`,
|
||||
marginLeft: `${x}px`,
|
||||
marginTop: `${y}px`
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
const styles = {
|
||||
root: {
|
||||
position: 'absolute',
|
||||
cursor: 'pointer',
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderColor: 'transparent',
|
||||
borderStyle: 'solid',
|
||||
borderTopWidth: 0,
|
||||
transform: 'translate(50%, 50%)'
|
||||
}
|
||||
};
|
||||
|
||||
export default Dot;
|
||||
@@ -0,0 +1,2 @@
|
||||
import View from './View';
|
||||
export default View;
|
||||
@@ -0,0 +1,36 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
|
||||
const compose = (s1, s2) => {
|
||||
if (s1 && s2) {
|
||||
return { ...s1, ...s2 };
|
||||
} else {
|
||||
return s1 || s2;
|
||||
}
|
||||
};
|
||||
|
||||
class View extends React.Component {
|
||||
render() {
|
||||
const { style, ...other } = this.props;
|
||||
return <div {...other} style={compose(viewStyle, style)} />;
|
||||
}
|
||||
}
|
||||
|
||||
const viewStyle = {
|
||||
alignItems: 'stretch',
|
||||
borderWidth: 0,
|
||||
borderStyle: 'solid',
|
||||
boxSizing: 'border-box',
|
||||
display: 'flex',
|
||||
flexBasis: 'auto',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 0,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
position: 'relative',
|
||||
// fix flexbox bugs
|
||||
minHeight: 0,
|
||||
minWidth: 0
|
||||
};
|
||||
|
||||
export default View;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user