Compare commits

...

90 Commits
0.3.1 ... 0.5.3

Author SHA1 Message Date
Nicolas Gallagher
aa22b06359 0.5.3 2018-03-15 13:32:24 -07:00
Paul Armstrong
2aa565c7c3 [fix] Picker default fontFamily
Close #860
2018-03-15 13:08:23 -07:00
Sakamoto, Kazunori
7b9b57960d Update getting-started.md
Fix __DEV__ value in example web/webpack.config.js

Close #862
2018-03-15 13:06:58 -07:00
Nicolas Gallagher
eae3ee9dca 0.5.2 2018-03-07 19:56:46 -08:00
Nicolas Gallagher
74e1a196b6 [fix] Linking.openURL works with nonce use
`Linking` no longer uses an iframe hack to add 'noopener' support to
older Safari. Instead it relies on browsers having support for
'noopener'.

Fix #837
Close #846
2018-03-05 11:50:08 -08:00
Nicolas Gallagher
48da9814e7 Update benchmark libraries 2018-03-05 10:40:02 -08:00
Nicolas Gallagher
4d391ef57c Link to example integrations in README 2018-03-05 10:22:26 -08:00
Nicolas Gallagher
780df69a80 Update link to Glitch starter 2018-03-01 19:37:12 -08:00
Nicolas Gallagher
6c229da01f Minor improvement to AppRegistry console.log message 2018-02-26 15:20:35 -08:00
Nicolas Gallagher
ae7aa818fb 0.5.1 2018-02-20 17:48:12 -08:00
Nicolas Gallagher
306cf67932 [add] Image style support for animations, interactions, and filters 2018-02-20 17:45:19 -08:00
Nicolas Gallagher
619c2048be [add] support for 'transformStyle' style prop 2018-02-20 17:44:22 -08:00
Nicolas Gallagher
b9f9a4f8d7 Move some View style propTypes to InteractionPropTypes 2018-02-20 17:43:22 -08:00
Nicolas Gallagher
58bc18c2f5 Group 'transition' style propTypes with animations 2018-02-20 17:42:19 -08:00
Nicolas Gallagher
073940fc4e Group 'perspective' style propType with transforms 2018-02-20 17:40:57 -08:00
Nicolas Gallagher
5c462303de Inline some values in TextStylePropTypes 2018-02-20 17:32:45 -08:00
Nicolas Gallagher
5fb92da317 [fix] Switch layout when left/right do not flip
The I18nManager can now disable the automatic BiDi flipping of
left/right, which caused the Switch layout to break in RTL mode. Change
the styles to use start/end.
2018-02-20 16:44:46 -08:00
Nicolas Gallagher
cafe10d851 Improve View 'pointerEvents' example 2018-02-19 18:48:31 -08:00
Nicolas Gallagher
b28581f44e [fix] Touchable 'touchAction' value 2018-02-19 18:27:49 -08:00
Nicolas Gallagher
9333e7e887 0.5.0 2018-02-19 15:48:05 -08:00
Nicolas Gallagher
b28cbbb37e [fix] TextInput default font styles
Match React Native (and web) expected defaults.
2018-02-19 15:29:05 -08:00
Nicolas Gallagher
a53372ceb3 [fix] babel-plugin only rewrites paths for known modules
Don't rewrite import paths for non-existent modules or types. They will
attempt to be imported from the package's main export. This change
currently requires a module map to be generated for the babel-plugin to
use. The map is automatically regenerated for any commit that alters the
entry file of react-native-web.

Fix #822
2018-02-19 13:05:42 -08:00
Nicolas Gallagher
239a43978f Update yarn.lock 2018-02-18 17:43:22 -08:00
Nicolas Gallagher
b4e4bfbb3c Fix the event handlers with event normalization
Events are now normalized within the responder system, so those handlers
don't need to be listed anymore. Recently added events (blur,
contextmenu, focus) are now also normalized.
2018-02-18 17:42:02 -08:00
Nicolas Gallagher
893963a799 [fix] PanResponder grant firing twice on touch devices
Touch events can produce trailing mouse events, which cause unwanted
events to fire in the responder event system.  This issue is avoided in
`Touchable` by cancelling the event in the responder release callback.
To fix the issue in other areas, like the PaneResponder, this hack is
moved into `createElement` and applied to event `onResponderRelease`
callback.

Fix #802
2018-02-18 17:41:48 -08:00
Nicolas Gallagher
e5adc5a37c Add 'coveragePathIgnorePatterns' to jest config 2018-02-17 18:03:36 -08:00
Nicolas Gallagher
6d908189a7 [fix] Text support for 'fontVariant' style
Fix #824
Close #825
2018-02-17 18:03:31 -08:00
Nicolas Gallagher
31db333ba3 [fix] Image support for SVG base64 data
The previous fix to support inline SVG data in utf-8 format broke images
that were rendering base64 SVGs.
2018-02-17 15:55:45 -08:00
Nicolas Gallagher
9fe089ca21 [add] development warning about using '!important' in styles
React Native for Web may use '!important' as part of the internal
resolving of styles. But user styles should never be allowed to include
'!important' in the value. Print a warning to the console when they do.
2018-02-17 14:46:31 -08:00
Nicolas Gallagher
a314d5b2e4 [change] MIT license
Now possible to license under MIT following the change to React Native's
license.

26684cf3ad

Fix #828
2018-02-17 12:23:45 -08:00
Nicolas Gallagher
fb845ebf44 Update benchmarks dependencies 2018-02-16 19:08:19 -08:00
Nicolas Gallagher
0d0c7e6e27 Document 'onBlur', 'onFocus', and 'onContextMenu' props 2018-02-16 18:53:33 -08:00
Nicolas Gallagher
f37003a079 Add end/start to BorderPropTypes 2018-02-15 19:08:34 -08:00
Nicolas Gallagher
f1fc2a9e37 Rewrite I18nManager and i18nStyle unit tests 2018-02-15 18:46:08 -08:00
Nicolas Gallagher
92794cdc9f [add] I18nManager and StyleSheet support for RTL without left/right flip
I18nManager supports `doLeftAndRightSwapInRTL` and
`swapLeftAndRightInRTL` to query and control the BiDi-flipping of
left/right properties and values. For example, you may choose to use
`end`/`start` for positioning that flips with writing direction, and
then disable `left`/`right` swapping in RTL so that `left` will always
be `left`.

The StyleSheet resolver cache must also account for the third "direction"
variant: RTL with no swapping of left/right.
2018-02-15 18:45:45 -08:00
Nicolas Gallagher
b754776373 [add] StyleSheet support for start/end properties and values
Add support for new style properties and values that automatically
account for the writing direction (as introduced in React Native
0.51.0). The start/end variants are automatically resolved to match the
global writing direction, as defined by I18nManager.isRTL. Start/End
take precedence over Left/Right.

Adds support for the following properties:

* `borderTop{End,Start}Radius`
* `borderBottom{End,Start}Radius`
* `border{End,Start}Color`
* `border{End,Start}Style`
* `border{End,Start}Width`
* `end`
* `margin{End,Start}`
* `padding{End,Start}`
* `start`

And values:

* `clear: "end" | "start"`
* `float: "end" | "start"`
* `textAlign: "end" | "start"`
2018-02-15 18:43:09 -08:00
Nicolas Gallagher
155b34e495 Fix unit test timeouts on Travis CI
Jest recommends using 'runInBand' for Travis CI. It runs all tests
serially in the current process, rather than creating a worker pool of
child processes that run tests.
2018-02-14 14:26:03 -08:00
Nicolas Gallagher
00c9dc4236 [fix] setNativeProps can apply flex styles
Updates the 'setValueForStyle' implementation to support style values
that contain "!important". This allows the 'flex{Basis,Grow,Shrink}'
values created by the style resolver to be applied. They currently use
the important priority as a work-around for browser-inconsistencies in
the 'flex' shorthand.

Upstream fix: https://github.com/facebook/react/pull/12181

Ref #798
Close #813
2018-02-14 13:51:00 -08:00
Nicolas Gallagher
b66aba1a06 website: fix ImageBackground source code link
Close #820
2018-02-14 13:39:14 -08:00
Nicolas Gallagher
17f8a674b8 [fix] ResponderEventPlugin skips 'mouseup' when no touch is active
Prevent the responder system recording 'mouseup' events if there is no
active 'touch'.

Fix #816
2018-02-13 15:02:09 -08:00
Nicolas Gallagher
b8080ba775 [fix] Touchable press events regression in 9ee89bc
Fix a regression introduced by "ResponderEvent event filtering"
9ee89bc7f7. Touchable press events were
firing twice after the event normalization was moved up into
'extractEvents'. The reason is:

1) events were previously not normalized throughout the responder system
2) once they were normalized, the fix introduced by a535c558d8 stopped working
3) because normalized nativeEvent did not include event data like 'cancelable'.

This patch adds more of the original nativeEvent fields to the
normalized event, so that React's synthetic event can return the correct
information for 'bubbles', 'cancelable', 'isDefaultPrevented()', etc.
2018-02-13 13:34:17 -08:00
Nicolas Gallagher
7265736545 [fix] StyleSheet accepts 'space-evenly' value for 'justifyContent' 2018-02-12 10:35:30 -08:00
Bulat Kidasov
399f465e59 [fix] NativeMethodsMixin measure/measureInWindow with scroll
Account for scroll offsets when calculating measurements.

Fix #702
Fix #805
Close #806
2018-02-12 10:27:47 -08:00
Nicolas Gallagher
9ee89bc7f7 [fix] ResponderEventPlugin event filtering
Exclude middle, wheel, and right click mouse events from the responder
system. This fixes the Touchables incorrectly triggering 'onPress' in
response to these events.

Filter mousemove events in the 'extractEvents' methods, and check for
active touches rather than the length of the touch bank. This fixes the
PanResponder not functioning after the first touch in Firefox.

Fix #719
Fix #729
Close #804
2018-02-12 10:08:44 -08:00
Jakub Rimal
748b2d0f3f [fix] Image 'source' prop update when missing in initial render
Close #811
2018-02-12 08:27:48 -08:00
Nicolas Gallagher
fb4635e013 Update README's 2018-02-08 16:09:56 -08:00
Nicolas Gallagher
73b459e770 [add] TextInput onKeyPress support for arrow keys
Close #791
Close #792
2018-02-08 10:57:32 -08:00
Nicolas Gallagher
a41af0f65f 0.4.0 2018-02-06 16:35:20 -08:00
Nicolas Gallagher
96eecc0da3 Replace lerna with custom script
Each package version is now updated with each release.

Fix #783
2018-02-06 16:30:03 -08:00
Nicolas Gallagher
69d5373222 [fix] Firefox rendering of TextInput
Removes UA controls from numeric inputs in Firefox, and makes the
default border solid in all browsers.

Fix #789
2018-02-06 11:17:38 -08:00
Nicolas Gallagher
538ab88eda Separate modules for StyleSheet and related side-effects 2018-02-05 12:18:43 -08:00
Nicolas Gallagher
21b3f39c0b [fix] babel-plugin require call for compiled modules
require('module') => require('module').default

Fix #786
2018-02-04 10:42:21 -08:00
Nicolas Gallagher
998e275e65 [add] CSS keyframes support via 'animationName' style
Keyframes can be defined using an array of objects as the value of
'animationName'. Each keyframe is transformed into a CSS keyframe rule.
2018-02-03 11:48:57 -08:00
Nicolas Gallagher
31d428a649 [fix] add support for TextInput placeholderTextColor prop
This change also ensures that potential object-styles (user-provided or
placeholderTextColor) are group together at the end of the style array.

Fix #560
2018-01-31 10:55:45 -08:00
Nicolas Gallagher
240cf7e05f [change] AppRegistry and StyleSheet APIs to fix SSR
SSR was not working correctly. Styles would accumulate in the style
cache and the styles returned for each 'getApplication' call would not
represent the styles needed to render a given tree, but rather all the
styles needed to render every tree that has been rendered by that server
instance.

This is now fixed by reinitializing the style resolver after a call to
'getStyleSheet' on the server. The return type of
'AppRegistry.getApplication' is changed – { element, getStyleElement }.
The 'getStyleElement' function must be called after the component tree
has been rendered.  Note that if 'getStyleElement' is not called for a
response, then its styles may leak into the next response's styles (but
will not affect the UX).

This patch also removes the 'StyleSheet.getStyleSheets' (web-only) API
and requires SSR to be done using 'AppRegistry.getApplication'.

Fix #778
2018-01-31 10:55:45 -08:00
Nicolas Gallagher
2ad710d83a [change] StyleSheet does not eagerly resolve and inject styles
Resolving styles at the same time as they are registered is problematic.
This work doesn't need to be complete until the moment a component is
rendered, rather than being done at startup.
2018-01-31 10:55:45 -08:00
Nicolas Gallagher
dcce72b66e Reorganize style resolver
The style resolver is reorganized to remove React Native style
registration, and contain all the stateful resolver logic (previously
partially found in NativeMethodsMixin).

Style registration is done in 'StyleSheet.create', and will subsequently
be decoupled from eager style resolution.

The stateful resolver is fully contained in the
ReactNativeStyleResolver. The options are removed from the resolver as
they aren't needed to implement the correct i18n-ified styles. This
functionality is also covered by unit tests now.
2018-01-31 10:55:44 -08:00
Nicolas Gallagher
083769d642 [fix] resolve inline-style pointerEvents to classNames
pointerEvents styles are now always resolved to classNames, even if
working with dynamic styles. In practice, inline-style pointerEvents
have always resolved to classNames as an accident of 'createElement'
registering each pointerEvent style. However, this patch ensures that
the style resolver produces correct output, and introduces the basic
mechanism by which 'placeholderTextColor' (for which classNames cannot
be precomputed) could function similarly.
2018-01-31 10:55:44 -08:00
Nicolas Gallagher
a53dba8c62 [change] simplify CSS rule injection
WebStyleSheet handles injecting CSS rules and returning the style
sheet's text content.

All CSS, including the reset, is now added using WebStyleSheet and the DOM API 'insertRule'.
Browsers will throw a parse error on unrecognized vendor-prefixes in
selectors, so rules with unsafe selectors are wrapped in '@media all {}'
before being passed to 'insertRule'. (This will be relevant when
attempting to support 'placeholderTextColor'. And it is a way to
batch-insert rules with a single call to 'insertRule'.)

The html tag 'font-family' CSS reset is removed as it's not needed.
2018-01-31 10:55:44 -08:00
Nicolas Gallagher
670d43ba04 Refactor how pointerEvents styles are managed 2018-01-31 10:55:44 -08:00
Nicolas Gallagher
73a731f2da Rename StyleManager and StyleRegistry 2018-01-31 10:55:44 -08:00
Nicolas Gallagher
1542f1f369 0.3.4 2018-01-31 10:31:50 -08:00
Nicolas Gallagher
6f58d7abe7 [fix] TextInput support for spellCheck prop
Fix #501
2018-01-31 10:27:08 -08:00
Nicolas Gallagher
7e0fbf9691 Fix yarn.lock
Yarn didn't make these changes to yarn.lock after updating packages.
2018-01-29 12:46:12 -08:00
Maximilian Stoiber
865034e8f7 Update benchmark instructions 2018-01-29 12:40:37 -08:00
Nicolas Gallagher
6e96ee4f3c Update benchmarked libraries 2018-01-29 08:59:18 -08:00
Maxime Thirouin
16d98b49f0 [fix] ignore native-only Image props
Close #788
2018-01-26 14:59:59 -08:00
Nicolas Gallagher
d04721c75a Update yarn.lock 2018-01-23 13:01:48 -08:00
Nicolas Gallagher
8512709251 Fix benchmark picker touch target size in Safari 2018-01-23 12:24:56 -08:00
Nicolas Gallagher
efeaea70a9 Benchmarks include forced layout time
This change to 'benchmarks' reports the time taken to perform a forced
layout after mounting the tree. Adding a forced layout to the stress
tests can surface how different approaches to styling may affect browser
render timings.

The total time displayed is now the sum of "scripting time" (previously
total time) and "layout time". The layout time is a reflection of the
time the browser takes to perform a style recalculation and relayout of
the document.

The Benchmark component now has a 'forceLayout' prop. When it is 'true'
a forced layout is triggered on componentDidUpdate. The time taken is
added to the sample's timing data.
2018-01-23 12:24:18 -08:00
Nicolas Gallagher
a403244e67 Update benchmarks library dependencies 2018-01-23 09:49:48 -08:00
Oleg Slobodskoi
985c1d63b6 Add benchmarks update test for react-jss
Close #785
2018-01-23 09:11:38 -08:00
Nicolas Gallagher
9d8d4057f6 Benchmark app component optimizations 2018-01-22 15:52:37 -08:00
Nicolas Gallagher
ec8843fe90 Compute benchmark props before each iteration
Fix a bug in the logic that was meant to perform component prop
computation in-between cycles.
2018-01-22 13:24:41 -08:00
Nicolas Gallagher
935970156c Benchmarks report total run time 2018-01-21 17:02:21 -08:00
Nicolas Gallagher
e4e6147081 0.3.3 2018-01-19 14:05:46 -08:00
Nicolas Gallagher
3e1b68d801 Add Moto G4 example benchmark results 2018-01-19 13:59:13 -08:00
Giuseppe Gurgone
1b493c9914 Add styled-jsx to benchmarks
Close #782
2018-01-19 12:51:21 -08:00
hushicai
6ecdc1a517 [fix] babel-plugin VariableDeclaration case
Convert VariableDeclarations, e.g.,

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

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

Fix #774
2018-01-17 17:59:05 -08:00
Nicolas Gallagher
a9342daee2 Add release script for benchmarks 2018-01-17 17:40:13 -08:00
Nicolas Gallagher
ed0cafac7c Rewrite benchmarks app
Reorganizes and rewrites the benchmarks. Each implementation is now
self-contained and the benchmarks can be run using a GUI. The benchmarks
themselves have been changed so that individual tests render over a
shorter time frame and more samples are taken.
2018-01-17 17:21:53 -08:00
Nicolas Gallagher
6e6fd4b5d0 Add note about Object.assign polyfill to docs 2018-01-16 17:14:17 -08:00
291 changed files with 5293 additions and 4017 deletions

View File

@@ -3,7 +3,6 @@
[ignore]
<PROJECT_ROOT>/.*/__tests__/.*
<PROJECT_ROOT>/packages/benchmarks/.*
<PROJECT_ROOT>/packages/.*/dist/.*
<PROJECT_ROOT>/website/.*
.*/node_modules/babel-plugin-transform-react-remove-prop-types/*

View File

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

45
LICENSE
View File

@@ -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.

View File

@@ -6,10 +6,11 @@
[React Native][react-native-url] to the Web.
* **High-quality user interfaces**: React Native for Web makes it easy to
create [fast](packages/benchmarks/README.md), adaptive web UIs in JavaScript.
It provides native-like interactions, support for multiple input modes (touch,
mouse, keyboard), optimized vendor-prefixed styles, built-in support for RTL
layout, built-in accessibility, and integrates with React Dev Tools.
create [fast](https://github.com/necolas/react-native-web/blob/master/packages/benchmarks/README.md),
adaptive web UIs in JavaScript. It provides native-like interactions, support
for multiple input modes (touch, mouse, keyboard), optimized vendor-prefixed
styles, built-in support for RTL layout, built-in accessibility, and integrates
with React Dev Tools.
* **Write once, render anywhere**: React Native for Web interoperates with
existing React DOM components and is compatible with the majority of the
@@ -18,21 +19,19 @@ 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 for Web? [Twitter](https://mobile.twitter.com),
[Major League Soccer](https://matchcenter.mlssoccer.com), [The
[Major League Soccer](https://matchcenter.mlssoccer.com),
[Flipkart](https://www.flipkart.com/), Playstation, Uber, [The
Times](https://github.com/newsuk/times-components), [React Native's
documentation](http://facebook.github.io/react-native/).
Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge.
Browser support: Chrome, Firefox, Edge, Safari 7+, IE 10+.
## Quick start
The easiest way to get started with React Native for Web is to use this
[ready-to-go project on Glitch](https://glitch.com/edit/#!/react-native-web-playground).
[ready-to-go project on Glitch](https://glitch.com/edit/#!/react-native).
You dont need to install anything to try it out.
If you are unfamiliar with setting up a React web project, please follow the
recommendations in the [React documentation](https://reactjs.org/).
## Documentation
You can find the React Native for Web API documentation [on the
@@ -54,17 +53,18 @@ yarn add --dev babel-plugin-react-native-web
### Guides
* [Getting started](website/guides/getting-started.md)
* [Style](website/guides/style.md)
* [Accessibility](website/guides/accessibility.md)
* [Internationalization](website/guides/internationalization.md)
* [Direct manipulation](website/guides/direct-manipulation.md)
* [Advanced use](website/guides/advanced.md)
* [Getting started](https://github.com/necolas/react-native-web/blob/master/website/guides/getting-started.md)
* [Style](https://github.com/necolas/react-native-web/blob/master/website/guides/style.md)
* [Accessibility](https://github.com/necolas/react-native-web/blob/master/website/guides/accessibility.md)
* [Internationalization](https://github.com/necolas/react-native-web/blob/master/website/guides/internationalization.md)
* [Direct manipulation](https://github.com/necolas/react-native-web/blob/master/website/guides/direct-manipulation.md)
* [Advanced use](https://github.com/necolas/react-native-web/blob/master/website/guides/advanced.md)
## Examples
There are several examples [on the website][website-url] and in the [website's
source code](./website). Here is an example to get you started:
source code](https://github.com/necolas/react-native-web/blob/master/website).
Here is an example to get you started:
```js
import React from 'react';
@@ -95,6 +95,16 @@ 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.
## Integrations
Examples of using React Native for Web with other web tools:
* [Next.js](https://github.com/zeit/next.js/tree/master/examples/with-react-native-web)
* [Storybook](https://github.com/necolas/react-native-web/tree/0.5.1/website/storybook/.storybook)
* [Razzle](https://github.com/jaredpalmer/razzle/tree/master/examples/with-react-native-web)
* [Phenomic](https://github.com/phenomic/phenomic/tree/v1.0.0-alpha.20/docs)
* [Styleguidist](https://github.com/styleguidist/react-styleguidist/tree/v6.2.6/examples/react-native)
## Contributing
The main purpose of this repository is to help evolve React web and native
@@ -124,7 +134,9 @@ 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.
[package-badge]: https://img.shields.io/npm/v/react-native-web.svg?style=flat
[package-url]: https://yarnpkg.com/en/package/react-native-web
@@ -132,6 +144,6 @@ React Native for Web is [BSD licensed](./LICENSE).
[ci-url]: https://travis-ci.org/necolas/react-native-web
[website-url]: https://necolas.github.io/react-native-web/storybook/
[react-native-url]: https://facebook.github.io/react-native/
[contributing-url]: ./.github/CONTRIBUTING.md
[contributing-url]: https://github.com/necolas/react-native-web/blob/master/.github/CONTRIBUTING.md
[good-first-issue-url]: https://github.com/necolas/react-native-web/labels/good%20first%20issue
[code-of-conduct]: https://code.facebook.com/codeofconduct

View File

@@ -1,10 +0,0 @@
{
"lerna": "2.5.1",
"version": "0.3.1",
"npmClient": "yarn",
"useWorkspaces": true,
"packages": [
"packages/*",
"website"
]
}

View File

@@ -1,12 +1,14 @@
{
"private": true,
"version": "0.5.3",
"name": "react-native-web-monorepo",
"scripts": {
"benchmark": "cd packages/benchmarks && yarn benchmark",
"clean": "del ./packages/*/dist",
"compile": "yarn clean && cd packages/react-native-web && babel src --out-dir dist --ignore \"**/__tests__\"",
"docs:start": "cd website && yarn start",
"docs:release": "cd website && yarn release",
"benchmarks": "cd packages/benchmarks && yarn build",
"benchmarks:release": "cd packages/benchmarks && yarn release",
"website": "cd website && yarn start",
"website:release": "cd website && yarn release",
"flow": "flow",
"fmt": "find packages scripts types website -name '*.js' | grep -v -E '(node_modules|dist|vendor)' | xargs yarn fmt:cmd",
"fmt:cmd": "prettier --write",
@@ -16,8 +18,8 @@
"precommit": "lint-staged",
"prerelease": "yarn test && yarn compile",
"release": "node ./scripts/release/publish.js",
"postrelease": "yarn docs:release",
"test": "yarn flow && yarn lint:check && yarn jest"
"postrelease": "yarn website:release && yarn benchmarks:release",
"test": "yarn flow && yarn lint:check && yarn jest --runInBand"
},
"devDependencies": {
"babel-cli": "^6.26.0",
@@ -41,9 +43,9 @@
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-react": "^7.5.1",
"flow-bin": "^0.63.1",
"glob": "^7.1.2",
"husky": "^0.14.3",
"jest": "^21.2.1",
"lerna": "^2.6.0",
"lint-staged": "^6.0.0",
"prettier": "^1.8.2",
"raf": "^3.4.0",
@@ -60,6 +62,9 @@
"fmt:cmd",
"git update-index --again",
"eslint"
],
"packages/react-native-web/src/index.js": [
"node ./scripts/babel/createModuleMap.js"
]
},
"prettier": {
@@ -67,5 +72,5 @@
"singleQuote": true
},
"author": "Nicolas Gallagher",
"license": "BSD-3-Clause"
"license": "MIT"
}

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "babel-plugin-react-native-web",
"version": "0.3.1",
"version": "0.5.3",
"description": "Babel plugin for React Native for Web",
"main": "index.js",
"devDependencies": {

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,61 @@
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
module.exports = {
ART: true,
ActivityIndicator: true,
Animated: true,
AppRegistry: true,
AppState: true,
AsyncStorage: true,
BackHandler: true,
Button: true,
CheckBox: true,
Clipboard: true,
ColorPropType: true,
Dimensions: true,
Easing: true,
EdgeInsetsPropType: true,
FlatList: true,
I18nManager: true,
Image: true,
ImageBackground: true,
InteractionManager: true,
Keyboard: true,
KeyboardAvoidingView: true,
Linking: true,
ListView: true,
Modal: true,
NativeModules: true,
NetInfo: true,
PanResponder: true,
Picker: true,
PixelRatio: true,
Platform: true,
PointPropType: true,
ProgressBar: true,
RefreshControl: true,
SafeAreaView: true,
ScrollView: true,
SectionList: true,
Slider: true,
StatusBar: true,
StyleSheet: true,
Switch: true,
Text: true,
TextInput: true,
TextPropTypes: true,
Touchable: true,
TouchableHighlight: true,
TouchableNativeFeedback: true,
TouchableOpacity: true,
TouchableWithoutFeedback: true,
UIManager: true,
Vibration: true,
View: true,
ViewPropTypes: true,
VirtualizedList: true,
createElement: true,
findNodeHandle: true,
processColor: true,
render: true,
unmountComponentAtNode: true
};

View File

@@ -1,56 +1,70 @@
# benchmarks
To run these benchmarks:
Try the [benchmarks app](https://necolas.github.io/react-native-web/benchmarks) online.
To run the benchmarks locally:
```
yarn benchmark
yarn benchmarks
open ./packages/benchmarks/dist/index.html
```
To run benchmarks for individual implementations append `?<name>,<name>` to the
URL, e.g., `?css-modules,react-native-web`.
Develop against these benchmarks:
```
yarn compile --watch
yarn benchmarks --watch
```
## Notes
These benchmarks are crude approximations of extreme cases that libraries may
encounter. The deep and wide tree cases look at the performance of mounting and
rendering large trees of styled elements. The Triangle case looks at the
performance of repeated style updates to a large mounted tree. Some libraries
must inject new styles for each "dynamic style", whereas others may not.
Libraries without support for dynamic styles (i.e., they rely on user-authored
inline styles) do not include the `SierpinskiTriangle` benchmark.
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_.
## Results
No benchmark will run for more than 20 seconds.
Typical render timings*: mean ± two standard deviations.
### Mount deep/wide tree
| Implementation | Deep tree (ms) | Wide tree (ms) | Triangle (ms) |
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) |
| :--- | ---: | ---: | ---: |
| `react-native-web@0.2.2` | `89.67` `±28.51` | `167.46` `±27.03` | `65.40` `±19.50` |
| `css-modules` | `77.42` 45.50` | `141.44` 33.96` | - |
| `inline-styles` | `236.25` `±95.57` | `477.01` 88.30` | `40.95` 23.53` |
| `css-modules` | `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` |
Other libraries
### Moto G4
| Implementation | Deep tree (ms) | Wide tree (ms) | Triangle (ms) |
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) |
| :--- | ---: | ---: | ---: |
| `styletron@3.0.0-rc.5` | `83.53` 33.55` | `153.12` 39.13` | `56.47` `±24.22` |
| `aphrodite@1.2.5` | `88.23` 31.22` | `164.03` 34.70` | - |
| `glamor@2.20.40` | `110.09` 34.20` | `182.06` `±50.39` | ‡ |
| `emotion@8.0.12` | `103.44` `±32.12` | `204.45` `±41.00` | `110.28` `±26.94` |
| `react-jss@8.2.0` | `136.17` `±59.23` | `270.51` `±69.20` | - |
| `styled-components@2.3.2` | `217.57` `±51.90` | `437.57` `±65.74` | `76.99` `±41.79` |
| `reactxp@0.46.6` | `240.88` `±79.82` | `467.32` `±74.42` | `70.95` `±32.90`|
| `radium@0.19.6` | `400.19` `±94.58` | `816.59` `±91.10` | `71.13` `±27.22` |
These results indicate that render times when using `react-native-web`,
`css-modules`, `aphrodite`, and `styletron` are roughly equivalent and
significantly faster than alternatives.
*MacBook Pro (13-inch, Early 2011); 2.3 GHz Intel Core i5; 8 GB 1333 MHz DDR3. Google Chrome 62.
‡Glamor essentially crashes the browser tab.
| `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` |

View File

@@ -3,9 +3,14 @@
<head>
<meta charset="UTF-8">
<title>Performance tests</title>
<meta name="viewport" content="width=device-width">
<style>
html, body { height: 100%; width: 100%; overflow: hidden; }
.root { height: 100%; overflow: hidden; }
</style>
</head>
<body>
<div class="root"></div>
<script src="dist/performance.bundle.js"></script>
<script src="./bundle.js"></script>
</body>
</html>

View File

@@ -1,33 +1,36 @@
{
"private": true,
"name": "benchmarks",
"version": "0.3.1",
"version": "0.5.3",
"scripts": {
"benchmark": "webpack --config ./webpack.config.js && open index.html"
"build": "mkdir -p dist && cp -f index.html dist/index.html && webpack --config ./webpack.config.js",
"release": "yarn build && git checkout gh-pages && rm -rf ../../benchmarks && mv dist ../../benchmarks && git add -A && git commit -m \"Benchmarks deploy\" && git push origin gh-pages && git checkout -"
},
"dependencies": {
"aphrodite": "^1.2.5",
"babel-polyfill": "^6.26.0",
"aphrodite": "1.2.5",
"classnames": "^2.2.5",
"d3-scale-chromatic": "^1.1.1",
"emotion": "^8.0.12",
"glamor": "^2.20.40",
"marky": "^1.2.0",
"radium": "^0.19.6",
"d3-scale-chromatic": "^1.2.0",
"emotion": "^9.0.2",
"fela": "^6.1.4",
"glamor": "2.20.40",
"radium": "^0.22.1",
"react": "^16.2.0",
"react-component-benchmark": "^0.0.4",
"react-dom": "^16.2.0",
"react-jss": "^8.2.0",
"react-native-web": "^0.3.1",
"reactxp": "^0.46.6",
"styled-components": "^2.3.2",
"styletron-client": "^3.0.0-rc.5",
"styletron-utils": "^3.0.0-rc.3"
"react-fela": "^7.0.1",
"react-jss": "^8.3.3",
"react-native-web": "0.5.3",
"reactxp": "^1.0.0",
"styled-components": "^3.2.0",
"styled-jsx": "^2.2.5",
"styletron-engine-atomic": "^1.0.0",
"styletron-react": "^4.0.0"
},
"devDependencies": {
"babel-plugin-react-native-web": "^0.3.1",
"css-loader": "^0.28.7",
"style-loader": "^0.19.1",
"babel-plugin-react-native-web": "0.5.3",
"css-loader": "^0.28.10",
"style-loader": "^0.20.2",
"webpack": "^3.10.0",
"webpack-bundle-analyzer": "^2.9.1"
"webpack-bundle-analyzer": "^2.9.2"
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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' }]
}
});

View 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;

View 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;

View File

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

View File

@@ -1,98 +0,0 @@
import * as marky from 'marky';
const fmt = time => `${Math.round(time * 100) / 100}ms`;
const measure = (name, fn) => {
marky.mark(name);
fn();
const performanceMeasure = marky.stop(name);
return performanceMeasure.duration;
};
const mean = values => {
const sum = values.reduce((sum, value) => sum + value, 0);
return sum / values.length;
};
const median = values => {
if (!Array.isArray(values)) {
return 0;
}
if (values.length === 1) {
return values[0];
}
const numbers = [...values].sort((a, b) => a - b);
return (numbers[(numbers.length - 1) >> 1] + numbers[numbers.length >> 1]) / 2;
};
const standardDeviation = values => {
const avg = mean(values);
const squareDiffs = values.map(value => {
const diff = value - avg;
return diff * diff;
});
const meanSquareDiff = mean(squareDiffs);
return Math.sqrt(meanSquareDiff);
};
export const log = (name, description, durations) => {
const stdDev = standardDeviation(durations);
const formattedMean = fmt(mean(durations));
const formattedMedian = fmt(median(durations));
const formattedStdDev = fmt(stdDev);
console.groupCollapsed(`${name}\n${formattedMean} ±${fmt(2 * stdDev)}`);
description && console.log(description);
console.log(`Median: ${formattedMedian}`);
console.log(`Mean: ${formattedMean}`);
console.log(`Standard deviation: ${formattedStdDev}`);
console.log(durations);
console.groupEnd();
};
const benchmark = ({ name, description, setup, teardown, task, runs }) => {
return new Promise(resolve => {
const durations = [];
let i = 0;
setup();
teardown();
const done = () => {
log(name, description, durations);
resolve();
};
const a = () => {
setup();
window.requestAnimationFrame(b);
};
const b = () => {
const duration = measure('mean', task);
durations.push(duration);
window.requestAnimationFrame(c);
};
const c = () => {
teardown();
window.requestAnimationFrame(d);
};
const d = () => {
i += 1;
if (i < runs) {
window.requestAnimationFrame(a);
} else {
window.requestAnimationFrame(done);
}
};
window.requestAnimationFrame(a);
});
};
export default benchmark;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,26 @@
/* eslint-disable react/prop-types */
import injectSheet from 'react-jss';
import React from 'react';
const Dot = ({ classes, children }) => <div className={classes.root}>{children}</div>;
const styles = {
root: {
position: 'absolute',
cursor: 'pointer',
width: 0,
height: 0,
borderColor: 'transparent',
borderStyle: 'solid',
borderTopWidth: 0,
transform: 'translate(50%, 50%)',
borderBottomColor: ({ color }) => color,
borderRightWidth: ({ size }) => size / 2,
borderBottomWidth: ({ size }) => size / 2,
borderLeftWidth: ({ size }) => size / 2,
marginLeft: ({ x }) => x,
marginTop: ({ y }) => y
}
};
export default injectSheet(styles)(Dot);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,97 +1,56 @@
import aphrodite from './implementations/aphrodite';
import cssModules from './implementations/css-modules';
import emotion from './implementations/emotion';
import jss from './implementations/jss';
import glamor from './implementations/glamor';
import inlineStyles from './implementations/inline-styles';
import radium from './implementations/radium';
import reactNativeWeb from './implementations/react-native-web';
import reactxp from './implementations/reactxp';
import styledComponents from './implementations/styled-components';
import styletron from './implementations/styletron';
import App from './app/App';
import impl from './impl';
import Tree from './cases/Tree';
import SierpinskiTriangle from './cases/SierpinskiTriangle';
import renderDeepTree from './cases/renderDeepTree';
import renderSierpinskiTriangle from './cases/renderSierpinskiTriangle';
import renderWideTree from './cases/renderWideTree';
import React from 'react';
import ReactDOM from 'react-dom';
const testMatrix = {
'inline-styles': [
() => renderDeepTree('inline-styles', inlineStyles),
() => renderWideTree('inline-styles', inlineStyles),
() => renderSierpinskiTriangle('inline-styles', inlineStyles)
],
'css-modules': [
() => renderDeepTree('css-modules', cssModules),
() => renderWideTree('css-modules', cssModules)
],
'react-native-web': [
() => renderDeepTree('react-native-web', reactNativeWeb),
() => renderWideTree('react-native-web', reactNativeWeb),
() => renderSierpinskiTriangle('react-native-web', reactNativeWeb)
],
const implementations = impl;
const packageNames = Object.keys(implementations);
aphrodite: [
() => renderDeepTree('aphrodite', aphrodite),
() => renderWideTree('aphrodite', aphrodite)
],
emotion: [
() => renderDeepTree('emotion', emotion),
() => renderWideTree('emotion', emotion),
() => renderSierpinskiTriangle('emotion', emotion)
],
glamor: [
() => renderDeepTree('glamor', glamor),
() => renderWideTree('glamor', glamor)
// disabled: glamor starts to lock up the browser
// () => renderSierpinskiTriangle('glamor', glamor)
],
jss: [() => renderDeepTree('jss', jss), () => renderWideTree('jss', jss)],
radium: [
() => renderDeepTree('radium', radium),
() => renderWideTree('radium', radium),
() => renderSierpinskiTriangle('radium', radium)
],
reactxp: [
() => renderDeepTree('reactxp', reactxp),
() => renderWideTree('reactxp', reactxp),
() => renderSierpinskiTriangle('reactxp', reactxp)
],
'styled-components': [
() => renderDeepTree('styled-components', styledComponents),
() => renderWideTree('styled-components', styledComponents),
() => renderSierpinskiTriangle('styled-components', styledComponents)
],
styletron: [
() => renderDeepTree('styletron', styletron),
() => renderWideTree('styletron', styletron),
() => renderSierpinskiTriangle('styletron', styletron)
]
const createTestBlock = fn => {
return packageNames.reduce((testSetups, packageName) => {
const { name, components, version } = implementations[packageName];
const { Component, getComponentProps, sampleCount, Provider, benchmarkType } = fn(components);
testSetups[packageName] = {
Component,
getComponentProps,
sampleCount,
Provider,
benchmarkType,
version,
name
};
return testSetups;
}, {});
};
const allTests = Object.keys(testMatrix).reduce((acc, curr) => {
testMatrix[curr].forEach(test => {
acc.push(test);
});
return acc;
}, []);
const tests = {
'Mount deep tree': createTestBlock(components => ({
benchmarkType: 'mount',
Component: Tree,
getComponentProps: () => ({ breadth: 2, components, depth: 7, id: 0, wrap: 1 }),
Provider: components.Provider,
sampleCount: 50
})),
'Mount wide tree': createTestBlock(components => ({
benchmarkType: 'mount',
Component: Tree,
getComponentProps: () => ({ breadth: 6, components, depth: 3, id: 0, wrap: 2 }),
Provider: components.Provider,
sampleCount: 50
})),
'Update dynamic styles': createTestBlock(components => ({
benchmarkType: 'update',
Component: SierpinskiTriangle,
getComponentProps: ({ cycle }) => {
return { components, s: 200, renderCount: cycle, x: 0, y: 0 };
},
Provider: components.Provider,
sampleCount: 100
}))
};
const tests = [];
if (window.location.search) {
window.location.search
.slice(1)
.split(',')
.forEach(implementation => {
if (Array.isArray(testMatrix[implementation])) {
tests.push(...testMatrix[implementation]);
} else {
throw new Error(`Benchmark for ${implementation} not found`);
}
});
} else {
tests.push(...allTests);
}
tests.push(() => () => Promise.resolve(console.log('Done')));
tests.reduce((promise, test) => promise.then(test()), Promise.resolve());
ReactDOM.render(<App tests={tests} />, document.querySelector('.root'));

View File

@@ -7,10 +7,10 @@ const appDirectory = path.resolve(__dirname);
module.exports = {
context: __dirname,
entry: ['babel-polyfill', './src/index'],
entry: './src/index',
output: {
path: path.resolve(appDirectory, 'dist'),
filename: 'performance.bundle.js'
filename: 'bundle.js'
},
module: {
rules: [
@@ -30,9 +30,9 @@ module.exports = {
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheDirectory: false,
presets: babelPreset,
plugins: ['react-native-web']
plugins: ['react-native-web', 'styled-jsx/babel']
}
}
}
@@ -49,14 +49,10 @@ module.exports = {
new webpack.optimize.UglifyJsPlugin({
compress: {
dead_code: true,
drop_console: false,
screw_ie8: true,
warnings: false
}
})
],
resolve: {
alias: {
'react-native': 'react-native-web'
}
}
]
};

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-web",
"version": "0.3.1",
"version": "0.5.3",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [
@@ -27,7 +27,7 @@
"react-dom": "16.x.x"
},
"author": "Nicolas Gallagher",
"license": "BSD-3-Clause",
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/necolas/react-native-web.git"

View File

@@ -17,7 +17,24 @@ exports[`components/ActivityIndicator prop "animating" is "false" 1`] = `
Object {
"animationDuration": "0.75s",
"animationIterationCount": "infinite",
"animationName": "rn-ActivityIndicator-animation",
"animationName": Array [
Object {
"0%": Object {
"transform": Array [
Object {
"rotate": "0deg",
},
],
},
"100%": Object {
"transform": Array [
Object {
"rotate": "360deg",
},
],
},
},
],
"animationPlayState": "paused",
"animationTimingFunction": "linear",
"height": 20,
@@ -80,7 +97,24 @@ exports[`components/ActivityIndicator prop "animating" is "true" 1`] = `
Object {
"animationDuration": "0.75s",
"animationIterationCount": "infinite",
"animationName": "rn-ActivityIndicator-animation",
"animationName": Array [
Object {
"0%": Object {
"transform": Array [
Object {
"rotate": "0deg",
},
],
},
"100%": Object {
"transform": Array [
Object {
"rotate": "360deg",
},
],
},
},
],
"animationTimingFunction": "linear",
"height": 20,
"width": 20,
@@ -177,7 +211,24 @@ exports[`components/ActivityIndicator prop "hidesWhenStopped" is "false" 1`] = `
Object {
"animationDuration": "0.75s",
"animationIterationCount": "infinite",
"animationName": "rn-ActivityIndicator-animation",
"animationName": Array [
Object {
"0%": Object {
"transform": Array [
Object {
"rotate": "0deg",
},
],
},
"100%": Object {
"transform": Array [
Object {
"rotate": "360deg",
},
],
},
},
],
"animationPlayState": "paused",
"animationTimingFunction": "linear",
"height": 20,
@@ -239,7 +290,24 @@ exports[`components/ActivityIndicator prop "hidesWhenStopped" is "true" 1`] = `
Object {
"animationDuration": "0.75s",
"animationIterationCount": "infinite",
"animationName": "rn-ActivityIndicator-animation",
"animationName": Array [
Object {
"0%": Object {
"transform": Array [
Object {
"rotate": "0deg",
},
],
},
"100%": Object {
"transform": Array [
Object {
"rotate": "360deg",
},
],
},
},
],
"animationPlayState": "paused",
"animationTimingFunction": "linear",
"height": 20,
@@ -302,7 +370,24 @@ exports[`components/ActivityIndicator prop "size" is "large" 1`] = `
Object {
"animationDuration": "0.75s",
"animationIterationCount": "infinite",
"animationName": "rn-ActivityIndicator-animation",
"animationName": Array [
Object {
"0%": Object {
"transform": Array [
Object {
"rotate": "0deg",
},
],
},
"100%": Object {
"transform": Array [
Object {
"rotate": "360deg",
},
],
},
},
],
"animationTimingFunction": "linear",
"height": 36,
"width": 36,
@@ -363,7 +448,24 @@ exports[`components/ActivityIndicator prop "size" is a number 1`] = `
Object {
"animationDuration": "0.75s",
"animationIterationCount": "infinite",
"animationName": "rn-ActivityIndicator-animation",
"animationName": Array [
Object {
"0%": Object {
"transform": Array [
Object {
"rotate": "0deg",
},
],
},
"100%": Object {
"transform": Array [
Object {
"rotate": "360deg",
},
],
},
},
],
"animationTimingFunction": "linear",
"height": 30,
"width": 30,

View File

@@ -1,9 +1,8 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule ActivityIndicator
@@ -88,7 +87,12 @@ const styles = StyleSheet.create({
},
animation: {
animationDuration: '0.75s',
animationName: 'rn-ActivityIndicator-animation',
animationName: [
{
'0%': { transform: [{ rotate: '0deg' }] },
'100%': { transform: [{ rotate: '360deg' }] }
}
],
animationTimingFunction: 'linear',
animationIterationCount: 'infinite'
},

View File

@@ -1,10 +1,8 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @providesModule Animated
* @flow

View File

@@ -1,9 +1,8 @@
/**
* Copyright (c) 2016-present, Nicolas Gallagher.
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow

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