mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-23 09:54:49 +08:00
Compare commits
402 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
000b92e707 | ||
|
|
d5f5dbccdb | ||
|
|
79456d5920 | ||
|
|
2d1e303a6a | ||
|
|
209ff1fa40 | ||
|
|
34d8160a43 | ||
|
|
ada5651be2 | ||
|
|
9fe9d3c68a | ||
|
|
1e202b6bd5 | ||
|
|
2b77bfd853 | ||
|
|
d0ac55aa4f | ||
|
|
66dd1bd9ef | ||
|
|
6add18c6f0 | ||
|
|
30d7c31b65 | ||
|
|
f7e6b43422 | ||
|
|
4b3f6efb21 | ||
|
|
85e098deec | ||
|
|
c949b0145a | ||
|
|
86b4cf5a51 | ||
|
|
1b7ce4eec6 | ||
|
|
8c8978ff76 | ||
|
|
513b5de881 | ||
|
|
145f80409d | ||
|
|
6d92cc5ec3 | ||
|
|
ec6458c09d | ||
|
|
a84c2ac95e | ||
|
|
75db0e9183 | ||
|
|
4b9a5fd8b4 | ||
|
|
b6be677db9 | ||
|
|
91c9457392 | ||
|
|
006d315a1a | ||
|
|
220eb79357 | ||
|
|
5db9a765b0 | ||
|
|
931d666fcc | ||
|
|
40c433c6df | ||
|
|
9888c2a3c6 | ||
|
|
3fa18becc7 | ||
|
|
aafeb0adad | ||
|
|
89468b7d6e | ||
|
|
f66af5e04d | ||
|
|
2363524fa7 | ||
|
|
5855e55615 | ||
|
|
5033e12d18 | ||
|
|
d6e8530f4d | ||
|
|
ad188a7ad6 | ||
|
|
bfaeae904e | ||
|
|
a54bdeec09 | ||
|
|
8fa7dc63ec | ||
|
|
d841db2337 | ||
|
|
8e7d31cff5 | ||
|
|
0764687a8f | ||
|
|
d31bdf2cf8 | ||
|
|
1f3a77dada | ||
|
|
c3cbd53a8a | ||
|
|
4f5ee15e4b | ||
|
|
9a1cade1f0 | ||
|
|
506dba933c | ||
|
|
c0de9dddf3 | ||
|
|
96c9c06272 | ||
|
|
505e3faee8 | ||
|
|
1f06229289 | ||
|
|
fc743e6eee | ||
|
|
ef97adec6e | ||
|
|
f196335281 | ||
|
|
d29e31d9d6 | ||
|
|
c7c1f29016 | ||
|
|
b56a737d62 | ||
|
|
f062eded40 | ||
|
|
fcc4fbf678 | ||
|
|
a18d30c809 | ||
|
|
b9172ceb8e | ||
|
|
744aaa26d4 | ||
|
|
2b5ddf753e | ||
|
|
b84e3b938a | ||
|
|
9c8407162e | ||
|
|
16b9ec2917 | ||
|
|
d4af1eb981 | ||
|
|
405a3b79e8 | ||
|
|
afd5293172 | ||
|
|
e4831b7bd8 | ||
|
|
baffc9a9e6 | ||
|
|
4151b47005 | ||
|
|
96ec805f59 | ||
|
|
d2df2c296e | ||
|
|
af80b046fa | ||
|
|
1d01af57d0 | ||
|
|
e7613ca4d1 | ||
|
|
0e81c6ef27 | ||
|
|
7fa8940325 | ||
|
|
3e681bed3e | ||
|
|
ecd4b40c71 | ||
|
|
e7cb364b63 | ||
|
|
90bd23f783 | ||
|
|
47a281373a | ||
|
|
6a0302169c | ||
|
|
b195f2b1f5 | ||
|
|
7b81d2a7ec | ||
|
|
a1d8ea776e | ||
|
|
15b960f097 | ||
|
|
001be82178 | ||
|
|
5eeef9e3d2 | ||
|
|
6a310999d0 | ||
|
|
a9ad313a79 | ||
|
|
d0ca7585ab | ||
|
|
3f8624e25f | ||
|
|
8f0c39c2fe | ||
|
|
4f9216853b | ||
|
|
14f7dfd515 | ||
|
|
c336995952 | ||
|
|
2afa5a3cf7 | ||
|
|
9fb818cfd4 | ||
|
|
3153cd8213 | ||
|
|
026a92fd53 | ||
|
|
e0f010da47 | ||
|
|
b299eb6c59 | ||
|
|
da38e87b50 | ||
|
|
a40521f485 | ||
|
|
f62ed22a14 | ||
|
|
bfaca0557b | ||
|
|
c3eedabac4 | ||
|
|
cf43ffd700 | ||
|
|
a8e5d43db5 | ||
|
|
b4e3427fea | ||
|
|
a82cfbe504 | ||
|
|
0eae7bed2e | ||
|
|
b8f54f61f0 | ||
|
|
d5c6b98340 | ||
|
|
16b2fb9bd7 | ||
|
|
48e62fcd64 | ||
|
|
0816c40790 | ||
|
|
2756ab49c3 | ||
|
|
10407f3aa2 | ||
|
|
0ee3310290 | ||
|
|
19b356aaea | ||
|
|
ea744fe780 | ||
|
|
f254c8eae6 | ||
|
|
5fcb36fc21 | ||
|
|
377f23f725 | ||
|
|
fc0b81416a | ||
|
|
cb545b0dac | ||
|
|
f684a3656e | ||
|
|
45975d3a1e | ||
|
|
2d83ffbd6b | ||
|
|
64307c066a | ||
|
|
bb66639519 | ||
|
|
c00360491b | ||
|
|
4bc16fa3eb | ||
|
|
2237777341 | ||
|
|
6a2252891a | ||
|
|
206a236df2 | ||
|
|
ff5a928a50 | ||
|
|
61bf7e76b0 | ||
|
|
e3170623f1 | ||
|
|
392de22992 | ||
|
|
c84163d80e | ||
|
|
50442c4e7c | ||
|
|
eb0e0b8771 | ||
|
|
15d5e57e92 | ||
|
|
def873e9e3 | ||
|
|
9e9b40f155 | ||
|
|
4a45595b7a | ||
|
|
45095fd300 | ||
|
|
004c7ce478 | ||
|
|
96c3f09fac | ||
|
|
edc99e79eb | ||
|
|
e8f2c98786 | ||
|
|
dcdf1468f9 | ||
|
|
ee5e80064f | ||
|
|
3e4d8d6b2f | ||
|
|
d3a8270d55 | ||
|
|
18933724d6 | ||
|
|
e7f84a9228 | ||
|
|
8b1e6f816f | ||
|
|
02b6f3ff3c | ||
|
|
6f52007cc8 | ||
|
|
1e59e53e66 | ||
|
|
f6a65210ca | ||
|
|
2e9071eb0e | ||
|
|
8f25fcc05b | ||
|
|
1e8577fc9e | ||
|
|
c51f567d19 | ||
|
|
bce5957991 | ||
|
|
34d8409d43 | ||
|
|
8442f13e96 | ||
|
|
c6e2b584af | ||
|
|
38affa9bae | ||
|
|
f9ebdb6327 | ||
|
|
83a8758f68 | ||
|
|
e022d166dd | ||
|
|
1a225bc449 | ||
|
|
cf2612663b | ||
|
|
1aec803086 | ||
|
|
2050730b77 | ||
|
|
a67bf0f490 | ||
|
|
4529a4ac0a | ||
|
|
5a04d07a35 | ||
|
|
9427eea293 | ||
|
|
b9803e1e07 | ||
|
|
7a3a9a5c3f | ||
|
|
4c59343fd3 | ||
|
|
ce89b7e3ec | ||
|
|
23fa663a6e | ||
|
|
b96dd668d3 | ||
|
|
a9cacb2ef5 | ||
|
|
c122814591 | ||
|
|
efe18f1b7e | ||
|
|
e1b576e427 | ||
|
|
5d77d6e30f | ||
|
|
8fb9a88ee6 | ||
|
|
a7cda988ef | ||
|
|
b239cfb04d | ||
|
|
8e94d858b2 | ||
|
|
aa22b06359 | ||
|
|
2aa565c7c3 | ||
|
|
7b9b57960d | ||
|
|
eae3ee9dca | ||
|
|
74e1a196b6 | ||
|
|
48da9814e7 | ||
|
|
4d391ef57c | ||
|
|
780df69a80 | ||
|
|
6c229da01f | ||
|
|
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 |
5
.eslintignore
Normal file
5
.eslintignore
Normal file
@@ -0,0 +1,5 @@
|
||||
coverage
|
||||
dist
|
||||
node_modules
|
||||
packages/**/vendor/*
|
||||
packages/examples
|
||||
@@ -25,7 +25,8 @@
|
||||
"document": false,
|
||||
"navigator": false,
|
||||
"window": false,
|
||||
// Flow global types
|
||||
// Flow global types,
|
||||
"$Enum": false,
|
||||
"HTMLInputElement": false,
|
||||
"ReactClass": false,
|
||||
"ReactComponent": false,
|
||||
@@ -56,6 +57,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,
|
||||
|
||||
15
.flowconfig
15
.flowconfig
@@ -1,14 +1,17 @@
|
||||
[version]
|
||||
^0.63.0
|
||||
|
||||
[ignore]
|
||||
.*/__tests__/.*
|
||||
.*/benchmarks/.*
|
||||
.*/docs/.*
|
||||
.*/node_modules/animated/*
|
||||
<PROJECT_ROOT>/.*/__tests__/.*
|
||||
<PROJECT_ROOT>/packages/.*/dist/.*
|
||||
<PROJECT_ROOT>/packages/examples/.*
|
||||
<PROJECT_ROOT>/packages/website/.*
|
||||
.*/node_modules/babel-plugin-transform-react-remove-prop-types/*
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
types
|
||||
<PROJECT_ROOT>/types
|
||||
|
||||
[options]
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
|
||||
86
.github/CONTRIBUTING.md
vendored
86
.github/CONTRIBUTING.md
vendored
@@ -4,7 +4,8 @@
|
||||
|
||||
Before opening an issue, please search the [issue
|
||||
tracker](https://github.com/necolas/react-native-web/issues) to make sure your
|
||||
issue hasn't already been reported.
|
||||
issue hasn't already been reported. Please note that your issue may be closed
|
||||
if it doesn't include the information requested in the issue template.
|
||||
|
||||
## Getting started
|
||||
|
||||
@@ -17,7 +18,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 +26,12 @@ yarn
|
||||
|
||||
## Automated tests
|
||||
|
||||
To run the linter:
|
||||
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
To run flow:
|
||||
|
||||
```
|
||||
@@ -40,62 +47,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 +108,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 +124,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
|
||||
```
|
||||
|
||||
20
.github/ISSUE_TEMPLATE.md
vendored
20
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,20 +0,0 @@
|
||||
**Do you want to request a *feature* or report a *bug*?**
|
||||
|
||||
**What is the current behavior?**
|
||||
|
||||
**If the current behavior is a bug, please provide the steps to reproduce and
|
||||
if a minimal demo of the problem via Glitch or similar (template:
|
||||
https://glitch.com/edit/#!/react-native-web-playground).**
|
||||
|
||||
1.
|
||||
2.
|
||||
|
||||
**What is the expected behavior?**
|
||||
|
||||
**Environment (include versions). Did this work in previous versions?**
|
||||
|
||||
* OS:
|
||||
* Device:
|
||||
* Browser:
|
||||
* React Native for Web (version):
|
||||
* React (version):
|
||||
52
.github/ISSUE_TEMPLATE/bug.md
vendored
Normal file
52
.github/ISSUE_TEMPLATE/bug.md
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: "\U0001F41B Bug report"
|
||||
about: "If something isn't working as expected \U0001F914"
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Thank you for reporting an issue. Please note that an issue must include the
|
||||
information that is marked as REQUIRED below, or it may be closed.
|
||||
-->
|
||||
|
||||
**The problem**
|
||||
<!--
|
||||
REQUIRED: A clear and concise description of the bug or problem.
|
||||
-->
|
||||
|
||||
**How to reproduce**
|
||||
<!--
|
||||
REQUIRED: Create a test case by forking this template https://codesandbox.io/s/6lx6ql1w5r
|
||||
|
||||
Failing to include a reduced test case may result in the issue being closed,
|
||||
and will delay any potential fix. Your application or GitHub project is NOT
|
||||
considered a reduced test case. If the issue only affects certain browsers,
|
||||
providing screenshots is also helpful.
|
||||
-->
|
||||
Simplified test case: <!-- add link here -->
|
||||
|
||||
Steps to reproduce:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Expected behavior**
|
||||
<!--
|
||||
REQUIRED: A clear and concise description of what you expected to happen.
|
||||
|
||||
Please check that the behaviour is not expected React Native behaviour by
|
||||
running your test case on iOS or Android using https://snack.expo.io.
|
||||
-->
|
||||
|
||||
**Environment (include versions). Did this work in previous versions?**
|
||||
|
||||
* React Native for Web (version): TBC
|
||||
* React (version): TBC
|
||||
* Browser: TBC
|
||||
|
||||
<!--
|
||||
OPTIONAL:
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
-->
|
||||
17
.github/ISSUE_TEMPLATE/feature.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/feature.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: "\U0001F680 Feature request"
|
||||
about: If you have a suggestion
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
<!-- A clear and concise description of what the problem is, e.g., I have an issue when [...] -->
|
||||
|
||||
**Describe a solution you'd like**
|
||||
<!-- A clear and concise description of what you want to happen. Add any considered drawbacks. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1 +0,0 @@
|
||||
**Before submitting a pull request,** please make sure you have followed the steps the CONTRIBUTING guide.
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
coverage
|
||||
node_modules
|
||||
dist
|
||||
|
||||
5
.prettierignore
Normal file
5
.prettierignore
Normal file
@@ -0,0 +1,5 @@
|
||||
coverage
|
||||
dist
|
||||
node_modules
|
||||
packages/**/vendor/*
|
||||
packages/examples
|
||||
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.
|
||||
|
||||
311
README.md
311
README.md
@@ -1,127 +1,246 @@
|
||||
# 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.
|
||||
**Compatibility: React Native 0.55**.
|
||||
|
||||
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.
|
||||
"React Native for Web" makes it possible to run [React
|
||||
Native][react-native-url] components and APIs on the web using React DOM. Check
|
||||
out the live demo of the [React Native examples][examples-url] running on the
|
||||
web.
|
||||
|
||||
## Features
|
||||
* **High-quality web interfaces**: 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-quality 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.
|
||||
|
||||
* 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.
|
||||
* **Write once, render anywhere**: 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.
|
||||
|
||||
Who is using React Native in production web apps?
|
||||
[Twitter](https://mobile.twitter.com),
|
||||
[Major League Soccer](https://matchcenter.mlssoccer.com),
|
||||
[Flipkart](https://twitter.com/naqvitalha/status/969577892991549440),
|
||||
[Uber](https://www.youtube.com/watch?v=RV9rxrNIxnY),
|
||||
[The Times](https://github.com/newsuk/times-components), [DataCamp](https://www.datacamp.com/community/tech/porting-practice-to-web-part1).
|
||||
|
||||
Browser support: Chrome, Firefox, Edge, Safari 7+, IE 10+.
|
||||
|
||||
## Quick start
|
||||
|
||||
Install in your existing app using `yarn` or `npm`:
|
||||
The easiest way to get started is to edit this
|
||||
[CodeSandbox](https://codesandbox.io/s/q4qymyp2l6) template (or
|
||||
[Glitch](https://glitch.com/edit/#!/react-native)). 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.
|
||||
For installation and configuration details please read the [getting
|
||||
started](https://github.com/necolas/react-native-web/blob/master/docs/guides/getting-started.md)
|
||||
guide.
|
||||
|
||||
## Documentation
|
||||
|
||||
The [interactive
|
||||
documentation](https://necolas.github.io/react-native-web/storybook/) shows all
|
||||
the supported APIs and Components.
|
||||
Please refer to the [React Native documentation][react-native-url] for the
|
||||
overall API, design details, and 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).
|
||||
|
||||
Guides:
|
||||
Some components and APIs are extended with additional features for the web. And
|
||||
in a few cases, features present for Android or iOS are missing on the web.
|
||||
These differences are documented [on the website][website-url].
|
||||
|
||||
* [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)
|
||||
### Guides
|
||||
|
||||
## Example code
|
||||
These guides provide a detailed look at using React Native to create accessible
|
||||
web experiences. Certain web-specific patterns are documented in the "web
|
||||
recipes" guide.
|
||||
|
||||
* [Getting started](https://github.com/necolas/react-native-web/blob/master/docs/guides/getting-started.md)
|
||||
* [Client-side rendering](https://github.com/necolas/react-native-web/blob/master/docs/guides/client-side-rendering.md)
|
||||
* [Server-side rendering](https://github.com/necolas/react-native-web/blob/master/docs/guides/server-side-rendering.md)
|
||||
* [Style](https://github.com/necolas/react-native-web/blob/master/docs/guides/style.md)
|
||||
* [Accessibility](https://github.com/necolas/react-native-web/blob/master/docs/guides/accessibility.md)
|
||||
* [Internationalization](https://github.com/necolas/react-native-web/blob/master/docs/guides/internationalization.md)
|
||||
* [Direct manipulation](https://github.com/necolas/react-native-web/blob/master/docs/guides/direct-manipulation.md)
|
||||
* [Web recipes](https://github.com/necolas/react-native-web/blob/master/docs/guides/web-recipes.md)
|
||||
* [Multi-platform apps](https://github.com/necolas/react-native-web/blob/master/docs/guides/multi-platform-apps.md)
|
||||
* [Experimental / unstable use](https://github.com/necolas/react-native-web/blob/master/docs/guides/advanced.md)
|
||||
|
||||
## Integrations
|
||||
|
||||
Examples of using React Native for Web with other web tools:
|
||||
|
||||
* [Docz](https://github.com/pedronauck/docz-plugin-react-native)
|
||||
* [Gatsby](https://github.com/slorber/gatsby-plugin-react-native-web)
|
||||
* [Next.js](https://github.com/zeit/next.js/tree/master/examples/with-react-native-web) (and [example recipes](https://gist.github.com/necolas/f9034091723f1b279be86c7429eb0c96))
|
||||
* [Phenomic](https://github.com/phenomic/phenomic/tree/master/examples/react-native-web-app)
|
||||
* [Razzle](https://github.com/jaredpalmer/razzle/tree/master/examples/with-react-native-web)
|
||||
* [Storybook](https://github.com/necolas/react-native-web/tree/master/packages/website/storybook/.storybook)
|
||||
* [Styleguidist](https://github.com/styleguidist/react-styleguidist/tree/master/examples/react-native)
|
||||
|
||||
## Examples
|
||||
|
||||
Check out all the [React Native examples][examples-url] ([source
|
||||
code](https://github.com/necolas/react-native-web/blob/master/packages/examples)).
|
||||
There are more examples [on the website][website-url] ([source
|
||||
code](https://github.com/necolas/react-native-web/blob/master/packages/website)).
|
||||
And here is a simple example to get you started:
|
||||
|
||||
```js
|
||||
import React from 'react'
|
||||
import { AppRegistry, Image, StyleSheet, Text, View } from 'react-native'
|
||||
import React from 'react';
|
||||
import { AppRegistry, 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
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.box}>
|
||||
<Text style={styles.text}>Hello, world!</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// App registration and rendering
|
||||
AppRegistry.registerComponent('MyApp', () => App)
|
||||
AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-root') })
|
||||
const styles = StyleSheet.create({
|
||||
box: { padding: 10 },
|
||||
text: { fontWeight: 'bold' }
|
||||
});
|
||||
|
||||
AppRegistry.registerComponent('App', () => App);
|
||||
AppRegistry.runApplication('App', { rootTag: document.getElementById('react-root') });
|
||||
```
|
||||
|
||||
## Starter kits
|
||||
This example will render the `App` into a container on the page.
|
||||
|
||||
* [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)
|
||||
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.
|
||||
|
||||
## Related projects
|
||||
## Compatibility with React Native
|
||||
|
||||
* [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)
|
||||
React Native v0.55
|
||||
|
||||
### Components
|
||||
|
||||
| Name | Status | Notes |
|
||||
| :----------------------- | :----- | :---- |
|
||||
| ActivityIndicator | ✓ | |
|
||||
| ART | ✓ | |
|
||||
| Button | ✓ | |
|
||||
| CheckBox | ✓ | |
|
||||
| FlatList | ✓ | |
|
||||
| Image | ✓ | Missing multiple sources ([#515](https://github.com/necolas/react-native-web/issues/515)) and HTTP headers ([#1019](https://github.com/necolas/react-native-web/issues/1019)). |
|
||||
| ImageBackground | ✓ | |
|
||||
| KeyboardAvoidingView | (✓) | Mock. No equivalent web APIs. |
|
||||
| ListView | ✓ | |
|
||||
| Modal | ✘ | Not started ([#1020](https://github.com/necolas/react-native-web/issues/1020)). |
|
||||
| Picker | ✓ | |
|
||||
| RefreshControl | ✘ | Not started ([#1027](https://github.com/necolas/react-native-web/issues/1027)). |
|
||||
| SafeAreaView | ✓ | |
|
||||
| ScrollView | ✓ | Missing momentum scroll events ([#1021](https://github.com/necolas/react-native-web/issues/1021)). |
|
||||
| SectionList | ✓ | |
|
||||
| Slider | ✘ | Not started ([#1022](https://github.com/necolas/react-native-web/issues/1022)). |
|
||||
| StatusBar | (✓) | Mock. No equivalent web APIs. |
|
||||
| SwipeableFlatList | ✓ | |
|
||||
| SwipeableListView | ✓ | |
|
||||
| Switch | ✓ | |
|
||||
| Text | ✓ | Missing `onLongPress` ([#1011](https://github.com/necolas/react-native-web/issues/1011)) and `numberOfLines` ([#13](https://github.com/necolas/react-native-web/issues/13)) support. |
|
||||
| TextInput | ✓ | Missing `onContentSizeChange` ([#793](https://github.com/necolas/react-native-web/issues/793)), rich text features ([#1023](https://github.com/necolas/react-native-web/issues/1023)), and auto-expanding behaviour ([#795](https://github.com/necolas/react-native-web/issues/795)). |
|
||||
| Touchable | ✓ | Includes additional support for mouse and keyboard interactions. |
|
||||
| TouchableHighlight | ✓ | |
|
||||
| TouchableNativeFeedback | ✘ | Not started ([#1024](https://github.com/necolas/react-native-web/issues/1024)). |
|
||||
| TouchableOpacity | ✓ | |
|
||||
| TouchableWithoutFeedback | ✓ | |
|
||||
| View | ✓ | |
|
||||
| VirtualizedList | ✓ | |
|
||||
| WebView | ✘ | Not started ([1025](https://github.com/necolas/react-native-web/issues/1025)). |
|
||||
| YellowBox | (✓) | Mock. No YellowBox functionality. |
|
||||
|
||||
### Modules
|
||||
|
||||
| Name | Status | Notes |
|
||||
| :----------------------- | :----- | :---- |
|
||||
| AccessibilityInfo | (✓) | Mock. No equivalent web APIs. |
|
||||
| Alert | ✘ | Not started ([#1026](https://github.com/necolas/react-native-web/issues/1026)). |
|
||||
| Animated | ✓ | Missing `useNativeDriver` support. |
|
||||
| AppRegistry | ✓ | Includes additional support for server rendering with `getApplication`. |
|
||||
| AppState | ✓ | |
|
||||
| AsyncStorage | ✓ | |
|
||||
| BackHandler | (✓) | Mock. No equivalent web APIs. |
|
||||
| CameraRoll | ✘ | No equivalent web APIs. |
|
||||
| Clipboard | ✓ | |
|
||||
| ColorPropType | ✓ | |
|
||||
| DeviceInfo | (✓) | Limited information. |
|
||||
| Dimensions | ✓ | |
|
||||
| Easing | ✓ | |
|
||||
| EdgeInsetsPropType | ✓ | |
|
||||
| Geolocation | ✓ | |
|
||||
| I18nManager | ✓ | Includes additional support for runtime switch to RTL. |
|
||||
| ImageEditor | ✘ | No equivalent web APIs. |
|
||||
| ImageStore | ✘ | No equivalent web APIs. |
|
||||
| InteractionManager | (✓) | |
|
||||
| Keyboard | (✓) | Mock. |
|
||||
| LayoutAnimation | (✓) | Missing translation to web animations. |
|
||||
| Linking | ✓ | |
|
||||
| NativeEventEmitter | ✓ | |
|
||||
| NativeMethodsMixin | ✓ | |
|
||||
| NativeModules | (✓) | Mocked. Missing ability to load native modules. |
|
||||
| NetInfo | ✓ | Missing functionality to detect expensive connections as there are no equivalent web APIs. |
|
||||
| PanResponder | ✓ | |
|
||||
| PixelRatio | ✓ | |
|
||||
| Platform | ✓ | |
|
||||
| PointPropType | ✓ | |
|
||||
| Settings | ✘ | No equivalent web APIs. |
|
||||
| Share | ✓ | Only available over HTTPS. Read about the [Web Share API](https://wicg.github.io/web-share/). |
|
||||
| StyleSheet | ✓ | |
|
||||
| TextPropTypes | ✓ | |
|
||||
| UIManager | ✓ | |
|
||||
| Vibration | ✓ | |
|
||||
| ViewPropTypes | ✓ | |
|
||||
|
||||
## 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
|
||||
[examples-url]: https://necolas.github.io/react-native-web/examples/
|
||||
[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,161 +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, ViewPropTypes } 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';
|
||||
import ViewPropTypes from 'react-native-web/dist/components/View/ViewPropTypes';
|
||||
"
|
||||
`;
|
||||
|
||||
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');
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`10. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
export { View } from 'react-native';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
export { default as View } from 'react-native-web/dist/components/View';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`11. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
export { Switch, Text, View as MyView, ViewPropTypes } from 'react-native';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
export { default as Switch } from 'react-native-web/dist/components/Switch';
|
||||
export { default as Text } from 'react-native-web/dist/components/Text';
|
||||
export { default as MyView } from 'react-native-web/dist/components/View';
|
||||
export { default as ViewPropTypes } from 'react-native-web/dist/components/View/ViewPropTypes';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`12. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
export { createElement, Switch, StyleSheet } from 'react-native';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
export { default as createElement } from 'react-native-web/dist/modules/createElement';
|
||||
export { default as Switch } from 'react-native-web/dist/components/Switch';
|
||||
export { default as StyleSheet } from 'react-native-web/dist/apis/StyleSheet';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`13. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
export { InvalidThing, TouchableOpacity } from 'react-native';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
export { InvalidThing } from 'react-native-web';
|
||||
export { default as TouchableOpacity } from 'react-native-web/dist/components/Touchable/TouchableOpacity';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`14. Rewrite react-native paths for react-native-web 1`] = `
|
||||
"
|
||||
export { default as RNW } from 'react-native';
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
export { default as RNW } from 'react-native-web';
|
||||
"
|
||||
`;
|
||||
@@ -1,46 +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, ViewPropTypes } 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');",
|
||||
|
||||
// export react-native
|
||||
"export { View } from 'react-native';",
|
||||
"export { Switch, Text, View as MyView, ViewPropTypes } from 'react-native';",
|
||||
"export { createElement, Switch, StyleSheet } from 'react-native';",
|
||||
"export { InvalidThing, TouchableOpacity } from 'react-native';",
|
||||
"export { default as RNW } from 'react-native';",
|
||||
{
|
||||
code: "const RNW = require('react-native');",
|
||||
output: "const RNW = require('react-native');",
|
||||
snapshot: false
|
||||
}
|
||||
]
|
||||
});
|
||||
181
babel/index.js
181
babel/index.js
@@ -1,181 +0,0 @@
|
||||
const getDistLocation = importName => {
|
||||
const root = 'react-native-web/dist';
|
||||
|
||||
switch (importName) {
|
||||
// apis
|
||||
case 'Animated':
|
||||
case 'AppRegistry':
|
||||
case 'AppState':
|
||||
case 'AsyncStorage':
|
||||
case 'BackHandler':
|
||||
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': {
|
||||
return `${root}/propTypes/${importName}`;
|
||||
}
|
||||
case 'TextPropTypes': {
|
||||
return `${root}/components/Text/${importName}`;
|
||||
}
|
||||
case 'ViewPropTypes': {
|
||||
return `${root}/components/View/${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);
|
||||
}
|
||||
},
|
||||
ExportNamedDeclaration(path) {
|
||||
const { source, specifiers } = path.node;
|
||||
if (source.value === 'react-native' && specifiers.length) {
|
||||
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')
|
||||
);
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
path.replaceWithMultiple(exports);
|
||||
}
|
||||
},
|
||||
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,48 +0,0 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from 'react';
|
||||
import View from '../View/glamor';
|
||||
|
||||
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
|
||||
<View
|
||||
{...other}
|
||||
style={[
|
||||
styles[`color${color}`],
|
||||
fixed && styles.fixed,
|
||||
layout === 'row' && styles.row,
|
||||
outer && styles.outer
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const styles = {
|
||||
outer: {
|
||||
padding: 4
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
color0: {
|
||||
backgroundColor: '#222'
|
||||
},
|
||||
color1: {
|
||||
backgroundColor: '#666'
|
||||
},
|
||||
color2: {
|
||||
backgroundColor: '#999'
|
||||
},
|
||||
color3: {
|
||||
backgroundColor: 'blue'
|
||||
},
|
||||
color4: {
|
||||
backgroundColor: 'orange'
|
||||
},
|
||||
color5: {
|
||||
backgroundColor: 'red'
|
||||
},
|
||||
fixed: {
|
||||
width: 20,
|
||||
height: 20
|
||||
}
|
||||
};
|
||||
|
||||
export default Box;
|
||||
@@ -1,49 +0,0 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import Radium from 'radium';
|
||||
import React from 'react';
|
||||
import View from '../View/radium';
|
||||
|
||||
const Box = ({ color, fixed = false, layout = 'column', outer = false, ...other }) => (
|
||||
<View
|
||||
{...other}
|
||||
style={[
|
||||
styles[`color${color}`],
|
||||
fixed && styles.fixed,
|
||||
layout === 'row' && styles.row,
|
||||
outer && styles.outer
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const styles = {
|
||||
outer: {
|
||||
padding: 4
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
color0: {
|
||||
backgroundColor: '#222'
|
||||
},
|
||||
color1: {
|
||||
backgroundColor: '#666'
|
||||
},
|
||||
color2: {
|
||||
backgroundColor: '#999'
|
||||
},
|
||||
color3: {
|
||||
backgroundColor: 'blue'
|
||||
},
|
||||
color4: {
|
||||
backgroundColor: 'orange'
|
||||
},
|
||||
color5: {
|
||||
backgroundColor: 'red'
|
||||
},
|
||||
fixed: {
|
||||
width: 20,
|
||||
height: 20
|
||||
}
|
||||
};
|
||||
|
||||
export default Radium(Box);
|
||||
@@ -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,31 +0,0 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import Radium from 'radium';
|
||||
import React from 'react';
|
||||
|
||||
class View extends React.Component {
|
||||
render() {
|
||||
const { style, ...other } = this.props;
|
||||
return <div {...other} style={[styles.root, style]} />;
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
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 Radium(View);
|
||||
@@ -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
@@ -39,29 +39,39 @@ using `aria-label`.
|
||||
|
||||
In some cases, we also want to alert the end user of the type of selected
|
||||
component (i.e., that it is a “button”). To provide more context to screen
|
||||
readers, you should specify the `accessibilityRole` property. (Note that React
|
||||
Native for Web also provides a compatibility mapping of equivalent
|
||||
readers on the web, you should use the `accessibilityRole` property. (Note that
|
||||
React Native for Web also provides a compatibility mapping of equivalent
|
||||
`accessibilityTraits` and `accessibilityComponentType` values to
|
||||
`accessibilityRole`).
|
||||
|
||||
The `accessibilityRole` prop is used to infer an [analogous HTML
|
||||
element][html-aria-url] and ARIA `role`, where possible. In most cases, both
|
||||
the element and ARIA `role` are rendered. While this may contradict some ARIA
|
||||
recommendations, it also helps avoid certain HTML5 conformance errors and
|
||||
accessibility anti-patterns (e.g., giving a `heading` role to a `button`
|
||||
element) and browser bugs.
|
||||
recommendations, it also helps avoid certain browser bugs, HTML5 conformance
|
||||
errors, and accessibility anti-patterns (e.g., giving a `heading` role to a
|
||||
`button` element).
|
||||
|
||||
For example:
|
||||
Straight-forward examples:
|
||||
|
||||
* `<View accessibilityRole='article' />` => `<article role='article' />`.
|
||||
* `<View accessibilityRole='banner' />` => `<header role='banner' />`.
|
||||
* `<View accessibilityRole='button' />` => `<div role='button' tabIndex='0' />`.
|
||||
* `<Text accessibilityRole='label' />` => `<label />`.
|
||||
* `<Text accessibilityRole='link' href='/' />` => `<a role='link' href='/' />`.
|
||||
* `<View accessibilityRole='main' />` => `<main role='main' />`.
|
||||
* `<View accessibilityRole="article" />` => `<article role="article" />`.
|
||||
* `<View accessibilityRole="banner" />` => `<header role="banner" />`.
|
||||
* `<Text accessibilityRole="label" />` => `<label />`.
|
||||
* `<Text accessibilityRole="link" />` => `<a role="link" />`.
|
||||
* `<View accessibilityRole="main" />` => `<main role="main" />`.
|
||||
|
||||
In the example below, the `TouchableHighlight` is announced by screen
|
||||
readers as a button.
|
||||
The `heading` role can be combined with `aria-level`:
|
||||
|
||||
* `<Text accessibilityRole="heading" />` => `<h1 />`.
|
||||
* `<Text accessibilityRole="heading" aria-level="3" />` => `<h3 />`.
|
||||
|
||||
The `button` role renders an accessible button but is not implemented using the
|
||||
native `button` element due to some browsers limiting the use of flexbox layout on
|
||||
its children.
|
||||
|
||||
* `<View accessibilityRole="button" />` => `<div role="button" tabIndex="0" />`.
|
||||
|
||||
In the example below, the `TouchableHighlight` is announced by screen readers
|
||||
as a button and it responds to touch, mouse, and keyboard interactions.
|
||||
|
||||
```js
|
||||
<TouchableHighlight accessibilityRole="button" onPress={this._handlePress}>
|
||||
@@ -71,16 +81,13 @@ readers as a button.
|
||||
</TouchableHighlight>
|
||||
```
|
||||
|
||||
Note: The `button` role is not implemented using the native `button` element
|
||||
due to browsers limiting the use of flexbox layout on its children.
|
||||
|
||||
Note: Avoid changing `accessibilityRole` values over time or after user
|
||||
actions. Generally, accessibility APIs do not provide a means of notifying
|
||||
assistive technologies of a `role` value change.
|
||||
|
||||
### accessibilityLiveRegion
|
||||
|
||||
When components dynamically change we may need to inform the user. The
|
||||
When components dynamically change we may need to inform the user. The
|
||||
`accessibilityLiveRegion` property serves this purpose and can be set to
|
||||
`none`, `polite` and `assertive`. On web, `accessibilityLiveRegion` is
|
||||
implemented using `aria-live`.
|
||||
@@ -114,6 +121,18 @@ value of `no` will remove a focusable element from the tab flow, and a value of
|
||||
`no-hide-descendants` will also hide the entire subtree from assistive
|
||||
technologies (this is implemented using `aria-hidden`).
|
||||
|
||||
### Spatial navigation
|
||||
|
||||
Focus-based web UIs, e.g., for TVs and Game Consoles can implement [TV remote
|
||||
control navigation](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox_OS_for_TV/TV_remote_control_navigation)
|
||||
outside of React using existing directional-focus libraries. Every DOM element
|
||||
that React Native considers focusable can be matched by the attribute
|
||||
`data-focusable="true"`.
|
||||
|
||||
```js
|
||||
const focusableElements = document.querySelectorAll('[data-focusable="true"]');
|
||||
```
|
||||
|
||||
### Other
|
||||
|
||||
Other ARIA properties can be set via [direct
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Advanced use
|
||||
# Experimental / unstable uses
|
||||
|
||||
## Use with existing React DOM components
|
||||
|
||||
@@ -101,9 +101,9 @@ const StyledView = styled(View, styles.container);
|
||||
## Use with react-sketchapp
|
||||
|
||||
Use with [react-sketchapp](http://airbnb.io/react-sketchapp/) requires that you
|
||||
alias `react-native` to `react-sketchapp`. This will allow you to render your
|
||||
existing React Native components in Sketch. Sketch-specific components like
|
||||
`Artboard` should be imported from `react-sketchapp`.
|
||||
alias `react-native` to `react-sketchapp`. This will allow you to render simple
|
||||
React Native components in Sketch. Sketch-specific components like `Artboard`
|
||||
should be imported from `react-sketchapp`.
|
||||
|
||||
If you're using `skpm`, you can rely on an [undocumented
|
||||
feature](https://github.com/sketch-pm/skpm/blob/master/lib/utils/webpackConfig.js)
|
||||
|
||||
42
docs/guides/client-side-rendering.md
Normal file
42
docs/guides/client-side-rendering.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Client-side rendering
|
||||
|
||||
Render apps using `AppRegistry`:
|
||||
|
||||
```js
|
||||
// index.web.js
|
||||
|
||||
import App from './src/App';
|
||||
import React from 'react';
|
||||
import { AppRegistry } from 'react-native';
|
||||
|
||||
// register the app
|
||||
AppRegistry.registerComponent('App', () => App);
|
||||
|
||||
AppRegistry.runApplication('App', {
|
||||
initialProps: {},
|
||||
rootTag: document.getElementById('react-app')
|
||||
});
|
||||
```
|
||||
|
||||
Or render individual components:
|
||||
|
||||
```js
|
||||
import AppHeader from './src/AppHeader';
|
||||
import React from 'react';
|
||||
import { render } from 'react-native';
|
||||
|
||||
render(<AppHeader />, document.getElementById('react-app-header'))
|
||||
```
|
||||
|
||||
(Components will also be rendered within a tree produced by calling
|
||||
`ReactDOM.render` (i.e., an existing web app), but
|
||||
otherwise it is not recommended.)
|
||||
|
||||
You might need to adjust the styles of the HTML document's root elements for
|
||||
your app to fill the viewport.
|
||||
|
||||
```html
|
||||
<html style="height:100%">
|
||||
<body style="height:100%">
|
||||
<div id="react-root" style="display:flex;height:100%"></div>
|
||||
```
|
||||
@@ -1,13 +1,13 @@
|
||||
# Direct manipulation
|
||||
|
||||
React Native for Web provides several methods to directly access the underlying
|
||||
DOM node. This can be useful when you need to make changes directly to a
|
||||
component without using state/props to trigger a re-render of the entire
|
||||
subtree, or when you want to focus a view or measure its on-screen dimensions.
|
||||
React Native provides several methods to directly access the underlying host
|
||||
node. This can be useful when you need to make changes directly to a component
|
||||
without using state/props to trigger a re-render of the entire subtree, or when
|
||||
you want to focus a view or measure its on-screen dimensions.
|
||||
|
||||
The methods described are available on most of the default components provided
|
||||
by React Native for Web. Note, however, that they are *not* available on the
|
||||
composite components that you define in your own app.
|
||||
by React Native for Web. Note, however, that they are *not* available on the composite
|
||||
components that you define in your own app.
|
||||
|
||||
## Instance methods
|
||||
|
||||
@@ -35,11 +35,17 @@ Like `measure`, but measures the view relative to another view, specified as
|
||||
`relativeToNativeNode`. This means that the returned `x`, `y` are relative to
|
||||
the origin `x`, `y` of the ancestor view.
|
||||
|
||||
As always, to obtain a native node handle for a component, you can use
|
||||
`findNodeHandle(component)`.
|
||||
|
||||
**measureInWindow**(callback: (x, y, width, height) => void)
|
||||
|
||||
Determines the location of the given view in the window and returns the values
|
||||
via an async callback.
|
||||
|
||||
**setNativeProps**(nativeProps: Object)
|
||||
|
||||
This function sends props straight to the underlying DOM node. See the [direct
|
||||
manipulation](../guides/direct-manipulation.md) guide for cases where
|
||||
`setNativeProps` should be used.
|
||||
This function sends props straight to the underlying DOM node.
|
||||
|
||||
## About `setNativeProps`
|
||||
|
||||
|
||||
@@ -1,211 +1,109 @@
|
||||
# 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.)
|
||||
This guide will help you render components and applications with React Native
|
||||
for Web.
|
||||
|
||||
It is recommended that your application provide a `Promise` and `Array.from`
|
||||
polyfill.
|
||||
Your application may need to polyfill `Promise`, `Object.assign`, `Array.from`,
|
||||
and [`ResizeObserver`](https://github.com/que-etc/resize-observer-polyfill) as
|
||||
necessary for your desired browser support.
|
||||
|
||||
## Web packager
|
||||
If you're not familiar with setting up a new React web project, please follow
|
||||
the recommendations in the [React documentation](https://reactjs.org/).
|
||||
|
||||
[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.
|
||||
## Install
|
||||
|
||||
Create a `web/webpack.config.js` file:
|
||||
```
|
||||
yarn add react react-dom react-native-web
|
||||
```
|
||||
|
||||
And if you need to use `ART`:
|
||||
|
||||
```
|
||||
yarn add react-art
|
||||
```
|
||||
|
||||
## Starter kits
|
||||
|
||||
[create-react-app](https://github.com/facebookincubator/create-react-app)
|
||||
includes built-in support for aliasing `react-native-web` to `react-native`.
|
||||
|
||||
```
|
||||
create-react-app my-app
|
||||
```
|
||||
|
||||
## Configuring a module bundler
|
||||
|
||||
If you have a custom setup, you may choose to configure your module bundler to
|
||||
alias the package to `react-native`.
|
||||
|
||||
For example, modify your [webpack](https://github.com/webpack/webpack)
|
||||
configuration as follows:
|
||||
|
||||
```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]'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// webpack.config.js
|
||||
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' ]
|
||||
alias: {
|
||||
'react-native$': 'react-native-web'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To run in development:
|
||||
Now you can create your components and applications with the React Native API.
|
||||
|
||||
## Configuring Babel
|
||||
|
||||
If you need to do the aliasing with Babel you can use
|
||||
[babel-plugin-module-resolver](https://www.npmjs.com/package/babel-plugin-module-resolver)
|
||||
|
||||
```
|
||||
./node_modules/.bin/webpack-dev-server -d --config web/webpack.config.js --inline --hot --colors
|
||||
{
|
||||
"plugins": [
|
||||
["module-resolver", {
|
||||
"alias": {
|
||||
"^react-native$": "react-native-web"
|
||||
}
|
||||
}]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
To build for production:
|
||||
## Configuring Jest
|
||||
|
||||
[Jest](https://facebook.github.io/jest/) can be configured using the provided
|
||||
preset. This will map `react-native` to `react-native-web` and provide
|
||||
appropriate mocks:
|
||||
|
||||
```
|
||||
./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"
|
||||
}
|
||||
{
|
||||
"preset": "react-native-web"
|
||||
}
|
||||
```
|
||||
|
||||
Please refer to the Jest documentation for more information.
|
||||
|
||||
## Configuring Flow
|
||||
|
||||
[Flow](https://flow.org) can be configured to understand the aliased module:
|
||||
|
||||
```
|
||||
[options]
|
||||
module.name_mapper='^react-native$' -> 'react-native-web'
|
||||
```
|
||||
|
||||
You may also need to include a custom libdef
|
||||
([example](https://gist.github.com/paularmstrong/f60b40d16fc83e1e8e532d483336f9bb))
|
||||
in your config.
|
||||
|
||||
## Other notes
|
||||
|
||||
### Safari flexbox performance
|
||||
|
||||
Safari prior to version 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.
|
||||
|
||||
@@ -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.
|
||||
154
docs/guides/multi-platform-apps.md
Normal file
154
docs/guides/multi-platform-apps.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# Multi-platform applications
|
||||
|
||||
## 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 below 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.
|
||||
|
||||
## Web packaging for existing React Native apps
|
||||
|
||||
What follows is merely an _example_ of one basic way to package a web app using
|
||||
[Webpack](https://webpack.js.org) and [Babel](https://babeljs.io/). (You can
|
||||
also use the React Native bundler, [Metro](https://github.com/facebook/metro), to
|
||||
build web apps.)
|
||||
|
||||
Packaging web apps is subtly different to packaging React Native apps and is
|
||||
complicated by the need to tree-shake and code-split non-trivial apps.
|
||||
|
||||
Install webpack-related dependencies, for example:
|
||||
|
||||
```
|
||||
yarn add --dev babel-loader url-loader webpack webpack-cli webpack-dev-server
|
||||
```
|
||||
|
||||
React Native's Babel preset rewrites ES modules to CommonJS modules, preventing
|
||||
bundlers from automatically performing "tree-shaking" to remove unused modules
|
||||
from your web app build. To help with this, you can install the following Babel
|
||||
plugin:
|
||||
|
||||
```
|
||||
yarn add --dev babel-plugin-react-native-web
|
||||
```
|
||||
|
||||
Create a `web/webpack.config.js` file:
|
||||
|
||||
```js
|
||||
// web/webpack.config.js
|
||||
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const appDirectory = path.resolve(__dirname, '../');
|
||||
|
||||
// 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(appDirectory, 'index.web.js'),
|
||||
path.resolve(appDirectory, 'src'),
|
||||
path.resolve(appDirectory, 'node_modules/react-native-uncompiled')
|
||||
],
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
cacheDirectory: true,
|
||||
// The 'react-native' preset is recommended to match React Native's packager
|
||||
presets: ['react-native'],
|
||||
// Re-write paths to import only the modules needed by the app
|
||||
plugins: ['react-native-web']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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 = {
|
||||
entry: [
|
||||
// load any web API polyfills
|
||||
// path.resolve(appDirectory, 'polyfills-web.js'),
|
||||
// your web-specific entry file
|
||||
path.resolve(appDirectory, 'index.web.js')
|
||||
],
|
||||
|
||||
// configures where the build ends up
|
||||
output: {
|
||||
filename: 'bundle.web.js',
|
||||
path: path.resolve(appDirectory, 'dist')
|
||||
},
|
||||
|
||||
// ...the rest of your config
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
babelLoaderConfiguration,
|
||||
imageLoaderConfiguration
|
||||
]
|
||||
},
|
||||
|
||||
resolve: {
|
||||
// This will only alias the exact import "react-native"
|
||||
alias: {
|
||||
'react-native$': 'react-native-web'
|
||||
},
|
||||
// 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 from the root of your application:
|
||||
|
||||
```
|
||||
./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.
|
||||
33
docs/guides/server-side-rendering.md
Normal file
33
docs/guides/server-side-rendering.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Server-side rendering
|
||||
|
||||
Server-side rendering to HTML is supported using `AppRegistry`:
|
||||
|
||||
```js
|
||||
import App from './src/App';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
import { AppRegistry } from 'react-native-web';
|
||||
|
||||
// register the app
|
||||
AppRegistry.registerComponent('App', () => App);
|
||||
|
||||
// prerender the app
|
||||
const { element, getStyleElement } = AppRegistry.getApplication('App', { initialProps });
|
||||
// first the element
|
||||
const html = ReactDOMServer.renderToString(element);
|
||||
// then the styles (optionally include a nonce if your CSP policy requires it)
|
||||
const css = ReactDOMServer.renderToStaticMarkup(getStyleElement({ nonce }));
|
||||
|
||||
// example HTML document string
|
||||
const document = `
|
||||
<!DOCTYPE html>
|
||||
<html style="height:100%">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
${css}
|
||||
<body style="height:100%; overflow-y:hidden">
|
||||
<div id="root" style="display:flex; height: 100%">
|
||||
${html}
|
||||
</div>
|
||||
<script nonce="${nonce}" src="${bundlePath}"></script>
|
||||
`
|
||||
```
|
||||
@@ -5,22 +5,22 @@ application. React Native for Web implements the React Native style API in a
|
||||
way that avoids *all* the [problems with CSS at
|
||||
scale](https://speakerdeck.com/vjeux/react-css-in-js):
|
||||
|
||||
1. No local variables
|
||||
2. Implicit dependencies
|
||||
3. No dead code elimination
|
||||
4. No code minification
|
||||
5. No sharing of constants
|
||||
6. Non-deterministic resolution
|
||||
7. No isolation
|
||||
1. No local variables.
|
||||
2. Implicit dependencies.
|
||||
3. No dead code elimination.
|
||||
4. No code minification.
|
||||
5. No sharing of constants.
|
||||
6. Non-deterministic resolution.
|
||||
7. No isolation.
|
||||
|
||||
At the same time, it has several benefits:
|
||||
|
||||
1. Simple API and expressive subset of CSS
|
||||
2. Generates CSS; the minimum required
|
||||
3. Good runtime performance
|
||||
4. Support for static and dynamic styles
|
||||
5. Support for RTL layouts
|
||||
6. Easy pre-rendering of critical CSS
|
||||
1. Simple API and expressive subset of CSS.
|
||||
2. Generates CSS; the minimum required.
|
||||
3. Good runtime performance.
|
||||
4. Support for static and dynamic styles.
|
||||
5. Support for RTL layouts.
|
||||
6. Easy pre-rendering of critical CSS.
|
||||
|
||||
## Defining styles
|
||||
|
||||
@@ -43,6 +43,8 @@ const styles = StyleSheet.create({
|
||||
|
||||
See the `style` documentation of individual components for supported properties.
|
||||
|
||||
NOTE: React Native does not yet support `rem` or `em` units.
|
||||
|
||||
## Using styles
|
||||
|
||||
All the React Native components accept a `style` property. The value can be a
|
||||
@@ -126,7 +128,7 @@ the CSS and takes precedence over the previous rules, resulting in a margin of
|
||||
<div class="marginTop marginBottom margin"></div>
|
||||
```
|
||||
|
||||
But in React Native the most *specific* style property takes precedence,
|
||||
But in React Native the most *precise* style property takes precedence,
|
||||
resulting in margins of `10, 0, 20, 0`.
|
||||
|
||||
```js
|
||||
@@ -192,7 +194,7 @@ inline styles.
|
||||
|
||||
All this allows React Native for Web to support the rich functionality of React
|
||||
Native styles (including RTL layouts and `setNativeProps`) while providing one
|
||||
of the [fastest](https://github.com/necolas/react-native-web/blob/master/benchmarks/README.md),
|
||||
of the [fastest](https://github.com/necolas/react-native-web/blob/master/packages/benchmarks/README.md),
|
||||
safest, and most efficient styles-in-JavaScript solutions.
|
||||
|
||||
## FAQs
|
||||
@@ -204,14 +206,11 @@ it does not concern itself with _where_ or _when_ those styles are applied to
|
||||
elements.
|
||||
|
||||
Media Queries may not be most appropriate for component-based designs. React
|
||||
Native provides the `Dimensions` API and `onLayout` props. If you do need Media
|
||||
Queries, using the `matchMedia` DOM API has the benefit of allowing you to swap
|
||||
out entire components, not just styles. There are also many React libraries
|
||||
wrapping JavaScript Media Query API's, e.g.,
|
||||
[react-media](https://github.com/reacttraining/react-media),
|
||||
[react-media-queries](https://github.com/bloodyowl/react-media-queries),
|
||||
[media-query-fascade](https://github.com/tanem/media-query-facade), or
|
||||
[react-responsive](https://github.com/contra/react-responsive).
|
||||
Native provides the `Dimensions` API and the component-scoped `onLayout` prop.
|
||||
|
||||
If you do choose to use Media Queries, using them in JavaScript via the
|
||||
`matchMedia` DOM API has the benefit of allowing you to swap out entire
|
||||
components, not just styles.
|
||||
|
||||
### What about pseudo-classes and pseudo-elements?
|
||||
|
||||
|
||||
51
docs/guides/web-recipes.md
Normal file
51
docs/guides/web-recipes.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Web recipes
|
||||
|
||||
Examples of how to implement web patterns with React Native.
|
||||
|
||||
#### `html` and `body` styles
|
||||
|
||||
The `html` and `body` elements require certain styles to support full-screen
|
||||
React Native apps, and disable "features" like pull-to-refresh in mobile
|
||||
browsers. Using the `body` as the root scroll view is [not reliably
|
||||
supported](https://github.com/necolas/react-native-web/issues/829).
|
||||
|
||||
[Example code](https://codesandbox.io/s/52x1871vjl).
|
||||
|
||||
#### Hover styles
|
||||
|
||||
Relying on the web's native hover styles can result in several unwanted UX
|
||||
consequences. Hover styles might be displayed during touch interactions and can
|
||||
remain visually "stuck". Furthermore, there's no way to delay or persist hover
|
||||
for accessibility purposes. This recipe shows how to apply hover styles that
|
||||
integrate with the Responder event system (e.g., the `Touchable*` press styles)
|
||||
and display styles *only* when the mouse is active. It can also be used as the
|
||||
basis for programmatic hover delays and rendering of components (e.g., hover
|
||||
cards).
|
||||
|
||||
[Example code](https://codesandbox.io/s/o9q8vy70l5)
|
||||
|
||||
#### Link styles
|
||||
|
||||
Cross-platform link components are straight-forward to create and can be
|
||||
combined with the hover recipe.
|
||||
|
||||
[Example code](https://codesandbox.io/s/53r88k5opx)
|
||||
|
||||
<!--
|
||||
#### Typography
|
||||
|
||||
Rather than relying on inhereted global typography styles, React Native allows
|
||||
you to define components that encapsulate a typographic system. This has the
|
||||
added benefit of enabling content-specific typography adjustments, e.g.,
|
||||
different font sizes or weights for different languages.
|
||||
|
||||
[Example code]()
|
||||
|
||||
#### Responsive font size
|
||||
|
||||
[Example code]()
|
||||
|
||||
#### SVG icons
|
||||
|
||||
[Example code]()
|
||||
-->
|
||||
@@ -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,36 +0,0 @@
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = (storybookBaseConfig, configType) => {
|
||||
const DEV = configType === 'DEVELOPMENT';
|
||||
|
||||
storybookBaseConfig.module.rules.push({
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: { cacheDirectory: true }
|
||||
}
|
||||
});
|
||||
|
||||
storybookBaseConfig.module.rules.push({
|
||||
test: /\.(gif|jpe?g|png|svg)$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
options: { name: '[name].[ext]' }
|
||||
}
|
||||
});
|
||||
|
||||
storybookBaseConfig.plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
'process.env.__REACT_NATIVE_DEBUG_ENABLED__': DEV
|
||||
})
|
||||
);
|
||||
|
||||
storybookBaseConfig.resolve.alias = {
|
||||
'react-native': path.join(__dirname, '../../../src/module')
|
||||
};
|
||||
|
||||
return storybookBaseConfig;
|
||||
};
|
||||
@@ -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;
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import sources from '../sources';
|
||||
import React from 'react';
|
||||
import { Image, StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
const ImageSourceExample = () => (
|
||||
<View style={styles.row}>
|
||||
<View style={styles.column}>
|
||||
<Text style={styles.text}>Static image</Text>
|
||||
<Image source={sources.static} style={styles.image} />
|
||||
</View>
|
||||
<View style={styles.column}>
|
||||
<Text style={styles.text}>Animated GIF</Text>
|
||||
<Image source={sources.animatedGif} style={styles.image} />
|
||||
</View>
|
||||
<View style={styles.column}>
|
||||
<Text style={styles.text}>Data PNG</Text>
|
||||
<Image source={sources.dataPng} style={styles.image} />
|
||||
</View>
|
||||
<View style={styles.column}>
|
||||
<Text style={styles.text}>Data SVG</Text>
|
||||
<Image source={sources.dataSvg} style={styles.image} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap'
|
||||
},
|
||||
column: {
|
||||
marginRight: '1rem'
|
||||
},
|
||||
text: {
|
||||
marginBottom: '0.5rem'
|
||||
},
|
||||
image: {
|
||||
flex: 1,
|
||||
height: 50,
|
||||
resizeMode: 'contain'
|
||||
}
|
||||
});
|
||||
|
||||
export default ImageSourceExample;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
4675
docs/yarn.lock
4675
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
import Enzyme from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
178
package.json
178
package.json
@@ -1,126 +1,84 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.1.7",
|
||||
"description": "React Native for Web",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/module.js",
|
||||
"files": [
|
||||
"babel",
|
||||
"dist",
|
||||
"src",
|
||||
"!**/__tests__"
|
||||
],
|
||||
"private": true,
|
||||
"version": "0.9.13",
|
||||
"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": "npm-run-all clean -p \"compile:* -- {@}\" --",
|
||||
"compile:commonjs": "cd packages/react-native-web && BABEL_ENV=commonjs babel src --out-dir dist/cjs --ignore \"**/__tests__\"",
|
||||
"compile:es": "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",
|
||||
"examples": "cd packages/examples && yarn build",
|
||||
"examples:release": "cd packages/examples && yarn release",
|
||||
"website": "cd packages/website && yarn start",
|
||||
"website:release": "cd packages/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": "prettier --write \"**/*.js\"",
|
||||
"jest": "BABEL_ENV=commonjs jest --config ./scripts/jest/config.js",
|
||||
"lint": "yarn lint:check --fix",
|
||||
"lint:check": "eslint packages scripts",
|
||||
"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 && yarn compile:commonjs",
|
||||
"release": "node ./scripts/release/publish.js",
|
||||
"postrelease": "yarn benchmarks:release && yarn examples:release && yarn website: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.2.3",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-tester": "^4.0.0",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.9",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"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": "^3.0.1",
|
||||
"eslint": "^4.6.1",
|
||||
"eslint-config-prettier": "^2.6.0",
|
||||
"eslint-plugin-promise": "^3.5.0",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"file-loader": "^1.1.4",
|
||||
"flow-bin": "^0.49.1",
|
||||
"jest": "^21.2.1",
|
||||
"lint-staged": "^4.1.3",
|
||||
"prettier": "^1.7.3",
|
||||
"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.6.0",
|
||||
"enzyme-adapter-react-16": "^1.5.0",
|
||||
"enzyme-to-json": "^3.3.3",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-promise": "^3.7.0",
|
||||
"eslint-plugin-react": "^7.7.0",
|
||||
"flow-bin": "^0.63.1",
|
||||
"glob": "^7.1.2",
|
||||
"husky": "^0.14.3",
|
||||
"inline-style-prefixer": "^5.0.3",
|
||||
"jest": "^22.4.3",
|
||||
"jest-canvas-mock": "^1.0.2",
|
||||
"lint-staged": "^7.1.0",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"prettier": "^1.12.1",
|
||||
"react": "^16.7.0",
|
||||
"react-art": "^16.7.0",
|
||||
"react-dom": "^16.7.0",
|
||||
"react-test-renderer": "^16.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "16.x.x",
|
||||
"react-dom": "16.x.x"
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"lint-staged": {
|
||||
"packages/react-native-web/src/index.js": [
|
||||
"node ./scripts/babel/createModuleMap.js",
|
||||
"prettier --write ./packages/babel-plugin-react-native-web/src/moduleMap.js",
|
||||
"git add ./packages/babel-plugin-react-native-web/src/moduleMap.js"
|
||||
],
|
||||
"**/*.js": [
|
||||
"prettier --write",
|
||||
"git update-index --again",
|
||||
"eslint"
|
||||
]
|
||||
},
|
||||
"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"
|
||||
}
|
||||
|
||||
52
packages/babel-plugin-react-native-web/README.md
Normal file
52
packages/babel-plugin-react-native-web/README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# 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", { commonjs: true }]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You should configure the plugin to match the module format used by your
|
||||
bundler. Most modern bundlers will use a package's ES modules by default (i.e.,
|
||||
if `package.json` has a `module` field). But if you need the plugin to rewrite
|
||||
import paths to point to CommonJS modules, you must set the `commonjs` option
|
||||
to `true`.
|
||||
|
||||
## 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.9.13",
|
||||
"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,145 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Rewrite react-native to react-native-web export from "react-native": 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[`Rewrite react-native to react-native-web export from "react-native-web": 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[`Rewrite react-native to react-native-web import from "native-native": 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[`Rewrite react-native to react-native-web import from "native-native": import from "native-native" 2`] = `
|
||||
"
|
||||
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/cjs/index';
|
||||
import View from 'react-native-web/dist/cjs/exports/View';
|
||||
import { Invalid } from 'react-native-web/dist/cjs/index';
|
||||
import MyView from 'react-native-web/dist/cjs/exports/View';
|
||||
import ViewPropTypes from 'react-native-web/dist/cjs/exports/ViewPropTypes';
|
||||
import * as ReactNativeModules from 'react-native-web/dist/cjs/index';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Rewrite react-native to react-native-web import from "react-native-web": 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[`Rewrite react-native to react-native-web require "react-native": 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[`Rewrite react-native to react-native-web require "react-native": require "react-native" 2`] = `
|
||||
"
|
||||
const ReactNative = require('react-native');
|
||||
const { View } = require('react-native');
|
||||
const { StyleSheet, TouchableOpacity } = require('react-native');
|
||||
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
const ReactNative = require('react-native-web/dist/cjs/index').default;
|
||||
|
||||
const View = require('react-native-web/dist/cjs/exports/View').default;
|
||||
|
||||
const StyleSheet = require('react-native-web/dist/cjs/exports/StyleSheet').default;
|
||||
|
||||
const TouchableOpacity = require('react-native-web/dist/cjs/exports/TouchableOpacity').default;
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`Rewrite react-native to react-native-web require "react-native-web": 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,69 @@
|
||||
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 "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,
|
||||
pluginOptions: { commonjs: 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"',
|
||||
code: `const ReactNative = require('react-native');
|
||||
const { View } = require('react-native');
|
||||
const { StyleSheet, TouchableOpacity } = require('react-native');`,
|
||||
snapshot: true,
|
||||
pluginOptions: { commonjs: 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
|
||||
});
|
||||
136
packages/babel-plugin-react-native-web/src/index.js
Normal file
136
packages/babel-plugin-react-native-web/src/index.js
Normal file
@@ -0,0 +1,136 @@
|
||||
const moduleMap = require('./moduleMap');
|
||||
|
||||
const isCommonJS = opts => opts.commonjs === true;
|
||||
|
||||
const getDistLocation = (importName, opts) => {
|
||||
const format = isCommonJS(opts) ? 'cjs/' : '';
|
||||
if (importName === 'index') {
|
||||
return `react-native-web/dist/${format}index`;
|
||||
} else if (importName && moduleMap[importName]) {
|
||||
return `react-native-web/dist/${format}exports/${importName}`;
|
||||
}
|
||||
};
|
||||
|
||||
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, state.opts);
|
||||
|
||||
if (distLocation) {
|
||||
return t.importDeclaration(
|
||||
[t.importDefaultSpecifier(t.identifier(specifier.local.name))],
|
||||
t.stringLiteral(distLocation)
|
||||
);
|
||||
}
|
||||
}
|
||||
return t.importDeclaration(
|
||||
[specifier],
|
||||
t.stringLiteral(getDistLocation('index', state.opts))
|
||||
);
|
||||
})
|
||||
.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, state.opts);
|
||||
|
||||
if (distLocation) {
|
||||
return t.exportNamedDeclaration(
|
||||
null,
|
||||
[t.exportSpecifier(t.identifier('default'), t.identifier(exportName))],
|
||||
t.stringLiteral(distLocation)
|
||||
);
|
||||
}
|
||||
}
|
||||
return t.exportNamedDeclaration(
|
||||
null,
|
||||
[specifier],
|
||||
t.stringLiteral(getDistLocation('index', state.opts))
|
||||
);
|
||||
})
|
||||
.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, state.opts);
|
||||
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(getDistLocation('index', state.opts))
|
||||
]),
|
||||
t.identifier('default')
|
||||
)
|
||||
)
|
||||
]);
|
||||
|
||||
path.replaceWith(importIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
70
packages/babel-plugin-react-native-web/src/moduleMap.js
Normal file
70
packages/babel-plugin-react-native-web/src/moduleMap.js
Normal file
@@ -0,0 +1,70 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
|
||||
module.exports = {
|
||||
ART: true,
|
||||
AccessibilityInfo: true,
|
||||
ActivityIndicator: true,
|
||||
Alert: true,
|
||||
Animated: true,
|
||||
AppRegistry: true,
|
||||
AppState: true,
|
||||
AsyncStorage: true,
|
||||
BackHandler: true,
|
||||
Button: true,
|
||||
CheckBox: true,
|
||||
Clipboard: true,
|
||||
ColorPropType: true,
|
||||
DeviceInfo: true,
|
||||
Dimensions: true,
|
||||
Easing: true,
|
||||
EdgeInsetsPropType: true,
|
||||
FlatList: true,
|
||||
I18nManager: true,
|
||||
Image: true,
|
||||
ImageBackground: true,
|
||||
InteractionManager: true,
|
||||
Keyboard: true,
|
||||
KeyboardAvoidingView: true,
|
||||
LayoutAnimation: true,
|
||||
Linking: true,
|
||||
ListView: true,
|
||||
Modal: true,
|
||||
NativeEventEmitter: 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,
|
||||
Share: true,
|
||||
Slider: true,
|
||||
StatusBar: true,
|
||||
StyleSheet: true,
|
||||
SwipeableFlatList: true,
|
||||
SwipeableListView: 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,
|
||||
YellowBox: 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>
|
||||
35
packages/benchmarks/package.json
Normal file
35
packages/benchmarks/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "benchmarks",
|
||||
"version": "0.9.13",
|
||||
"scripts": {
|
||||
"build": "mkdir -p dist && cp -f index.html dist/index.html && ./node_modules/.bin/webpack-cli --config ./webpack.config.js",
|
||||
"release": "NODE_ENV=production 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": "^2.2.3",
|
||||
"classnames": "^2.2.6",
|
||||
"d3-scale-chromatic": "^1.3.3",
|
||||
"emotion": "^10.0.5",
|
||||
"fela": "^10.0.2",
|
||||
"react": "^16.7.0",
|
||||
"react-dom": "^16.7.0",
|
||||
"react-fela": "^10.0.2",
|
||||
"react-jss": "^8.6.1",
|
||||
"react-native-web": "0.9.13",
|
||||
"reactxp": "^1.5.0",
|
||||
"styled-components": "^4.1.3",
|
||||
"styled-jsx": "^3.1.2",
|
||||
"styletron-engine-atomic": "^1.0.13",
|
||||
"styletron-react": "^4.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-react-native-web": "0.9.13",
|
||||
"css-loader": "^2.0.2",
|
||||
"style-loader": "^0.23.1",
|
||||
"url-loader": "^1.1.2",
|
||||
"webpack": "^4.28.1",
|
||||
"webpack-bundle-analyzer": "^3.0.3",
|
||||
"webpack-cli": "^3.1.2"
|
||||
}
|
||||
}
|
||||
297
packages/benchmarks/src/app/App.js
Normal file
297
packages/benchmarks/src/app/App.js
Normal file
@@ -0,0 +1,297 @@
|
||||
/* 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,
|
||||
flex: 1
|
||||
}
|
||||
});
|
||||
253
packages/benchmarks/src/app/Benchmark/index.js
Normal file
253
packages/benchmarks/src/app/Benchmark/index.js
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2017 Paul Armstrong
|
||||
* https://github.com/paularmstrong/react-component-benchmark
|
||||
*/
|
||||
|
||||
/* 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
|
||||
};
|
||||
71
packages/benchmarks/src/app/Button.js
Normal file
71
packages/benchmarks/src/app/Button.js
Normal file
@@ -0,0 +1,71 @@
|
||||
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',
|
||||
fontSize: 20,
|
||||
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 '../app/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 '../app/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;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user