diff --git a/README.md b/README.md index a51134de..ba236889 100644 --- a/README.md +++ b/README.md @@ -18,35 +18,31 @@ Browser support: Chrome, Firefox, Safari >= 7, IE 10, Edge. "React Native for Web" is a project to bring React Native's building blocks and touch handling to the Web. [Read more](#why). -Browse the UI Explorer to see React Native [examples running on -Web](https://necolas.github.io/react-native-web/storybook/). Or try it out -online with [React Native for Web: Playground](https://www.webpackbin.com/bins/-KgucwxRbn7HRU-V-3Bc). +Browse the [UI Explorer](https://necolas.github.io/react-native-web/storybook/) +to see React Native examples running on Web. Or try it out online with [React +Native for Web: Playground](https://www.webpackbin.com/bins/-KgucwxRbn7HRU-V-3Bc). ## Quick start To install in your app: ``` -npm install --save react@15.4 react-dom@15.4 react-native-web +npm install --save react@15.5 react-dom@15.5 react-native-web ``` Read the [Getting Started](docs/guides/getting-started.md) guide. -Alternatively, you can quickly setup a local project -using [create-react-app](https://github.com/facebookincubator/create-react-app) -(which supports `react-native-web` out-of-the-box once installed) and -[react-native-web-starter](https://github.com/grabcode/react-native-web-starter). - ## Documentation Guides: * [Getting started](docs/guides/getting-started.md) +* [Style](docs/guides/style.md) * [Accessibility](docs/guides/accessibility.md) * [Direct manipulation](docs/guides/direct-manipulation.md) * [Internationalization](docs/guides/internationalization.md) +* [Advanced use](docs/guides/advanced.md) * [Known issues](docs/guides/known-issues.md) -* [Style](docs/guides/style.md) Exported modules: @@ -87,17 +83,16 @@ Exported modules: There are many different teams at Twitter building web applications with React. We want to share React components, libraries, and APIs between teams…much like the OSS community tries to do. At our scale, this involves dealing with -multiple, inter-related problems including: a common way to handle style, -animation, touch, viewport adaptation, accessibility, themes, RTL layout, and -server-rendering. +multiple, inter-related problems including: component styles, animation, touch +interactions, layout adaptation, accessibility, RTL layout, theming, and build- +or server-rendering. This is hard to do with React DOM, as the components are essentially the same low-level building blocks that the browser provides. However, React Native -avoids, solves, or can solve almost all these problems facing Web teams. -Central to this is React Native's JavaScript style API (not strictly -"CSS-in-JS") which avoids the key [problems with -CSS](https://speakerdeck.com/vjeux/react-css-in-js) by giving up some of the -complexity of CSS. +avoids, solves, or can solve almost all these problems. Central to this is +React Native's JavaScript style API (not strictly "CSS-in-JS") which avoids the +key [problems with CSS](https://speakerdeck.com/vjeux/react-css-in-js) by +giving up some of the complexity of CSS. ## Example code @@ -140,11 +135,11 @@ AppRegistry.runApplication('MyApp', { rootTag: document.getElementById('react-ro ## Related projects -* [react-native-web-starter](https://github.com/grabcode/react-native-web-starter) * [react-native-web-player](https://github.com/dabbott/react-native-web-player) +* [react-native-web-starter](https://github.com/grabcode/react-native-web-starter) +* [react-native-web-webpack](https://github.com/ndbroadbent/react-native-web-webpack) * [reactxp](https://github.com/microsoft/reactxp) * [react-web](https://github.com/taobaofed/react-web) -* [react-native-web-webpack](https://github.com/ndbroadbent/react-native-web-webpack) ## License diff --git a/docs/apis/StyleSheet.md b/docs/apis/StyleSheet.md index ec441c55..bf79a0bc 100644 --- a/docs/apis/StyleSheet.md +++ b/docs/apis/StyleSheet.md @@ -1,9 +1,10 @@ # StyleSheet The `StyleSheet` abstraction converts predefined styles to (vendor-prefixed) -CSS without requiring a compile-time step. Some styles cannot be resolved -outside of the render loop and are applied as inline styles. Read more about -[how to style your application](../guides/style.md). +CSS without requiring a compile-time step. Styles that cannot be resolved +outside of the render loop (e.g., dynamic positioning) are usually applied as +inline styles. Read more about [how to style your +application](../guides/style.md). ## Methods @@ -15,7 +16,7 @@ Each key of the object passed to `create` must define a style object. Flattens an array of styles into a single style object. -**renderToString**: function +(web) **renderToString**: function Returns a string of the stylesheet for use in server-side rendering. diff --git a/docs/guides/advanced.md b/docs/guides/advanced.md new file mode 100644 index 00000000..f3f235de --- /dev/null +++ b/docs/guides/advanced.md @@ -0,0 +1,104 @@ +# Advanced use + +## Use with existing React DOM components + +React Native for Web exports a web-specific module called `createDOMElement`, +which can be used to wrap React DOM components. This allows you to use React +Native's accessibility and style optimizations. + +In the example below, `Video` will now accept common React Native props such as +`accessibilityLabel`, `accessible`, `style`, and even the Responder event +props. + +```js +import { createDOMElement } from 'react-native'; + +const Video = (props) => createDOMElement('video', props); +``` + +This also works with composite components defined in your existing component +gallery or dependencies ([live example](https://www.webpackbin.com/bins/-KiTSGFw3fB9Szg7quLI)). + +```js +import RaisedButton from 'material-ui/RaisedButton'; +import { createDOMElement, StyleSheet } from 'react-native'; + +const CustomButton = (props) => createDOMElement(RaisedButton, { + ...props, + style: [ styles.button, props.style ] +}); + +const styles = StyleSheet.create({ + button: { + padding: 20 + } +}); +``` + +Remember that React Native styles are not the same as React DOM styles, and +care needs to be taken not to pass React DOM styles into your React Native +wrapped components. + +## Use as a library framework + +The React Native (for Web) building blocks can be used to create higher-level +components and abstractions. In the example below, a `styled` function provides +an API inspired by styled-components ([live +example](https://www.webpackbin.com/bins/-KjT9ziwv4O7FDZdvsnX)). + +```js +const { createDOMElement, StyleSheet } = ReactNative; + +/** + * styled API + */ +const styled = (Component, styler) => { + const isDOMComponent = typeof Component === 'string'; + + class Styled extends React.Component { + static contextTypes = { + getTheme: React.PropTypes.func + }; + + render() { + const theme = this.context.getTheme && this.context.getTheme(); + const localProps = { ...this.props, theme }; + const nextProps = { ...this.props } + const style = typeof styler === 'function' ? styler(localProps) : styler; + nextProps.style = [ style, this.props.style ]; + + return ( + isDOMComponent + ? createDOMElement(Component, nextProps) + : + ); + } + } + return Styled; +} + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + backgroundColor: '#2196F3', + flex: 1, + justifyContent: 'center' + } +}); + +const StyledView = styled(View, styles.container); +``` + +## Use with react-sketchapp + +Use with [react-sketchapp](http://airbnb.io/react-sketchapp/) requires that you +alias `react-native` to `react-sketchapp`. This will allow you to render your +existing React Native components in Sketch. Sketch-specific components like +`Artboard` should be imported from `react-sketchapp`. + +If you're using `skpm`, you can rely on an [undocumented +feature](https://github.com/sketch-pm/skpm/blob/master/lib/utils/webpackConfig.js) +which will merge your `webpack.config.js`, `.babelrc`, or `package.json` Babel +config into its internal webpack config. The simplest option may be to use the +[babel-plugin-module-alias](https://www.npmjs.com/package/babel-plugin-module-alias) +and configure it in your `package.json`. diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index 607259e6..d68d17c9 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -1,5 +1,14 @@ # Getting started +This guide will help you to correctly configure build and test tools to work +with React Native for Web. + +Alternatively, you can quickly setup a local project using +[create-react-app](https://github.com/facebookincubator/create-react-app) +(which supports `react-native-web` out-of-the-box once installed), +[react-native-web-starter](https://github.com/grabcode/react-native-web-starter), +or [react-native-web-webpack](https://github.com/ndbroadbent/react-native-web-webpack). + It is recommended that your application provide a `Promise` and `Array.from` polyfill. @@ -76,10 +85,6 @@ module.exports = { } ``` -A more complex example setup for web apps can be found in various starter kits -(e.g., create-react-app and -[react-native-web-webpack](https://github.com/ndbroadbent/react-native-web-webpack)) - Please refer to the Webpack documentation for more information. ## Jest @@ -144,8 +149,8 @@ AppRegistry.runApplication('App', { }) ``` -Rendering within `ReactDOM.render` also works when introduce `react-native-web` -to an existing web app, but it is not recommended oherwise. +Rendering within `ReactDOM.render` also works when introducing +`react-native-web` to an existing web app, but otherwise it is not recommended. ## Server-side rendering @@ -164,4 +169,15 @@ AppRegistry.registerComponent('App', () => AppContainer) // prerender the app const { element, stylesheet } = AppRegistry.getApplication('App', { initialProps }); const initialHTML = ReactDOMServer.renderToString(element); + +// construct HTML document +const document = ` + + + +${stylesheet} + + +${initialHTML} +` ``` diff --git a/docs/guides/style.md b/docs/guides/style.md index 7d486aea..8cc7c3c3 100644 --- a/docs/guides/style.md +++ b/docs/guides/style.md @@ -1,73 +1,90 @@ # Style -React Native for Web relies on JavaScript to define styles for your -application. This allows you to avoid issues arising from the [7 deadly sins of -CSS](https://speakerdeck.com/vjeux/react-css-in-js): +React Native relies on JavaScript to define and resolve the styles of your +application. React Native for Web implements the React Native style API in a +way that avoids *all* the [problems with CSS at +scale](https://speakerdeck.com/vjeux/react-css-in-js): -1. Global namespace -2. Dependency hell +1. No local variables +2. Implicit dependencies 3. No dead code elimination 4. No code minification 5. No sharing of constants 6. Non-deterministic resolution -7. Lack of isolation +7. No isolation + +At the same time, it has several benefits: + +1. Simple API and expressive subset of CSS +2. Generates CSS; the minimum required +3. Good runtime performance +4. Support for static and dynamic styles +5. Support for RTL layouts +6. Easy pre-rendering of critical CSS ## Defining styles -Styles should be defined outside of the component: +Styles should be defined outside of the component. Using `StyleSheet.create` is +optional but provides the best performance (by relying on generated CSS +stylesheets). Avoid creating unregistered style objects. ```js -class Example extends React.Component {} - const styles = StyleSheet.create({ heading: { color: 'gray', fontSize: '2rem' }, text: { - color: 'gray', - fontSize: '1.25rem' + marginTop: '1rem', + margin: 10 } }) ``` -Using `StyleSheet.create` is optional but provides the best performance -(`style` is resolved to CSS stylesheets). Avoid creating unregistered style -objects. - -The attribute names and values are a subset of CSS. See the `style` -documentation of individual components. +See the `style` documentation of individual components for supported properties. ## Using styles -All the React Native components accept a `style` attribute. +All the React Native components accept a `style` property. The value can be a +registered object, a plain object, or an array. ```js - +// registered object + +// plain object + + +// array of registered or plain objects + ``` -A common pattern is to conditionally add style based on a condition: +The array syntax will merge styles from left-to-right as normal JavaScript +objects, and can be used to conditionally apply styles: ```js -// either ``` +When styles are registered with `StyleSheet.create`, the return value is a +number and not a style object. This is important for performance optimizations, +but still allows you to merge styles in a deterministic manner at runtime. If +you need access to the underlying style objects you need to use +`StyleSheet.flatten` (but be aware that this is not the optimized path). + ## Composing styles -In order to let a call site customize the style of your component children, you -can pass styles around. Use `View.propTypes.style` and `Text.propTypes.style` in -order to make sure only valid styles are being passed. +To let other components customize the style of a component's children you can +expose a prop so styles can be explicitly passed into the component. ```js class List extends React.Component { static propTypes = { - style: View.propTypes.style, - elementStyle: View.propTypes.style, + style: ViewPropTypes.style, + elementStyle: ViewPropTypes.style, } render() { @@ -90,56 +107,125 @@ In another file: You also have much greater control over how styles are composed when compared to using class names. For example, you may choose to accept a limited subset -of style props in the component's API, and control when they are applied: +of style props in the component's API, and control when they are applied. -```js -class List extends React.Component { - static propTypes = { - children: React.PropTypes.any, - // limit which styles are accepted - style: React.PropTypes.shape({ - borderColor: View.propTypes.borderColor, - borderWidth: View.propTypes.borderWidth - }) - } +## How styles are resolved - render() { - return ( - - ) - } -} +React Native style resolution is deterministic and slightly different from CSS. + +In the following HTML/CSS example, the `.margin` selector is defined last in +the CSS and takes precedence over the previous rules, resulting in a margin of +`0, 0, 0, 0`. + +```html + +
``` -## Media Queries +But in React Native the most *specific* style property takes precedence, +resulting in margins of `10, 0, 20, 0`. + +```js +const style = [ + { marginTop: 10 }, + { marginBottom: 20 }, + { margin: 0 } +]; + +const Box = () => +``` + +## Implementation details + +React Native for Web transforms React Native styles into React DOM styles. Any +styles defined using `StyleSheet.create` will ultimately be rendered using CSS +class names. + +React Native for Web introduced a novel strategy to achieve this. Each rule is +broken down into declarations, properties are expanded to their long-form, and +the resulting key-value pairs are mapped to unique "atomic CSS" class names. + +Input: + +```js +const Box = () => + +const styles = StyleSheet.create({ + box: { + margin: 0 + } +}); +``` + +Output: + +```html + + +
+``` + +This ensures that CSS order doesn't impact rendering and CSS rules are +efficiently deduplicated. Rather than the total CSS growing in proportion to +the number of *rules*, it grows in proportion to the number of *unique +declarations*. As a result, the DOM style sheet is only written to when new +unique declarations are defined and it is usually small enough to be +pre-rendered and inlined. + +Class names are deterministic, which means that the resulting CSS and HTML is +consistent across builds – important for large apps using code-splitting and +deploying incremental updates. + +At runtime registered styles are resolved to DOM style props and memoized. +Any dynamic styles that contain declarations previously registered as static +styles can also be converted to CSS class names. Otherwise, they render as +inline styles. + +All this allows React Native for Web to support the rich functionality of React +Native styles (including RTL layouts and `setNativeProps`) while providing one +of the [fastest](https://github.com/necolas/react-native-web/blob/master/performance/README.md), +safest, and most efficient styles-in-JavaScript solutions. + +## FAQs + +### What about Media Queries? `StyleSheet.create` is a way of defining the styles your application requires; it does not concern itself with _where_ or _when_ those styles are applied to elements. -There are various React libraries wrapping JavaScript Media Query API's, e.g., +Media Queries may not be most appropriate for component-based designs. React +Native provides the `Dimensions` API and `onLayout` props. If you do need Media +Queries, using the `matchMedia` DOM API has the benefit of allowing you to swap +out entire components, not just styles. There are also many React libraries +wrapping JavaScript Media Query API's, e.g., +[react-media](https://github.com/reacttraining/react-media), [react-media-queries](https://github.com/bloodyowl/react-media-queries), [media-query-fascade](https://github.com/tanem/media-query-facade), or -[react-responsive](https://github.com/contra/react-responsive). This has the -benefit of co-locating breakpoint-specific DOM and style changes. +[react-responsive](https://github.com/contra/react-responsive). -## Pseudo-classes and pseudo-elements +### What about pseudo-classes and pseudo-elements? Pseudo-classes like `:hover` and `:focus` can be implemented with events (e.g. `onFocus`). Pseudo-elements are not supported; elements should be used instead. -### Reset +### Do I need a CSS reset? -You **do not** need to include a CSS reset or -[normalize.css](https://necolas.github.io/normalize.css/). +No. React Native for Web includes a very small CSS reset that removes unwanted +User Agent styles from (pseudo-)elements beyond the reach of React (e.g., +`html`, `body`) or inline styles (e.g., `::-moz-focus-inner`). The rest is +handled at the component-level. -React Native for Web includes a very small CSS reset taken from normalize.css. -It removes unwanted User Agent styles from (pseudo-)elements beyond the reach -of React (e.g., `html`, `body`) or inline styles (e.g., `::-moz-focus-inner`). +### What about using DevTools? + +It's recommended that you rely more on React DevTools and live/hot-reloading +rather than inspecting and editing the DOM directly. diff --git a/docs/static/components.png b/docs/static/components.png deleted file mode 100644 index 66da579a..00000000 Binary files a/docs/static/components.png and /dev/null differ diff --git a/docs/static/styling-strategy.png b/docs/static/styling-strategy.png deleted file mode 100644 index 850ef677..00000000 Binary files a/docs/static/styling-strategy.png and /dev/null differ