[change] StyleSheet: support code-splitting / export smaller API

Quick-fix for code-splitting support by updating the rendered style
sheet in place. Reduce the API to `create`, as the rest is now internal
to the framework.

Fix #34
This commit is contained in:
Nicolas Gallagher
2015-12-27 11:37:05 +00:00
parent 69166b1502
commit 8ac84f6da5
7 changed files with 96 additions and 60 deletions
+2 -1
View File
@@ -110,7 +110,8 @@ const Html = () => (
)
```
Rendering on the client automatically includes your app styles:
Rendering on the client automatically includes your app styles and supports
progressive app loading (i.e. code-splitting / lazy bundle loading):
```js
// client.js
+17 -24
View File
@@ -37,24 +37,10 @@ Use styles:
</View>
```
Render styles on the server or in the browser:
```js
StyleSheet.renderToString()
```
## Methods
**create**(obj: {[key: string]: any})
**destroy**()
Clears all style information.
**renderToString**()
Renders a CSS Style Sheet.
## About
### Strategy
@@ -71,9 +57,9 @@ CSS](https://speakerdeck.com/vjeux/react-css-in-js):
6. Non-deterministic resolution
7. Breaking isolation
The strategy also minimizes the amount of generated CSS, making it more viable
to inline the style sheet when pre-rendering pages on the server. There is one
unique selector per unique style _declaration_.
The strategy minimizes the amount of generated CSS, making it viable to inline
the style sheet when pre-rendering pages on the server. There is one unique
selector per unique style _declaration_.
```js
// definition
@@ -88,7 +74,7 @@ unique selector per unique style _declaration_.
}
}
// css
// css output
//
// .a { color: gray; }
// .b { font-size: 2rem; }
@@ -130,16 +116,17 @@ In production the class names are obfuscated.
(CSS libraries like [Atomic CSS](http://acss.io/),
[Basscss](http://www.basscss.com/), [SUIT CSS](https://suitcss.github.io/), and
[tachyons](http://tachyons.io/) are attempts to limit style scope and limit
style sheet growth in a similar way. But they're CSS utility libraries, each with a
particular set of classes and features to learn. All of them require developers
to manually connect CSS classes for given styles.)
style sheet growth in a similar way. But they're CSS utility libraries, each
with a particular set of classes and features to learn. And all of them require
developers to manually connect CSS classes for given styles.)
### Reset
React Native for Web includes a very small CSS reset taken from
[normalize.css](https://necolas.github.io/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`).
[normalize.css](https://necolas.github.io/normalize.css/) **you do not need
to include 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`).
```css
html {
@@ -162,4 +149,10 @@ input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
ol,
ul,
li {
list-style:none
}
```
+1 -1
View File
@@ -7,7 +7,7 @@
"dist"
],
"scripts": {
"build": "rm -rf ./dist && mkdir dist && babel src -d dist --ignore src/**/__tests__,src/modules/specHelpers",
"build": "rm -rf ./dist && mkdir dist && babel src -d dist --ignore **/__tests__,src/modules/specHelpers",
"build:umd": "webpack --config config/webpack.config.js --sort-assets-by --progress",
"examples": "webpack-dev-server --config config/webpack.config.example.js --inline --hot --colors --quiet",
"lint": "eslint config examples src",
-1
View File
@@ -1,6 +1,5 @@
/* eslint-env mocha */
import * as utils from '../modules/specHelpers'
import assert from 'assert'
import React from '..'
+1 -1
View File
@@ -15,7 +15,7 @@ import Touchable from './components/Touchable'
import View from './components/View'
const renderStyle = () => {
return `<style id='react-stylesheet'>${StyleSheet.renderToString()}</style>`
return `<style id='react-stylesheet'>${StyleSheet._renderToString()}</style>`
}
const render = (element, container, callback) => {
+44 -15
View File
@@ -8,24 +8,40 @@ const styles = { root: { borderWidth: 1 } }
suite('modules/StyleSheet', () => {
setup(() => {
StyleSheet.destroy()
StyleSheet._destroy()
})
test('create', () => {
assert.equal(StyleSheet.create(styles), styles)
})
suite('create', () => {
const div = document.createElement('div')
test('renderToString', () => {
StyleSheet.create(styles)
assert.equal(
StyleSheet.renderToString(),
`${resetCSS}\n${predefinedCSS}\n` +
`/* 4 unique declarations */\n` +
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
`.borderTopWidth\\:1px{border-top-width:1px;}`
)
setup(() => {
document.body.appendChild(div)
StyleSheet.create(styles)
div.innerHTML = `<style id='react-stylesheet'>${StyleSheet._renderToString()}</style>`
})
teardown(() => {
document.body.removeChild(div)
})
test('returns styles object', () => {
assert.equal(StyleSheet.create(styles), styles)
})
test('updates already-rendered style sheet', () => {
StyleSheet.create({ root: { color: 'red' } })
assert.equal(
document.getElementById('react-stylesheet').textContent,
`${resetCSS}\n${predefinedCSS}\n` +
`/* 5 unique declarations */\n` +
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
`.borderTopWidth\\:1px{border-top-width:1px;}\n` +
`.color\\:red{color:red;}`
)
})
})
test('resolve', () => {
@@ -34,4 +50,17 @@ suite('modules/StyleSheet', () => {
StyleSheet.create(styles)
assert.deepEqual(StyleSheet.resolve(props), expected)
})
test('_renderToString', () => {
StyleSheet.create(styles)
assert.equal(
StyleSheet._renderToString(),
`${resetCSS}\n${predefinedCSS}\n` +
`/* 4 unique declarations */\n` +
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` +
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` +
`.borderRightWidth\\:1px{border-right-width:1px;}\n` +
`.borderTopWidth\\:1px{border-top-width:1px;}`
)
})
})
+31 -17
View File
@@ -13,6 +13,24 @@ const initialState = { classNames: predefinedClassNames }
const options = { obfuscateClassNames: process.env.NODE_ENV === 'production' }
const createStore = () => new Store(initialState, options)
let store = createStore()
let isRendered = false
/**
* Destroy existing styles
*/
const _destroy = () => {
store = createStore()
isRendered = false
}
/**
* Render the styles as a CSS style sheet
*/
const _renderToString = () => {
const css = store.toString()
isRendered = true
return `${resetCSS}\n${predefinedCSS}\n${css}`
}
/**
* Process all unique declarations
@@ -33,24 +51,20 @@ const create = (styles: Object): Object => {
}
})
})
// update the style sheet in place
if (isRendered) {
const stylesheet = document.getElementById('react-stylesheet')
if (stylesheet) {
stylesheet.textContent = _renderToString()
} else if (process.env.NODE_ENV !== 'production') {
console.error('ReactNativeWeb: cannot find "react-stylesheet" element')
}
}
return styles
}
/**
* Destroy existing styles
*/
const destroy = () => {
store = createStore()
}
/**
* Render the styles as a CSS style sheet
*/
const renderToString = () => {
const css = store.toString()
return `${resetCSS}\n${predefinedCSS}\n${css}`
}
/**
* Accepts React props and converts inline styles to single purpose classes
* where possible.
@@ -81,8 +95,8 @@ const resolve = ({ className = '', style = {} }) => {
}
export default {
_destroy,
_renderToString,
create,
destroy,
renderToString,
resolve
}