Skip to content

Commit

Permalink
[change] StyleSheet: news APIs and refactor
Browse files Browse the repository at this point in the history
This fixes several issues with 'StyleSheet' and simplifies the
implementation.

1. The generated style sheet could render after an apps existing style
sheets, potentially overwriting certain 'html' and 'body' styles. To fix
this, the style sheet is now rendered first in the document head.

2. 'StyleSheet' didn't make it easy to render app shells on the server.
The prerendered style sheet would contain classnames that didn't apply
to the client-generated style sheet (in part because the class names
were not generated as a hash of the declaration). When the client
initialized, server-rendered parts of the page could become unstyled. To
fix this 'StyleSheet' uses inline styles by default and a few predefined
CSS rules where inline styles are not possible.

3. Even with the strategy of mapping declarations to unique CSS rules,
very large apps can produce very large style sheets. For example,
twitter.com would produce a gzipped style sheet ~30 KB. Issues related
to this are also alleviated by using inline styles.

4. 'StyleSheet' didn't really work unless you rendered an app using
'AppRegistry'. To fix this, 'StyleSheet' now handles injection of the
DOM style sheet.

Using inline styles doesn't appear to have any serious performance
problems compared to using single classes (ref #110).

Fix #90
Fix #106
  • Loading branch information
necolas committed Jul 11, 2016
1 parent 2168854 commit 77f72aa
Show file tree
Hide file tree
Showing 26 changed files with 326 additions and 398 deletions.
65 changes: 37 additions & 28 deletions docs/apis/StyleSheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,55 +15,64 @@ 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
**render**: function

Returns a string of CSS used to style the application.
Returns a React `<style>` element for use in server-side rendering.

## Properties

**hairlineWidth**: number
**absoluteFill**: number

## Example
A very common pattern is to create overlays with position absolute and zero positioning,
so `absoluteFill` can be used for convenience and to reduce duplication of these repeated
styles.

```js
<View style={StyleSheet.absoluteFill} />
```

**absoluteFillObject**: object

Sometimes you may want `absoluteFill` but with a couple tweaks - `absoluteFillObject` can be
used to create a customized entry in a `StyleSheet`, e.g.:

```js
const styles = StyleSheet.create({
container: {
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#d6d7da',
},
title: {
fontSize: 19,
fontWeight: 'bold',
},
activeTitle: {
color: 'red',
wrapper: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'transparent',
top: 10
}
})
```

Use styles:
**hairlineWidth**: number

## Example

```js
<View style={styles.container}>
<Text
children={'Title text'}
style={[
styles.title,
this.props.isActive && styles.activeTitle
]}
/>
</View>
```

Or:

```js
<View style={styles.container}>
<Text
style={{
...styles.title,
...(this.props.isActive && styles.activeTitle)
}}
/>
</View>
const styles = StyleSheet.create({
container: {
borderRadius: 4,
borderWidth: 0.5,
borderColor: '#d6d7da',
},
title: {
fontSize: 19,
fontWeight: 'bold',
},
activeTitle: {
color: 'red',
}
})
```
2 changes: 1 addition & 1 deletion docs/guides/rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,5 @@ AppRegistry.runApplication('App', {
})

// prerender the app
const { html, style, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
const { html, styleElement } = AppRegistry.prerenderApplication('App', { initialProps })
```
28 changes: 14 additions & 14 deletions examples/components/GridView.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,24 @@ export default class GridView extends Component {
render() {
const { alley, children, gutter, style, ...other } = this.props

const rootStyle = {
...style,
...styles.root
}
const rootStyle = [
style,
styles.root
]

const contentContainerStyle = {
...styles.contentContainer,
marginHorizontal: `calc(-0.5 * ${alley})`,
paddingHorizontal: `${gutter}`
}
const contentContainerStyle = [
styles.contentContainer,
{ marginHorizontal: `calc(-0.5 * ${alley})` },
{ paddingHorizontal: `${gutter}` }
]

const newChildren = React.Children.map(children, (child) => {
return child && React.cloneElement(child, {
style: {
...child.props.style,
...styles.column,
marginHorizontal: `calc(0.5 * ${alley})`
}
style: [
child.props.style,
styles.column,
{ marginHorizontal: `calc(0.5 * ${alley})` }
]
})
})

Expand Down
2 changes: 1 addition & 1 deletion examples/components/Heading.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const Heading = ({ children, size = 'normal' }) => (
<Text
accessibilityRole='heading'
children={children}
style={{ ...styles.root, ...sizeStyles[size] }}
style={[ styles.root, sizeStyles[size] ]}

This comment has been minimized.

Copy link
@MoOx

MoOx Jul 11, 2016

Contributor

@necolas any reason why you prefer array to spread (since at the end, it's sort of the same)?

This comment has been minimized.

Copy link
@necolas

necolas Jul 11, 2016

Author Owner

React Native's StyleSheet.create returns objects like this { root: 2 }, so styles.root is actually 2.

This comment has been minimized.

Copy link
@MoOx

MoOx Jul 11, 2016

Contributor

Oh. Thanks for the clarification!

/>
)

Expand Down
10 changes: 5 additions & 5 deletions examples/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { AppRegistry } from 'react-native'
import React from 'react'
import ReactNative, { AppRegistry } from 'react-native'
import App from './components/App'
import Game2048 from './2048/Game2048'
import TicTacToeApp from './TicTacToe/TicTacToe'

const rootTag = document.getElementById('react-root')
AppRegistry.registerComponent('App', () => App)

AppRegistry.runApplication('App', {
rootTag: document.getElementById('react-root')
})
AppRegistry.runApplication('App', { rootTag })
// ReactNative.render(<App />, rootTag)
8 changes: 2 additions & 6 deletions src/apis/AppRegistry/__tests__/renderApplication-test.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
/* eslint-env mocha */

import assert from 'assert'
import React from 'react'
import { elementId } from '../../StyleSheet'
import { prerenderApplication } from '../renderApplication'
import React from 'react'

const component = () => <div />

suite('apis/AppRegistry/renderApplication', () => {
test('prerenderApplication', () => {
const { html, style, styleElement } = prerenderApplication(component, {})
const { html, styleElement } = prerenderApplication(component, {})

assert.ok(html.indexOf('<div ') > -1)
assert.ok(typeof style === 'string')
assert.equal(styleElement.type, 'style')
assert.equal(styleElement.props.id, elementId)
assert.equal(styleElement.props.dangerouslySetInnerHTML.__html, style)
})
})
13 changes: 2 additions & 11 deletions src/apis/AppRegistry/renderApplication.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,9 @@ import ReactDOMServer from 'react-dom/server'
import ReactNativeApp from './ReactNativeApp'
import StyleSheet from '../../apis/StyleSheet'

const renderStyleSheetToString = () => StyleSheet.renderToString()
const styleAsElement = (style) => <style dangerouslySetInnerHTML={{ __html: style }} id={StyleSheet.elementId} />
const styleAsTagString = (style) => `<style id="${StyleSheet.elementId}">${style}</style>`

export default function renderApplication(RootComponent: Component, initialProps: Object, rootTag: any) {
invariant(rootTag, 'Expect to have a valid rootTag, instead got ', rootTag)

// insert style sheet if needed
const styleElement = document.getElementById(StyleSheet.elementId)
if (!styleElement) { rootTag.insertAdjacentHTML('beforebegin', styleAsTagString(renderStyleSheetToString())) }

const component = (
<ReactNativeApp
initialProps={initialProps}
Expand All @@ -42,7 +34,6 @@ export function prerenderApplication(RootComponent: Component, initialProps: Obj
/>
)
const html = ReactDOMServer.renderToString(component)
const style = renderStyleSheetToString()
const styleElement = styleAsElement(style)
return { html, style, styleElement }
const styleElement = StyleSheet.render()
return { html, styleElement }
}
113 changes: 0 additions & 113 deletions src/apis/StyleSheet/StyleSheetRegistry.js

This file was deleted.

55 changes: 0 additions & 55 deletions src/apis/StyleSheet/__tests__/StyleSheetRegistry-test.js

This file was deleted.

13 changes: 13 additions & 0 deletions src/apis/StyleSheet/__tests__/createReactStyleObject-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* eslint-env mocha */

import assert from 'assert'
import createReactStyleObject from '../createReactStyleObject'

suite('apis/StyleSheet/createReactStyleObject', () => {
test('converts ReactNative style to ReactDOM style', () => {
const reactNativeStyle = { display: 'flex', marginVertical: 0, opacity: 0 }
const expectedStyle = { display: 'flex', marginTop: '0px', marginBottom: '0px', opacity: 0 }

assert.deepEqual(createReactStyleObject(reactNativeStyle), expectedStyle)
})
})
Loading

0 comments on commit 77f72aa

Please sign in to comment.