diff --git a/index.js b/index.js
new file mode 100644
index 00000000..2d2f3c0b
--- /dev/null
+++ b/index.js
@@ -0,0 +1 @@
+module.exports = require('./dist')
diff --git a/package.json b/package.json
index c1ff298d..a3e2692e 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,7 @@
"build": "babel src --out-dir dist",
"test": "ava",
"lint": "xo",
- "prepublishOnly": "yarn build && yarn test && yarn lint --quiet"
+ "prepublishOnly": "rm -rf dist && yarn build"
},
"husky": {
"hooks": {
diff --git a/readme.md b/readme.md
index cd5f80ff..524943e6 100644
--- a/readme.md
+++ b/readme.md
@@ -11,38 +11,37 @@ Code and docs are for v3 which we highly recommend you to try. Looking for style
- [Getting started](#getting-started)
- [Configuration options](#configuration-options)
- * [`optimizeForSpeed`](#optimizeforspeed)
- * [`sourceMaps`](#sourcemaps)
- * [`styleModule`](#stylemodule)
- * [`vendorPrefixes`](#vendorprefixes)
+ - [`optimizeForSpeed`](#optimizeforspeed)
+ - [`sourceMaps`](#sourcemaps)
+ - [`styleModule`](#stylemodule)
+ - [`vendorPrefixes`](#vendorprefixes)
- [Features](#features)
- [How It Works](#how-it-works)
- * [Why It Works Like This](#why-it-works-like-this)
+ - [Why It Works Like This](#why-it-works-like-this)
- [Targeting The Root](#targeting-the-root)
- [Global styles](#global-styles)
- * [One-off global selectors](#one-off-global-selectors)
+ - [One-off global selectors](#one-off-global-selectors)
- [Dynamic styles](#dynamic-styles)
- * [Via interpolated dynamic props](#via-interpolated-dynamic-props)
- * [Via `className` toggling](#via-classname-toggling)
- * [Via inline `style`](#via-inline-style)
+ - [Via interpolated dynamic props](#via-interpolated-dynamic-props)
+ - [Via `className` toggling](#via-classname-toggling)
+ - [Via inline `style`](#via-inline-style)
- [Constants](#constants)
- [Server-Side Rendering](#server-side-rendering)
- * [`styled-jsx/server`](#styled-jsxserver)
- [External CSS and styles outside of the component](#external-css-and-styles-outside-of-the-component)
- * [External styles](#external-styles)
- * [Styles outside of components](#styles-outside-of-components)
- * [The `resolve` tag](#the-resolve-tag)
- * [Styles in regular CSS files](#styles-in-regular-css-files)
+ - [External styles](#external-styles)
+ - [Styles outside of components](#styles-outside-of-components)
+ - [The `resolve` tag](#the-resolve-tag)
+ - [Styles in regular CSS files](#styles-in-regular-css-files)
- [CSS Preprocessing via Plugins](#css-preprocessing-via-plugins)
- * [Plugin options](#plugin-options)
- * [Example plugins](#example-plugins)
+ - [Plugin options](#plugin-options)
+ - [Example plugins](#example-plugins)
- [Rendering in tests](#rendering-in-tests)
- [FAQ](#faq)
- * [Warning: unknown `jsx` prop on <style> tag](#warning-unknown-jsx-prop-on-style-tag)
- * [Can I return an array of components when using React 16?](#can-i-return-an-array-of-components-when-using-react-16)
- * [Styling third parties / child components from the parent](#styling-third-parties--child-components-from-the-parent)
- * [Some styles are missing in production](https://github.com/zeit/styled-jsx/issues/319#issuecomment-349239326)
- * [Build a component library with styled-jsx](#build-a-component-library-with-styled-jsx)
+ - [Warning: unknown `jsx` prop on <style> tag](#warning-unknown-jsx-prop-on-style-tag)
+ - [Can I return an array of components when using React 16?](#can-i-return-an-array-of-components-when-using-react-16)
+ - [Styling third parties / child components from the parent](#styling-third-parties--child-components-from-the-parent)
+ - [Some styles are missing in production](https://github.com/zeit/styled-jsx/issues/319#issuecomment-349239326)
+ - [Build a component library with styled-jsx](#build-a-component-library-with-styled-jsx)
- [Syntax Highlighting](#syntax-highlighting)
## Getting started
@@ -57,9 +56,7 @@ Next, add `styled-jsx/babel` to `plugins` in your babel configuration:
```json
{
- "plugins": [
- "styled-jsx/babel"
- ]
+ "plugins": ["styled-jsx/babel"]
}
```
@@ -70,8 +67,8 @@ export default () => (
only this paragraph will get the style :)
- { /* you can include
s here that include
- other
s that don't get unexpected styles! */ }
+ {/* you can include s here that include
+ other
s that don't get unexpected styles! */}
@@ -198,7 +193,7 @@ the global styles being inserted multiple times.
Sometimes it's useful to skip selectors scoping. In order to get a one-off global selector we support `:global()`, inspired by [css-modules](https://github.com/css-modules/css-modules).
-This is very useful in order to, for example, generate a *global class* that
+This is very useful in order to, for example, generate a _global class_ that
you can pass to 3rd-party components. For example, to style
`react-select` which supports passing a custom class via `optionClassName`:
@@ -212,7 +207,7 @@ export default () => (
/* "div" will be prefixed, but ".react-select" won't */
div :global(.react-select) {
- color: red
+ color: red;
}
`}
@@ -228,18 +223,18 @@ To make a component's visual representation customizable from the outside world
Any value that comes from the component's `render` method scope is treated as dynamic. This makes it possible to use `props` and `state` for example.
```jsx
-const Button = (props) => (
+const Button = props => (
)
```
@@ -249,22 +244,22 @@ New styles' injection is optimized to perform well at runtime.
That said when your CSS is mostly static we recommend to split it up in static and dynamic styles and use two separate `style` tags so that, when changing, only the dynamic parts are recomputed/rendered.
```jsx
-const Button = (props) => (
+const Button = props => (
)
```
@@ -274,19 +269,19 @@ const Button = (props) => (
The second option is to pass properties that toggle class names.
```jsx
-const Button = (props) => (
-
)
```
### Emmet
- If you're using Emmet you can add the following snippet to `~/emmet/snippets-styledjsx.json` This will allow you to expand `style-jsx` to a styled-jsx block.
+If you're using Emmet you can add the following snippet to `~/emmet/snippets-styledjsx.json` This will allow you to expand `style-jsx` to a styled-jsx block.
- ```json
- {
+```json
+{
"html": {
"snippets": {
"style-jsx": ""
@@ -974,18 +980,23 @@ const Button = ({ children }) => (
```
### Syntax Highlighting [Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=Divlo.vscode-styled-jsx-syntax)
+
Launch VS Code Quick Open (⌘+P), paste the following command, and press enter.
+
```
ext install Divlo.vscode-styled-jsx-syntax
```
If you use Stylus instead of plain CSS, install [vscode-styled-jsx-stylus](https://marketplace.visualstudio.com/items?itemName=samuelroy.vscode-styled-jsx-stylus) or paste the command below.
+
```
ext install vscode-styled-jsx-stylus
```
### Autocomplete [Visual Studio Code Extension](https://marketplace.visualstudio.com/items?itemName=Divlo.vscode-styled-jsx-languageserver)
+
Launch VS Code Quick Open (⌘+P), paste the following command, and press enter.
+
```
ext install Divlo.vscode-styled-jsx-languageserver
```
@@ -995,6 +1006,7 @@ ext install Divlo.vscode-styled-jsx-languageserver
Install [vim-styled-jsx](https://github.com/alampros/vim-styled-jsx) with your plugin manager of choice.
## ESLint
+
If you're using `eslint-plugin-import`, the `css` import will generate errors, being that it's a "magic" import (not listed in package.json). To avoid these, simply add the following line to your eslint configuration:
```
diff --git a/server.js b/server.js
deleted file mode 100644
index 3b4b9b0e..00000000
--- a/server.js
+++ /dev/null
@@ -1 +0,0 @@
-module.exports = require('./dist/server')
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 00000000..cb4fe737
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1 @@
+export { StyleRegistry, useStyleRegistry } from './stylesheet-registry'
diff --git a/src/server.js b/src/server.js
deleted file mode 100644
index 19e833f2..00000000
--- a/src/server.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react'
-import { flush } from './style'
-
-export default function flushToReact(options = {}) {
- return flush().map(args => {
- const id = args[0]
- const css = args[1]
- return React.createElement('style', {
- id: `__${id}`,
- // Avoid warnings upon render with a key
- key: `__${id}`,
- nonce: options.nonce ? options.nonce : undefined,
- dangerouslySetInnerHTML: {
- __html: css
- }
- })
- })
-}
-
-export function flushToHTML(options = {}) {
- return flush().reduce((html, args) => {
- const id = args[0]
- const css = args[1]
- html += ``
- return html
- }, '')
-}
diff --git a/src/style.js b/src/style.js
index 3a08f433..2d0c6bc2 100644
--- a/src/style.js
+++ b/src/style.js
@@ -1,17 +1,16 @@
-import { useLayoutEffect } from 'react'
-import StyleSheetRegistry from './stylesheet-registry'
-
-const styleSheetRegistry = new StyleSheetRegistry()
+import { useLayoutEffect, useContext } from 'react'
+import { StyleSheetContext } from './stylesheet-registry'
export default function JSXStyle(props) {
+ const registry = useContext(StyleSheetContext)
if (typeof window === 'undefined') {
- styleSheetRegistry.add(props)
+ registry.add(props)
return null
}
useLayoutEffect(() => {
- styleSheetRegistry.add(props)
+ registry.add(props)
return () => {
- styleSheetRegistry.remove(props)
+ registry.remove(props)
}
// props.children can be string[], will be striped since id is identical
}, [props.id, String(props.dynamic)])
@@ -19,17 +18,12 @@ export default function JSXStyle(props) {
}
JSXStyle.dynamic = info => {
+ const registry = useContext(StyleSheetContext)
return info
.map(tagInfo => {
const baseId = tagInfo[0]
const props = tagInfo[1]
- return styleSheetRegistry.computeId(baseId, props)
+ return registry.computeId(baseId, props)
})
.join(' ')
}
-
-export function flush() {
- const cssRules = styleSheetRegistry.cssRules()
- styleSheetRegistry.flush()
- return cssRules
-}
diff --git a/src/stylesheet-registry.js b/src/stylesheet-registry.js
index a3e2593f..cbbaff97 100644
--- a/src/stylesheet-registry.js
+++ b/src/stylesheet-registry.js
@@ -1,8 +1,10 @@
+import React, { useState, useContext, createContext, useMemo } from 'react'
import hashString from 'string-hash'
import DefaultStyleSheet from './lib/stylesheet'
const sanitize = rule => rule.replace(/\/style/gi, '\\/style')
-export default class StyleSheetRegistry {
+
+export class StyleSheetRegistry {
constructor({
styleSheet = null,
optimizeForSpeed = false,
@@ -217,3 +219,50 @@ function invariant(condition, message) {
throw new Error(`StyleSheetRegistry: ${message}.`)
}
}
+
+export const StyleSheetContext = createContext(new StyleSheetRegistry())
+
+export function StyleRegistry({ children }) {
+ const rootRegistry = useContext(StyleSheetContext)
+ const registry = useState(() => rootRegistry || new StyleSheetRegistry())
+
+ return React.createElement(
+ StyleSheetContext.Provider,
+ { value: registry },
+ children
+ )
+}
+
+export function useStyleRegistry() {
+ const registry = useContext(StyleSheetContext)
+
+ return useMemo(
+ () => ({
+ styles() {
+ return mapRulesToStyle(registry.cssRules())
+ },
+ flush() {
+ registry.flush()
+ }
+ }),
+ [registry]
+ )
+}
+
+function mapRulesToStyle(options = {}) {
+ const registry = useStyleRegistry()
+ const cssRules = registry.styles()
+ return cssRules.map(args => {
+ const id = args[0]
+ const css = args[1]
+ return React.createElement('style', {
+ id: `__${id}`,
+ // Avoid warnings upon render with a key
+ key: `__${id}`,
+ nonce: options.nonce ? options.nonce : undefined,
+ dangerouslySetInnerHTML: {
+ __html: css
+ }
+ })
+ })
+}
diff --git a/test/index.js b/test/index.js
index d0dfc89c..06969e07 100644
--- a/test/index.js
+++ b/test/index.js
@@ -5,8 +5,44 @@ import ReactDOM from 'react-dom/server'
// Ours
import plugin from '../src/babel'
+import JSXStyle from '../src/style'
+import {
+ StyleSheetRegistry,
+ StyleSheetContext
+} from '../src/stylesheet-registry'
import _transform, { transformSource as _transformSource } from './_transform'
+const flushToHTML = (registry, options = {}) => {
+ const cssRules = registry.cssRules()
+ registry.flush()
+ return cssRules.reduce((html, args) => {
+ const id = args[0]
+ const css = args[1]
+ html += ``
+ return html
+ }, '')
+}
+
+function flushToReact(registry, options = {}) {
+ const cssRules = registry.cssRules()
+ registry.flush()
+ return cssRules.map(args => {
+ const id = args[0]
+ const css = args[1]
+ return React.createElement('style', {
+ id: `__${id}`,
+ // Avoid warnings upon render with a key
+ key: `__${id}`,
+ nonce: options.nonce ? options.nonce : undefined,
+ dangerouslySetInnerHTML: {
+ __html: css
+ }
+ })
+ })
+}
+
const transform = (file, opts = {}) =>
_transform(file, {
plugins: [plugin],
@@ -143,18 +179,39 @@ test('works with exported non-jsx style (CommonJS modules)', async t => {
t.snapshot(code)
})
-function clearModulesCache() {
- ;['../src/lib/stylesheet', '../src/style', '../src/server'].forEach(
- moduleName => {
- delete require.cache[require.resolve(moduleName)]
- }
+test('sever rendering with hook api', t => {
+ function App() {
+ const color = 'green'
+ return React.createElement(
+ 'div',
+ null,
+ React.createElement(JSXStyle, { id: 2 }, 'div { color: blue }'),
+ React.createElement(JSXStyle, { id: 3 }, `div { color: ${color} }`)
+ )
+ }
+
+ // Expected CSS
+ const expected =
+ '' +
+ ''
+
+ const registry = new StyleSheetRegistry()
+ const createApp = () =>
+ React.createElement(
+ StyleSheetContext.Provider,
+ { value: registry },
+ React.createElement(App)
+ )
+
+ // Render using react
+ ReactDOM.renderToString(createApp())
+ const html = ReactDOM.renderToStaticMarkup(
+ React.createElement('head', null, flushToReact(registry))
)
-}
+ t.is(html, `${expected}`)
+})
test('server rendering', t => {
- clearModulesCache()
- const JSXStyle = require('../src/style').default
- const { default: flush, flushToHTML } = require('../src/server')
function App() {
const color = 'green'
return React.createElement(
@@ -190,31 +247,36 @@ test('server rendering', t => {
'' +
''
+ const registry = new StyleSheetRegistry()
+ const createApp = () =>
+ React.createElement(
+ StyleSheetContext.Provider,
+ { value: registry },
+ React.createElement(App)
+ )
+
// Render using react
- ReactDOM.renderToString(React.createElement(App))
+ ReactDOM.renderToString(createApp())
const html = ReactDOM.renderToStaticMarkup(
- React.createElement('head', null, flush())
+ React.createElement('head', null, flushToReact(registry))
)
t.is(html, `${expected}`)
// Assert that memory is empty
- t.is(0, flush().length)
- t.is('', flushToHTML())
+ t.is(0, registry.cssRules().length)
+ t.is('', flushToHTML(registry))
// Render to html again
- ReactDOM.renderToString(React.createElement(App))
- t.is(expected, flushToHTML())
+ ReactDOM.renderToString(createApp())
+ t.is(expected, flushToHTML(registry))
// Assert that memory is empty
- t.is(0, flush().length)
- t.is('', flushToHTML())
+ t.is(0, flushToReact(registry).length)
+ t.is('', flushToHTML(registry))
})
test('server rendering with nonce', t => {
- clearModulesCache()
- const JSXStyle = require('../src/style').default
- const { default: flush, flushToHTML } = require('../src/server')
function App() {
const color = 'green'
return React.createElement(
@@ -244,6 +306,14 @@ test('server rendering with nonce', t => {
)
}
+ const registry = new StyleSheetRegistry()
+ const createApp = () =>
+ React.createElement(
+ StyleSheetContext.Provider,
+ { value: registry },
+ React.createElement(App)
+ )
+
// Expected CSS
const expected =
'' +
@@ -251,30 +321,31 @@ test('server rendering with nonce', t => {
''
// Render using react
- ReactDOM.renderToString(React.createElement(App))
+ ReactDOM.renderToString(createApp())
const html = ReactDOM.renderToStaticMarkup(
- React.createElement('head', null, flush({ nonce: 'test-nonce' }))
+ React.createElement(
+ 'head',
+ null,
+ flushToReact(registry, { nonce: 'test-nonce' })
+ )
)
t.is(html, `${expected}`)
// Assert that memory is empty
- t.is(0, flush({ nonce: 'test-nonce' }).length)
- t.is('', flushToHTML({ nonce: 'test-nonce' }))
+ t.is(0, flushToReact(registry, { nonce: 'test-nonce' }).length)
+ t.is('', flushToHTML(registry, { nonce: 'test-nonce' }))
// Render to html again
- ReactDOM.renderToString(React.createElement(App))
- t.is(expected, flushToHTML({ nonce: 'test-nonce' }))
+ ReactDOM.renderToString(createApp())
+ t.is(expected, flushToHTML(registry, { nonce: 'test-nonce' }))
// Assert that memory is empty
- t.is(0, flush({ nonce: 'test-nonce' }).length)
- t.is('', flushToHTML({ nonce: 'test-nonce' }))
+ t.is(0, flushToReact(registry, { nonce: 'test-nonce' }).length)
+ t.is('', flushToHTML(registry, { nonce: 'test-nonce' }))
})
test('optimized styles do not contain new lines', t => {
- clearModulesCache()
- const JSXStyle = require('../src/style').default
- const { default: flush } = require('../src/server')
function App() {
return React.createElement(
'div',
@@ -289,9 +360,17 @@ test('optimized styles do not contain new lines', t => {
)
}
- ReactDOM.renderToString(React.createElement(App))
+ const registry = new StyleSheetRegistry()
+ const createApp = () =>
+ React.createElement(
+ StyleSheetContext.Provider,
+ { value: registry },
+ React.createElement(App)
+ )
+
+ ReactDOM.renderToString(createApp())
const html = ReactDOM.renderToStaticMarkup(
- React.createElement('head', null, flush())
+ React.createElement('head', null, flushToReact(registry))
)
const expected =
''
diff --git a/test/snapshots/attribute.js.snap b/test/snapshots/attribute.js.snap
index 61300916..b6a89e61 100644
Binary files a/test/snapshots/attribute.js.snap and b/test/snapshots/attribute.js.snap differ
diff --git a/test/snapshots/external.js.snap b/test/snapshots/external.js.snap
index 83c58757..4ef41ea0 100644
Binary files a/test/snapshots/external.js.snap and b/test/snapshots/external.js.snap differ
diff --git a/test/snapshots/index.js.snap b/test/snapshots/index.js.snap
index ff80a432..aa6e036c 100644
Binary files a/test/snapshots/index.js.snap and b/test/snapshots/index.js.snap differ
diff --git a/test/snapshots/plugins.js.snap b/test/snapshots/plugins.js.snap
index 0ecb63e0..3ddf9d22 100644
Binary files a/test/snapshots/plugins.js.snap and b/test/snapshots/plugins.js.snap differ
diff --git a/test/stylesheet-registry.js b/test/stylesheet-registry.js
index 12c06840..2dca45b7 100644
--- a/test/stylesheet-registry.js
+++ b/test/stylesheet-registry.js
@@ -2,7 +2,7 @@
import test from 'ava'
// Ours
-import StyleSheetRegistry from '../src/stylesheet-registry'
+import { StyleSheetRegistry } from '../src/stylesheet-registry'
import makeSheet, { invalidRules } from './stylesheet'
import withMock, { withMockDocument } from './helpers/with-mock'