[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 ```js
// client.js // client.js
+17 -24
View File
@@ -37,24 +37,10 @@ Use styles:
</View> </View>
``` ```
Render styles on the server or in the browser:
```js
StyleSheet.renderToString()
```
## Methods ## Methods
**create**(obj: {[key: string]: any}) **create**(obj: {[key: string]: any})
**destroy**()
Clears all style information.
**renderToString**()
Renders a CSS Style Sheet.
## About ## About
### Strategy ### Strategy
@@ -71,9 +57,9 @@ CSS](https://speakerdeck.com/vjeux/react-css-in-js):
6. Non-deterministic resolution 6. Non-deterministic resolution
7. Breaking isolation 7. Breaking isolation
The strategy also minimizes the amount of generated CSS, making it more viable The strategy minimizes the amount of generated CSS, making it viable to inline
to inline the style sheet when pre-rendering pages on the server. There is one the style sheet when pre-rendering pages on the server. There is one unique
unique selector per unique style _declaration_. selector per unique style _declaration_.
```js ```js
// definition // definition
@@ -88,7 +74,7 @@ unique selector per unique style _declaration_.
} }
} }
// css // css output
// //
// .a { color: gray; } // .a { color: gray; }
// .b { font-size: 2rem; } // .b { font-size: 2rem; }
@@ -130,16 +116,17 @@ In production the class names are obfuscated.
(CSS libraries like [Atomic CSS](http://acss.io/), (CSS libraries like [Atomic CSS](http://acss.io/),
[Basscss](http://www.basscss.com/), [SUIT CSS](https://suitcss.github.io/), and [Basscss](http://www.basscss.com/), [SUIT CSS](https://suitcss.github.io/), and
[tachyons](http://tachyons.io/) are attempts to limit style scope and limit [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 style sheet growth in a similar way. But they're CSS utility libraries, each
particular set of classes and features to learn. All of them require developers with a particular set of classes and features to learn. And all of them require
to manually connect CSS classes for given styles.) developers to manually connect CSS classes for given styles.)
### Reset ### Reset
React Native for Web includes a very small CSS reset taken from React Native for Web includes a very small CSS reset taken from
[normalize.css](https://necolas.github.io/normalize.css/). It removes unwanted [normalize.css](https://necolas.github.io/normalize.css/) **you do not need
User Agent styles from (pseudo-)elements beyond the reach of React (e.g., to include normalize.css**. It removes unwanted User Agent styles from
`html`, `body`) or inline styles (e.g., `::-moz-focus-inner`). (pseudo-)elements beyond the reach of React (e.g., `html`, `body`) or inline
styles (e.g., `::-moz-focus-inner`).
```css ```css
html { html {
@@ -162,4 +149,10 @@ input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration { input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none; -webkit-appearance: none;
} }
ol,
ul,
li {
list-style:none
}
``` ```
+1 -1
View File
@@ -7,7 +7,7 @@
"dist" "dist"
], ],
"scripts": { "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", "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", "examples": "webpack-dev-server --config config/webpack.config.example.js --inline --hot --colors --quiet",
"lint": "eslint config examples src", "lint": "eslint config examples src",
-1
View File
@@ -1,6 +1,5 @@
/* eslint-env mocha */ /* eslint-env mocha */
import * as utils from '../modules/specHelpers'
import assert from 'assert' import assert from 'assert'
import React from '..' import React from '..'
+1 -1
View File
@@ -15,7 +15,7 @@ import Touchable from './components/Touchable'
import View from './components/View' import View from './components/View'
const renderStyle = () => { const renderStyle = () => {
return `<style id='react-stylesheet'>${StyleSheet.renderToString()}</style>` return `<style id='react-stylesheet'>${StyleSheet._renderToString()}</style>`
} }
const render = (element, container, callback) => { const render = (element, container, callback) => {
+44 -15
View File
@@ -8,24 +8,40 @@ const styles = { root: { borderWidth: 1 } }
suite('modules/StyleSheet', () => { suite('modules/StyleSheet', () => {
setup(() => { setup(() => {
StyleSheet.destroy() StyleSheet._destroy()
}) })
test('create', () => { suite('create', () => {
assert.equal(StyleSheet.create(styles), styles) const div = document.createElement('div')
})
test('renderToString', () => { setup(() => {
StyleSheet.create(styles) document.body.appendChild(div)
assert.equal( StyleSheet.create(styles)
StyleSheet.renderToString(), div.innerHTML = `<style id='react-stylesheet'>${StyleSheet._renderToString()}</style>`
`${resetCSS}\n${predefinedCSS}\n` + })
`/* 4 unique declarations */\n` +
`.borderBottomWidth\\:1px{border-bottom-width:1px;}\n` + teardown(() => {
`.borderLeftWidth\\:1px{border-left-width:1px;}\n` + document.body.removeChild(div)
`.borderRightWidth\\:1px{border-right-width:1px;}\n` + })
`.borderTopWidth\\:1px{border-top-width:1px;}`
) 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', () => { test('resolve', () => {
@@ -34,4 +50,17 @@ suite('modules/StyleSheet', () => {
StyleSheet.create(styles) StyleSheet.create(styles)
assert.deepEqual(StyleSheet.resolve(props), expected) 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 options = { obfuscateClassNames: process.env.NODE_ENV === 'production' }
const createStore = () => new Store(initialState, options) const createStore = () => new Store(initialState, options)
let store = createStore() 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 * 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 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 * Accepts React props and converts inline styles to single purpose classes
* where possible. * where possible.
@@ -81,8 +95,8 @@ const resolve = ({ className = '', style = {} }) => {
} }
export default { export default {
_destroy,
_renderToString,
create, create,
destroy,
renderToString,
resolve resolve
} }