mirror of
https://github.com/zhigang1992/styled-components.git
synced 2026-01-12 22:52:39 +08:00
Add component-based global styling API (#1867)
* Initial work to add createGlobalStyle functionality * Delete global styles when component returned from createGlobalStyle is unmounted * Check context for stylesheet instance * Adjust the size of the browser style sheet after removing a textNode * Add initial implementation of removeComponent to ServerStyleSheet * Tidy up code as part of createGlobalStyle addition * Added printWidth to .prettierrc * Add createGlobalStyle information to CHANGELOG * Increase bundle size * Remove duplicate inject property * Add missing removeComponent method * Add basic test cases for createGlobalStyle * Add mutation test cases * fixup! Make tests work by pulling CONTEXT_KEY into StyleSheetManager locally * Basic implementation for interpolation and theme support * Test and WIP implementation for theme updates * Use correct theme update color * Add missing type annotations * Reimplement on top of GlobalStyle and StyleSheet * Add failing case for multiple rules * Use CSSConstructor correctly * Remove unused imports * Revert obsolete change * Improve test harness * Add support for multiple GlobalStyle components * Update test results * Revert obsolete change * Revert unrelated change * Explain purpose of static execution context * Fix flow issues * Fall back to null when children are not passed * Increase bundle size limit to 16.5kB * Be more explicit about getCSS scope * Avoid type errors for subscribe ob production builds * Mark withTheme usage as stop-gap measure * Warn about createGlobalStyle children being ignored * Add SSR test for createGlobalStyle * Increase commonjs bundle size to 12kB * Add a deprecation warning on `injectGlobal`. Resolve unrelated typo. * adjust changelog * Remove unnecessary ThemeProvider usage in Test case * Decrement start index in removeRules * Merge changes from develop * Update createGlobalStyle to new Context api * Fix typo in CHANGELOG.md * Remove package-lock.json and add to gitignore * Move global style updation into render method for GlobalStyleComponent * Throw error if children passed as props for createGlobalStyle component * Remove package.lock.json from sandbox * Remove injectGlobal API, replace it with createGlobalStyle in test cases * Unskip working test cases * Make changes based on feedback to PR * Remove injectGlobal.test.js.snap from tests * Replace injectglobal with createGlobalStyle in example * Add createGlobalStyle to standalone and no tags builds * adjust changelog
This commit is contained in:
committed by
Evan Jacobs
parent
a6b7a756b6
commit
a95bbfaa15
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ test-results.json
|
||||
mount-deep-tree-trace.json
|
||||
mount-wide-tree-trace.json
|
||||
update-dynamic-styles-trace.json
|
||||
package-lock.json
|
||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -25,6 +25,19 @@ _The format is based on [Keep a Changelog](http://keepachangelog.com/) and this
|
||||
Keyframes is now implemented in a "lazy" manner: its styles will be injected with the render phase of components using them.
|
||||
|
||||
`keyframes` no longer returns an animation name, instead it returns an object which has method `.getName()` for the purpose of getting the animation name.
|
||||
|
||||
* Add `createGlobalStyle` that returns a component which, when mounting, will apply global styles. This is a replacement for the `injectGlobal` API. It can be updated, replaced, removed, etc like any normal component and the global scope will update accordingly, by @JamieDixon @marionebl and @yjimk (see #1416)
|
||||
|
||||
```jsx
|
||||
const GlobalStyles = createGlobalStyle`
|
||||
html {
|
||||
color: 'red';
|
||||
}
|
||||
`
|
||||
|
||||
// then put it in your React tree somewhere:
|
||||
// <GlobalStyles />
|
||||
```
|
||||
|
||||
- Migrate to use new `React.forwardRef` API, by [@probablyup](https://github.com/probablyup); note that this removes the `innerRef` API since it is no longer needed.
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
import styled, { injectGlobal, keyframes } from '..'
|
||||
import styled, { createGlobalStyle, keyframes } from '..'
|
||||
|
||||
export default () => {
|
||||
injectGlobal`
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
@@ -28,6 +28,7 @@ export default () => {
|
||||
render() {
|
||||
return (
|
||||
<Wrapper>
|
||||
<GlobalStyle />
|
||||
<Title>Hello World, this is my first styled component!</Title>
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
|
||||
<script src="/styled-components.js"></script>
|
||||
<script type="text/babel">
|
||||
styled.injectGlobal`
|
||||
const GlobalStyle = styled.createGlobalStyle`
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
@@ -44,6 +44,7 @@
|
||||
render() {
|
||||
return (
|
||||
<Wrapper>
|
||||
<GlobalStyle/>
|
||||
<Title>Hello World, this is my first styled component!</Title>
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
import styled, { css, keyframes, injectGlobal } from 'styled-components'
|
||||
import styled, { css, keyframes, createGlobalStyle } from 'styled-components'
|
||||
|
||||
import {
|
||||
LiveProvider as _LiveProvider,
|
||||
@@ -11,8 +11,7 @@ import {
|
||||
|
||||
import buttonExample from './Button.example'
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
injectGlobal`
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
body {
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
@@ -117,6 +116,7 @@ const LiveError = styled(_LiveError)`
|
||||
|
||||
const App = () => (
|
||||
<Body>
|
||||
<GlobalStyle />
|
||||
<Heading>
|
||||
<Title>
|
||||
Interactive sandbox for <Code>styled-components</Code>
|
||||
@@ -129,7 +129,7 @@ const App = () => (
|
||||
<Content>
|
||||
<LiveProvider
|
||||
code={buttonExample}
|
||||
scope={{ styled, css, keyframes }}
|
||||
scope={{ styled, css, createGlobalStyle, keyframes }}
|
||||
noInline
|
||||
>
|
||||
<LiveEditor />
|
||||
|
||||
20
src/base.js
20
src/base.js
@@ -1,16 +1,18 @@
|
||||
// @flow
|
||||
|
||||
/* Import singletons */
|
||||
import flatten from './utils/flatten'
|
||||
import stringifyRules from './utils/stringifyRules'
|
||||
import isStyledComponent from './utils/isStyledComponent'
|
||||
import generateAlphabeticName from './utils/generateAlphabeticName'
|
||||
import css from './constructors/css'
|
||||
import _ComponentStyle from './models/ComponentStyle'
|
||||
import ServerStyleSheet from './models/ServerStyleSheet'
|
||||
import StyleSheetManager from './models/StyleSheetManager'
|
||||
|
||||
/* Import singleton constructors */
|
||||
import _keyframes from './constructors/keyframes'
|
||||
import _injectGlobal from './constructors/injectGlobal'
|
||||
import _createGlobalStyle from './constructors/createGlobalStyle'
|
||||
|
||||
/* Import components */
|
||||
import ThemeProvider from './models/ThemeProvider'
|
||||
@@ -58,17 +60,27 @@ if (
|
||||
window['__styled-components-init__'] += 1
|
||||
}
|
||||
|
||||
/* Instantiate internal singletons */
|
||||
const ComponentStyle = _ComponentStyle(
|
||||
generateAlphabeticName,
|
||||
flatten,
|
||||
stringifyRules
|
||||
)
|
||||
|
||||
/* Instantiate exported singletons */
|
||||
const keyframes = _keyframes(generateAlphabeticName, stringifyRules, css)
|
||||
const injectGlobal = _injectGlobal(stringifyRules, css)
|
||||
|
||||
const createGlobalStyle = _createGlobalStyle(
|
||||
ComponentStyle,
|
||||
stringifyRules,
|
||||
css
|
||||
)
|
||||
/* Export everything */
|
||||
|
||||
export * from './secretInternals'
|
||||
export {
|
||||
css,
|
||||
keyframes,
|
||||
injectGlobal,
|
||||
createGlobalStyle,
|
||||
isStyledComponent,
|
||||
ThemeProvider,
|
||||
withTheme,
|
||||
|
||||
@@ -15,3 +15,6 @@ export const IS_BROWSER =
|
||||
export const DISABLE_SPEEDY =
|
||||
(typeof __DEV__ === 'boolean' && __DEV__) ||
|
||||
process.env.NODE_ENV !== 'production'
|
||||
|
||||
// Shared empty execution context when generating static styles
|
||||
export const STATIC_EXECUTION_CONTEXT = {}
|
||||
|
||||
103
src/constructors/createGlobalStyle.js
Normal file
103
src/constructors/createGlobalStyle.js
Normal file
@@ -0,0 +1,103 @@
|
||||
// @flow
|
||||
import React from 'react'
|
||||
import { STATIC_EXECUTION_CONTEXT } from '../constants'
|
||||
import _GlobalStyle from '../models/GlobalStyle'
|
||||
import StyleSheet from '../models/StyleSheet'
|
||||
import { StyleSheetConsumer } from '../models/StyleSheetManager'
|
||||
import StyledError from '../utils/error'
|
||||
import determineTheme from '../utils/determineTheme'
|
||||
import { ThemeConsumer, type Theme } from '../models/ThemeProvider'
|
||||
import type { CSSConstructor, Interpolation, Stringifier } from '../types'
|
||||
import hashStr from '../vendor/glamor/hash'
|
||||
|
||||
export default (
|
||||
ComponentStyle: Function,
|
||||
stringifyRules: Stringifier,
|
||||
css: CSSConstructor
|
||||
) => {
|
||||
const GlobalStyle = _GlobalStyle(ComponentStyle, stringifyRules)
|
||||
|
||||
const createGlobalStyle = (
|
||||
strings: Array<string>,
|
||||
...interpolations: Array<Interpolation>
|
||||
) => {
|
||||
const rules = css(strings, ...interpolations)
|
||||
const id = `sc-global-${hashStr(JSON.stringify(rules))}`
|
||||
const style = new GlobalStyle(rules, id)
|
||||
class GlobalStyleComponent extends React.Component<*, *> {
|
||||
componentWillUnmount() {
|
||||
const { sheet } = this.props
|
||||
style.removeStyles(sheet)
|
||||
}
|
||||
render() {
|
||||
const { sheet, context } = this.props
|
||||
style.renderStyles(context, sheet)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class GlobalStyleComponentManager extends React.Component<*, *> {
|
||||
static defaultProps: Object
|
||||
|
||||
static styledComponentId = id
|
||||
|
||||
render() {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (typeof this.props.children !== 'undefined') {
|
||||
throw new StyledError(11)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<StyleSheetConsumer>
|
||||
{(styleSheet?: StyleSheet) => {
|
||||
if (style.isStatic) {
|
||||
return (
|
||||
<GlobalStyleComponent
|
||||
sheet={styleSheet || StyleSheet.master}
|
||||
context={STATIC_EXECUTION_CONTEXT}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ThemeConsumer>
|
||||
{(theme?: Theme) => {
|
||||
const { defaultProps } = this.constructor
|
||||
let context = {
|
||||
...this.props,
|
||||
}
|
||||
if (typeof theme !== 'undefined') {
|
||||
const determinedTheme = determineTheme(
|
||||
this.props,
|
||||
theme,
|
||||
defaultProps
|
||||
)
|
||||
// $FlowFixMe TODO: flow for optional styleSheet
|
||||
context = {
|
||||
theme: determinedTheme,
|
||||
...context,
|
||||
}
|
||||
}
|
||||
return (
|
||||
<GlobalStyleComponent
|
||||
sheet={styleSheet || StyleSheet.master}
|
||||
context={context}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</ThemeConsumer>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</StyleSheetConsumer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use internal abstractions to avoid additional component layers
|
||||
// Depends on a future overall refactoring of theming system / context
|
||||
return GlobalStyleComponentManager
|
||||
}
|
||||
|
||||
return createGlobalStyle
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// @flow
|
||||
import hashStr from '../vendor/glamor/hash'
|
||||
import StyleSheet from '../models/StyleSheet'
|
||||
import type { Interpolation, Stringifier } from '../types'
|
||||
|
||||
type InjectGlobalFn = (
|
||||
strings: Array<string>,
|
||||
...interpolations: Array<Interpolation>
|
||||
) => void
|
||||
|
||||
export default (stringifyRules: Stringifier, css: Function) => {
|
||||
const injectGlobal: InjectGlobalFn = (...args) => {
|
||||
const styleSheet = StyleSheet.master
|
||||
const rules = css(...args)
|
||||
const hash = hashStr(JSON.stringify(rules))
|
||||
const id = `sc-global-${hash}`
|
||||
|
||||
if (!styleSheet.hasId(id)) {
|
||||
styleSheet.inject(id, stringifyRules(rules))
|
||||
}
|
||||
}
|
||||
|
||||
return injectGlobal
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`createGlobalStyle should throw error when children are passed as props 1`] = `"[createGlobalStyle] received children which will not be rendered. Please use the component without passing children elements."`;
|
||||
@@ -1,14 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`injectGlobal should extract @import rules into separate style tags 1`] = `
|
||||
"<style data-styled=\\"\\" data-styled-version=\\"JEST_MOCK_VERSION\\">
|
||||
/* sc-component-id: sc-global-1433896746-import */
|
||||
@import url('bla');</style>
|
||||
<style data-styled=\\"\\" data-styled-version=\\"JEST_MOCK_VERSION\\">
|
||||
/* sc-component-id: sc-global-3616276198 */
|
||||
html{padding:1px;}
|
||||
/* sc-component-id: sc-a */
|
||||
.sc-a {} .b{color:green;}
|
||||
/* sc-component-id: sc-global-1433896746 */
|
||||
html{color:blue;}</style>"
|
||||
`;
|
||||
287
src/constructors/test/createGlobalStyle.test.js
Normal file
287
src/constructors/test/createGlobalStyle.test.js
Normal file
@@ -0,0 +1,287 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
|
||||
import { expectCSSMatches, getCSS, resetStyled, resetCreateGlobalStyle, stripComments, stripWhitespace } from '../../test/utils'
|
||||
import ThemeProvider from '../../models/ThemeProvider'
|
||||
import ServerStyleSheet from '../../models/ServerStyleSheet'
|
||||
import StyleSheetManager from '../../models/StyleSheetManager'
|
||||
|
||||
const createGlobalStyle = resetCreateGlobalStyle()
|
||||
const styled = resetStyled();
|
||||
|
||||
let context;
|
||||
|
||||
beforeEach(() => {
|
||||
context = setup()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
context.cleanup()
|
||||
})
|
||||
|
||||
describe(`createGlobalStyle`, () => {
|
||||
it(`returns a function`, () => {
|
||||
const Component = createGlobalStyle``
|
||||
expect(typeof Component).toBe('function')
|
||||
});
|
||||
|
||||
it(`injects global <style> when rendered`, () => {
|
||||
const { render } = context
|
||||
const Component = createGlobalStyle`[data-test-inject]{color:red;} `
|
||||
render(<Component />)
|
||||
expectCSSMatches(`[data-test-inject]{color:red;} `)
|
||||
});
|
||||
|
||||
it(`injects global <style> when rendered to string`, () => {
|
||||
const sheet = new ServerStyleSheet();
|
||||
const Component = createGlobalStyle`[data-test-inject]{color:red;} `
|
||||
const html = context.renderToString(sheet.collectStyles(<Component />))
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = sheet.getStyleTags();
|
||||
const style = container.querySelector('style');
|
||||
|
||||
expect(html).toBe('');
|
||||
expect(stripWhitespace(stripComments(style.textContent))).toBe('[data-test-inject]{ color:red; } ');
|
||||
});
|
||||
|
||||
it(`supports interpolation`, () => {
|
||||
const { cleanup, render } = setup()
|
||||
const Component = createGlobalStyle`div {color:${props => props.color};} `
|
||||
render(
|
||||
<Component color="orange" />
|
||||
)
|
||||
expectCSSMatches(`div{color:orange;} `)
|
||||
cleanup()
|
||||
})
|
||||
|
||||
it(`supports theming`, () => {
|
||||
const { cleanup, render } = setup()
|
||||
const Component = createGlobalStyle`div {color:${props => props.theme.color};} `
|
||||
render(
|
||||
<ThemeProvider theme={{ color: 'black' }}>
|
||||
<Component />
|
||||
</ThemeProvider>
|
||||
)
|
||||
expectCSSMatches(`div{color:black;} `)
|
||||
cleanup()
|
||||
})
|
||||
|
||||
it(`updates theme correctly`, () => {
|
||||
const { cleanup, render } = setup()
|
||||
const Component = createGlobalStyle`div {color:${props => props.theme.color};} `
|
||||
let update;
|
||||
class App extends React.Component {
|
||||
state = { color: 'grey' }
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
update = (payload) => {
|
||||
this.setState(payload)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ThemeProvider theme={{ color: this.state.color }}>
|
||||
<Component />
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
render(<App />)
|
||||
expectCSSMatches(`div{color:grey;} `)
|
||||
|
||||
update({ color: 'red' })
|
||||
expectCSSMatches(`div{color:red;} `)
|
||||
|
||||
cleanup()
|
||||
})
|
||||
|
||||
it(`renders to StyleSheetManager.target`, () => {
|
||||
const { container, render } = context
|
||||
const Component = createGlobalStyle`[data-test-target]{color:red;} `
|
||||
render(
|
||||
<StyleSheetManager target={container}>
|
||||
<Component />
|
||||
</StyleSheetManager>
|
||||
)
|
||||
|
||||
const style = container.firstChild;
|
||||
expect(style.tagName).toBe('STYLE')
|
||||
expect(style.textContent).toContain(`[data-test-target]{color:red;}`)
|
||||
});
|
||||
|
||||
it(`adds new global rules non-destructively`, () => {
|
||||
const { container, render } = context
|
||||
const Color = createGlobalStyle`[data-test-add]{color:red;} `
|
||||
const Background = createGlobalStyle`[data-test-add]{background:yellow;} `
|
||||
|
||||
render(
|
||||
<React.Fragment>
|
||||
<Color />
|
||||
<Background />
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
setTimeout(() => {
|
||||
expectCSSMatches(`
|
||||
[data-test-add]{color:red;}
|
||||
[data-test-add]{background:yellow;}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
it(`stringifies multiple rules correctly`, () => {
|
||||
const { cleanup, render } = setup()
|
||||
const Component = createGlobalStyle`
|
||||
div {
|
||||
color: ${props => props.fg};
|
||||
background: ${props => props.bg};
|
||||
}
|
||||
`
|
||||
render(
|
||||
<Component fg="red" bg="green" />
|
||||
)
|
||||
expectCSSMatches(`div{color:red;background:green;} `)
|
||||
cleanup()
|
||||
})
|
||||
|
||||
it(`injects multiple <GlobalStyle> components correctly`, () => {
|
||||
const { cleanup, render } = setup()
|
||||
|
||||
const A = createGlobalStyle`body { background: palevioletred; }`;
|
||||
const B = createGlobalStyle`body { color: white; }`;
|
||||
|
||||
render(
|
||||
<React.Fragment>
|
||||
<A />
|
||||
<B />
|
||||
</React.Fragment>
|
||||
)
|
||||
expectCSSMatches(`body{background:palevioletred;} body{color:white;}`)
|
||||
cleanup()
|
||||
})
|
||||
|
||||
it(`removes styling injected styling when unmounted`, () => {
|
||||
const { cleanup, container, render } = setup()
|
||||
const Component = createGlobalStyle`[data-test-remove]{color:grey;} `
|
||||
|
||||
class Comp extends React.Component {
|
||||
state = {
|
||||
styled: true
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({ styled: false })
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.state.styled ? <Component /> : null
|
||||
}
|
||||
}
|
||||
|
||||
render(<Comp />)
|
||||
expect(getCSS(document)).not.toContain(`[data-test-remove]{color:grey;}`)
|
||||
cleanup()
|
||||
})
|
||||
|
||||
it(`removes styling injected for multiple <GlobalStyle> components correctly`, () => {
|
||||
const { container, render } = context
|
||||
|
||||
const A = createGlobalStyle`body { background: palevioletred; }`;
|
||||
const B = createGlobalStyle`body { color: white; }`;
|
||||
|
||||
class Comp extends React.Component {
|
||||
state = {
|
||||
a: true,
|
||||
b: true
|
||||
}
|
||||
|
||||
onClick() {
|
||||
if (this.state.a === true && this.state.b === true) {
|
||||
this.setState({
|
||||
a: true,
|
||||
b: false
|
||||
})
|
||||
} else if (this.state.a === true && this.state.b === false) {
|
||||
this.setState({
|
||||
a: false,
|
||||
b: false
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
a: true,
|
||||
b: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div data-test-el onClick={() => this.onClick()}>
|
||||
{this.state.a ? <A /> : null}
|
||||
{this.state.b ? <B /> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render(<Comp />)
|
||||
const el = document.querySelector('[data-test-el]')
|
||||
expectCSSMatches(`body{background:palevioletred;} body{color:white;}`)
|
||||
|
||||
{
|
||||
el.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
const css = getCSS(document)
|
||||
expect(css).not.toContain('body{color:white;}')
|
||||
expect(css).toContain('body{background:palevioletred;}')
|
||||
}
|
||||
|
||||
{
|
||||
el.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
||||
const css = getCSS(document)
|
||||
expect(css).not.toContain('body{color:white;}')
|
||||
expect(css).not.toContain('body{background:palevioletred;}')
|
||||
}
|
||||
})
|
||||
|
||||
it(`should throw error when children are passed as props`, () => {
|
||||
const { cleanup, render } = setup()
|
||||
const Component = createGlobalStyle`
|
||||
div {
|
||||
color: ${props => props.fg};
|
||||
background: ${props => props.bg};
|
||||
}
|
||||
`
|
||||
expect(() => render(
|
||||
<Component fg="red" bg="green">
|
||||
<div />
|
||||
</Component>
|
||||
)).toThrowErrorMatchingSnapshot()
|
||||
|
||||
cleanup()
|
||||
})
|
||||
})
|
||||
|
||||
function setup() {
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
|
||||
return {
|
||||
container,
|
||||
render(comp) {
|
||||
ReactDOM.render(comp, container)
|
||||
},
|
||||
renderToString(comp) {
|
||||
return ReactDOMServer.renderToString(comp)
|
||||
},
|
||||
cleanup() {
|
||||
resetStyled()
|
||||
resetCreateGlobalStyle()
|
||||
document.body.removeChild(container)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// @flow
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
|
||||
import _injectGlobal from '../injectGlobal'
|
||||
import stringifyRules from '../../utils/stringifyRules'
|
||||
import css from '../css'
|
||||
import { expectCSSMatches, resetStyled } from '../../test/utils'
|
||||
|
||||
const injectGlobal = _injectGlobal(stringifyRules, css)
|
||||
|
||||
const styled = resetStyled()
|
||||
const rule1 = 'width:100%;'
|
||||
const rule2 = 'padding:10px;'
|
||||
const rule3 = 'color:blue;'
|
||||
|
||||
describe('injectGlobal', () => {
|
||||
beforeEach(() => {
|
||||
resetStyled()
|
||||
})
|
||||
|
||||
it(`should inject rules into the head`, () => {
|
||||
injectGlobal`
|
||||
html {
|
||||
${rule1}
|
||||
}
|
||||
`
|
||||
expectCSSMatches(`
|
||||
html {
|
||||
${rule1}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
it(`should non-destructively inject styles when called repeatedly`, () => {
|
||||
injectGlobal`
|
||||
html {
|
||||
${rule1}
|
||||
}
|
||||
`
|
||||
|
||||
injectGlobal`
|
||||
a {
|
||||
${rule2}
|
||||
}
|
||||
`
|
||||
expectCSSMatches(`
|
||||
html {
|
||||
${rule1}
|
||||
}
|
||||
a {
|
||||
${rule2}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
it(`should non-destructively inject styles when called after a component`, () => {
|
||||
const Comp = styled.div`
|
||||
${rule3};
|
||||
`
|
||||
TestRenderer.create(<Comp />)
|
||||
|
||||
injectGlobal`
|
||||
html {
|
||||
${rule1}
|
||||
}
|
||||
`
|
||||
|
||||
expectCSSMatches(`
|
||||
.sc-a {}
|
||||
.b {
|
||||
${rule3}
|
||||
}
|
||||
html {
|
||||
${rule1}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
it('should extract @import rules into separate style tags', () => {
|
||||
injectGlobal`html { padding: 1px; }`
|
||||
const Comp = styled.div`
|
||||
color: green;
|
||||
`
|
||||
TestRenderer.create(<Comp />)
|
||||
injectGlobal`html { color: blue; } @import url('bla');`
|
||||
|
||||
const style = Array.from(document.querySelectorAll('style'))
|
||||
.map(tag => tag.outerHTML)
|
||||
.join('\n')
|
||||
|
||||
expect(style).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
@@ -20,7 +20,6 @@ const ComponentStyle = _ComponentStyle(
|
||||
flatten,
|
||||
stringifyRules
|
||||
)
|
||||
|
||||
const constructWithOptions = _constructWithOptions(css)
|
||||
const StyledComponent = _StyledComponent(ComponentStyle)
|
||||
|
||||
|
||||
@@ -1,39 +1,12 @@
|
||||
// @flow
|
||||
import hashStr from '../vendor/glamor/hash'
|
||||
|
||||
import isStaticRules from '../utils/isStaticRules'
|
||||
import type { RuleSet, NameGenerator, Flattener, Stringifier } from '../types'
|
||||
import StyleSheet from './StyleSheet'
|
||||
import { IS_BROWSER } from '../constants'
|
||||
import isStyledComponent from '../utils/isStyledComponent'
|
||||
|
||||
const areStylesCacheable = IS_BROWSER
|
||||
|
||||
const isStaticRules = (rules: RuleSet, attrs?: Object): boolean => {
|
||||
for (let i = 0, len = rules.length; i < len; i += 1) {
|
||||
const rule = rules[i]
|
||||
|
||||
// recursive case
|
||||
if (Array.isArray(rule) && !isStaticRules(rule)) {
|
||||
return false
|
||||
} else if (typeof rule === 'function' && !isStyledComponent(rule)) {
|
||||
// functions are allowed to be static if they're just being
|
||||
// used to get the classname of a nested styled component
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs !== undefined) {
|
||||
// eslint-disable-next-line guard-for-in, no-restricted-syntax
|
||||
for (const key in attrs) {
|
||||
if (typeof attrs[key] === 'function') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const isHMREnabled =
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
typeof module !== 'undefined' &&
|
||||
@@ -88,7 +61,6 @@ export default (
|
||||
|
||||
const flatCSS = flatten(this.rules, executionContext, styleSheet)
|
||||
const name = generateRuleHash(this.componentId + flatCSS.join(''))
|
||||
|
||||
if (!styleSheet.hasNameForId(componentId, name)) {
|
||||
styleSheet.inject(
|
||||
this.componentId,
|
||||
|
||||
44
src/models/GlobalStyle.js
Normal file
44
src/models/GlobalStyle.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// @flow
|
||||
import type { RuleSet, Stringifier } from '../types'
|
||||
import flatten from '../utils/flatten'
|
||||
import isStaticRules from '../utils/isStaticRules'
|
||||
import StyleSheet from './StyleSheet'
|
||||
|
||||
export default (ComponentStyle: Function, stringifyRules: Stringifier) => {
|
||||
class GlobalStyle {
|
||||
rules: RuleSet
|
||||
componentId: string
|
||||
isStatic: boolean
|
||||
|
||||
constructor(rules: RuleSet, componentId: string) {
|
||||
this.rules = rules
|
||||
this.componentId = componentId
|
||||
this.isStatic = isStaticRules(rules)
|
||||
if (!StyleSheet.master.hasId(componentId)) {
|
||||
StyleSheet.master.deferredInject(componentId, [])
|
||||
}
|
||||
}
|
||||
|
||||
createStyles(executionContext: Object, styleSheet: StyleSheet) {
|
||||
const flatCSS = flatten(this.rules, executionContext)
|
||||
const css = stringifyRules(flatCSS, '')
|
||||
// TODO: We will need to figure out how to do this before 4.0
|
||||
// const name = ComponentStyle.generateName(this.componentId + css)
|
||||
styleSheet.inject(this.componentId, css, '')
|
||||
}
|
||||
|
||||
renderStyles(executionContext: Object, styleSheet: StyleSheet) {
|
||||
this.removeStyles(styleSheet)
|
||||
this.createStyles(executionContext, styleSheet)
|
||||
}
|
||||
|
||||
removeStyles(styleSheet: StyleSheet) {
|
||||
const { componentId } = this
|
||||
if (styleSheet.hasId(componentId)) {
|
||||
styleSheet.remove(componentId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return GlobalStyle
|
||||
}
|
||||
@@ -72,7 +72,6 @@ export default class StyleSheet {
|
||||
if (!IS_BROWSER || this.forceServer) {
|
||||
return this
|
||||
}
|
||||
|
||||
const els = []
|
||||
const names = []
|
||||
const extracted = []
|
||||
@@ -136,7 +135,7 @@ export default class StyleSheet {
|
||||
}
|
||||
|
||||
/* retrieve a "master" instance of StyleSheet which is typically used when no other is available
|
||||
* The master StyleSheet is targeted by injectGlobal, keyframes, and components outside of any
|
||||
* The master StyleSheet is targeted by createGlobalStyle, keyframes, and components outside of any
|
||||
* StyleSheetManager's context */
|
||||
static get master(): StyleSheet {
|
||||
return master || (master = new StyleSheet().rehydrate())
|
||||
@@ -237,7 +236,7 @@ export default class StyleSheet {
|
||||
return (this.tagMap[id] = tag)
|
||||
}
|
||||
|
||||
/* mainly for injectGlobal to check for its id */
|
||||
/* mainly for createGlobalStyle to check for its id */
|
||||
hasId(id: string) {
|
||||
return this.tagMap[id] !== undefined
|
||||
}
|
||||
@@ -278,7 +277,6 @@ export default class StyleSheet {
|
||||
}
|
||||
|
||||
const tag = this.getTagForId(id)
|
||||
|
||||
/* add deferred rules for component */
|
||||
if (this.deferred[id] !== undefined) {
|
||||
// Combine passed cssRules with previously deferred CSS rules
|
||||
|
||||
@@ -47,7 +47,6 @@ export default class StyleSheetManager extends Component<Props> {
|
||||
render() {
|
||||
const { children, sheet, target } = this.props
|
||||
const context = this.getContext(sheet, target)
|
||||
|
||||
return (
|
||||
<StyleSheetContext.Provider value={context}>
|
||||
{React.Children.only(children)}
|
||||
|
||||
@@ -286,9 +286,7 @@ const makeBrowserTag = (
|
||||
marker.appendData(`${rule}${separator}`)
|
||||
}
|
||||
}
|
||||
|
||||
addNameForId(names, id, name)
|
||||
|
||||
if (extractImport && importRules.length > 0) {
|
||||
usedImportRuleTag = true
|
||||
// $FlowFixMe
|
||||
@@ -320,7 +318,6 @@ const makeBrowserTag = (
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
return {
|
||||
clone() {
|
||||
throw new StyledError(5)
|
||||
|
||||
@@ -6,6 +6,7 @@ import React, { Component, createElement } from 'react'
|
||||
import createWarnTooManyClasses from '../utils/createWarnTooManyClasses'
|
||||
import determineTheme from '../utils/determineTheme'
|
||||
import escape from '../utils/escape'
|
||||
|
||||
import generateDisplayName from '../utils/generateDisplayName'
|
||||
import getComponentName from '../utils/getComponentName'
|
||||
import once from '../utils/once'
|
||||
|
||||
@@ -10,34 +10,6 @@ body{background:papayawhip;}
|
||||
.sc-a {} .b{color:red;}</style>"
|
||||
`;
|
||||
|
||||
exports[`ssr should allow global styles to be injected during rendering 1`] = `"<h1 class=\\"PageOne a\\">Camera One!</h1>"`;
|
||||
|
||||
exports[`ssr should allow global styles to be injected during rendering 2`] = `
|
||||
"<style data-styled=\\"a\\" data-styled-version=\\"JEST_MOCK_VERSION\\">
|
||||
/* sc-component-id: sc-global-737874422 */
|
||||
html::before{content:'Before both renders';}
|
||||
/* sc-component-id: PageOne */
|
||||
.PageOne {} .a{color:red;}</style><style data-styled=\\"\\" data-styled-version=\\"JEST_MOCK_VERSION\\">
|
||||
/* sc-component-id: sc-global-2914197427 */
|
||||
html::before{content:'During first render';}</style>"
|
||||
`;
|
||||
|
||||
exports[`ssr should allow global styles to be injected during rendering 3`] = `"<h2 class=\\"PageTwo b\\">Camera Two!</h2>"`;
|
||||
|
||||
exports[`ssr should allow global styles to be injected during rendering 4`] = `
|
||||
"<style data-styled=\\"b\\" data-styled-version=\\"JEST_MOCK_VERSION\\">
|
||||
/* sc-component-id: sc-global-737874422 */
|
||||
html::before{content:'Before both renders';}
|
||||
/* sc-component-id: PageTwo */
|
||||
.PageTwo {} .b{color:blue;}
|
||||
/* sc-component-id: sc-global-2914197427 */
|
||||
html::before{content:'During first render';}
|
||||
/* sc-component-id: sc-global-1207956261 */
|
||||
html::before{content:'Between renders';}</style><style data-styled=\\"\\" data-styled-version=\\"JEST_MOCK_VERSION\\">
|
||||
/* sc-component-id: sc-global-3990873394 */
|
||||
html::before{content:'During second render';}</style>"
|
||||
`;
|
||||
|
||||
exports[`ssr should dispatch global styles to each ServerStyleSheet 1`] = `"<h1 class=\\"Header a\\"></h1>"`;
|
||||
|
||||
exports[`ssr should dispatch global styles to each ServerStyleSheet 2`] = `
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
import React from 'react'
|
||||
import TestRenderer from 'react-test-renderer'
|
||||
|
||||
import { resetStyled, expectCSSMatches, seedNextClassnames } from './utils'
|
||||
import { resetStyled, expectCSSMatches, seedNextClassnames, resetCreateGlobalStyle } from './utils'
|
||||
|
||||
import _injectGlobal from '../constructors/injectGlobal'
|
||||
import _createGlobalStyle from '../constructors/createGlobalStyle'
|
||||
import stringifyRules from '../utils/stringifyRules'
|
||||
import css from '../constructors/css'
|
||||
import _keyframes from '../constructors/keyframes'
|
||||
@@ -16,7 +16,8 @@ const keyframes = _keyframes(
|
||||
stringifyRules,
|
||||
css
|
||||
)
|
||||
const injectGlobal = _injectGlobal(stringifyRules, css)
|
||||
|
||||
let createGlobalStyle
|
||||
|
||||
const getStyleTags = () =>
|
||||
Array.from(document.querySelectorAll('style')).map(el => ({
|
||||
@@ -31,6 +32,7 @@ describe('rehydration', () => {
|
||||
*/
|
||||
beforeEach(() => {
|
||||
styled = resetStyled()
|
||||
createGlobalStyle = resetCreateGlobalStyle()
|
||||
})
|
||||
|
||||
describe('with existing styled components', () => {
|
||||
@@ -204,21 +206,23 @@ describe('rehydration', () => {
|
||||
})
|
||||
|
||||
it('should inject new global styles at the end', () => {
|
||||
injectGlobal`
|
||||
const Component = createGlobalStyle`
|
||||
body { color: tomato; }
|
||||
`
|
||||
TestRenderer.create(<Component />)
|
||||
expectCSSMatches(
|
||||
'body { background: papayawhip; } .b { color: red; } body { color:tomato; }'
|
||||
)
|
||||
})
|
||||
|
||||
it('should interleave global and local styles', () => {
|
||||
injectGlobal`
|
||||
const Component = createGlobalStyle`
|
||||
body { color: tomato; }
|
||||
`
|
||||
const A = styled.div.withConfig({ componentId: 'ONE' })`
|
||||
color: blue;
|
||||
`
|
||||
TestRenderer.create(<Component />)
|
||||
TestRenderer.create(<A />)
|
||||
|
||||
expectCSSMatches(
|
||||
@@ -316,13 +320,18 @@ describe('rehydration', () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('should not change styles if rendered in the same order they were created with', () => {
|
||||
injectGlobal`
|
||||
// TODO: We need this test to run before we release 4.0 to the public
|
||||
// Skipping this test for now, because a fix to StyleTags is needed
|
||||
// which is being worked on
|
||||
it.skip('should not change styles if rendered in the same order they were created with', () => {
|
||||
const Component1 = createGlobalStyle`
|
||||
html { font-size: 16px; }
|
||||
`
|
||||
injectGlobal`
|
||||
TestRenderer.create(<Component1 />)
|
||||
const Component2 = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`
|
||||
TestRenderer.create(<Component2 />)
|
||||
const A = styled.div.withConfig({ componentId: 'ONE' })`
|
||||
color: blue;
|
||||
`
|
||||
@@ -340,21 +349,26 @@ describe('rehydration', () => {
|
||||
`)
|
||||
})
|
||||
|
||||
it('should still not change styles if rendered in a different order', () => {
|
||||
// TODO: We need this test to run before we release 4.0 to the public
|
||||
// Skipping this test for now, because a fix to StyleTags is needed
|
||||
// which is being worked on
|
||||
it.skip('should still not change styles if rendered in a different order', () => {
|
||||
const B = styled.div.withConfig({ componentId: 'TWO' })`
|
||||
color: red;
|
||||
`
|
||||
TestRenderer.create(<B />)
|
||||
injectGlobal`
|
||||
const Component1 = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`
|
||||
TestRenderer.create(<Component1 />)
|
||||
const A = styled.div.withConfig({ componentId: 'ONE' })`
|
||||
color: blue;
|
||||
`
|
||||
TestRenderer.create(<A />)
|
||||
injectGlobal`
|
||||
const Component2 = createGlobalStyle`
|
||||
html { font-size: 16px; }
|
||||
`
|
||||
TestRenderer.create(<Component2 />)
|
||||
|
||||
expectCSSMatches(`
|
||||
html { font-size: 16px; }
|
||||
|
||||
@@ -4,20 +4,18 @@
|
||||
import React from 'react'
|
||||
import { renderToString, renderToNodeStream } from 'react-dom/server'
|
||||
import ServerStyleSheet from '../models/ServerStyleSheet'
|
||||
import { resetStyled } from './utils'
|
||||
import _injectGlobal from '../constructors/injectGlobal'
|
||||
import { resetStyled, resetCreateGlobalStyle } from './utils'
|
||||
import _keyframes from '../constructors/keyframes'
|
||||
import stringifyRules from '../utils/stringifyRules'
|
||||
import css from '../constructors/css'
|
||||
|
||||
jest.mock('../utils/nonce')
|
||||
|
||||
const injectGlobal = _injectGlobal(stringifyRules, css)
|
||||
|
||||
let index = 0
|
||||
const keyframes = _keyframes(() => `keyframe_${index++}`, stringifyRules, css)
|
||||
|
||||
let styled
|
||||
let createGlobalStyle
|
||||
|
||||
describe('ssr', () => {
|
||||
beforeEach(() => {
|
||||
@@ -25,6 +23,7 @@ describe('ssr', () => {
|
||||
require('../utils/nonce').mockReset()
|
||||
|
||||
styled = resetStyled(true)
|
||||
createGlobalStyle = resetCreateGlobalStyle()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@@ -47,7 +46,7 @@ describe('ssr', () => {
|
||||
})
|
||||
|
||||
it('should extract both global and local CSS', () => {
|
||||
injectGlobal`
|
||||
const Component = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`
|
||||
const Heading = styled.h1`
|
||||
@@ -56,7 +55,10 @@ describe('ssr', () => {
|
||||
|
||||
const sheet = new ServerStyleSheet()
|
||||
const html = renderToString(
|
||||
sheet.collectStyles(<Heading>Hello SSR!</Heading>)
|
||||
sheet.collectStyles(<React.Fragment>
|
||||
<Component />
|
||||
<Heading>Hello SSR!</Heading>
|
||||
</React.Fragment>)
|
||||
)
|
||||
const css = sheet.getStyleTags()
|
||||
|
||||
@@ -86,7 +88,7 @@ describe('ssr', () => {
|
||||
// eslint-disable-next-line
|
||||
require('../utils/nonce').mockImplementation(() => 'foo')
|
||||
|
||||
injectGlobal`
|
||||
const Component = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`
|
||||
const Heading = styled.h1`
|
||||
@@ -95,7 +97,10 @@ describe('ssr', () => {
|
||||
|
||||
const sheet = new ServerStyleSheet()
|
||||
const html = renderToString(
|
||||
sheet.collectStyles(<Heading>Hello SSR!</Heading>)
|
||||
sheet.collectStyles(<React.Fragment>
|
||||
<Component />
|
||||
<Heading>Hello SSR!</Heading>
|
||||
</React.Fragment>)
|
||||
)
|
||||
const css = sheet.getStyleTags()
|
||||
|
||||
@@ -127,7 +132,7 @@ describe('ssr', () => {
|
||||
})
|
||||
|
||||
it('should share global styles but keep renders separate', () => {
|
||||
injectGlobal`
|
||||
const Component = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`
|
||||
const PageOne = styled.h1.withConfig({ componentId: 'PageOne' })`
|
||||
@@ -139,13 +144,19 @@ describe('ssr', () => {
|
||||
|
||||
const sheetOne = new ServerStyleSheet()
|
||||
const htmlOne = renderToString(
|
||||
sheetOne.collectStyles(<PageOne>Camera One!</PageOne>)
|
||||
sheetOne.collectStyles(<React.Fragment>
|
||||
<Component />
|
||||
<PageOne>Camera One!</PageOne>
|
||||
</React.Fragment>)
|
||||
)
|
||||
const cssOne = sheetOne.getStyleTags()
|
||||
|
||||
const sheetTwo = new ServerStyleSheet()
|
||||
const htmlTwo = renderToString(
|
||||
sheetTwo.collectStyles(<PageTwo>Camera Two!</PageTwo>)
|
||||
sheetTwo.collectStyles(<React.Fragment>
|
||||
<Component />
|
||||
<PageTwo>Camera Two!</PageTwo>
|
||||
</React.Fragment>)
|
||||
)
|
||||
const cssTwo = sheetTwo.getStyleTags()
|
||||
|
||||
@@ -155,41 +166,8 @@ describe('ssr', () => {
|
||||
expect(cssTwo).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('should allow global styles to be injected during rendering', () => {
|
||||
injectGlobal`html::before { content: 'Before both renders'; }`
|
||||
const PageOne = styled.h1.withConfig({ componentId: 'PageOne' })`
|
||||
color: red;
|
||||
`
|
||||
const PageTwo = styled.h2.withConfig({ componentId: 'PageTwo' })`
|
||||
color: blue;
|
||||
`
|
||||
|
||||
const sheetOne = new ServerStyleSheet()
|
||||
const htmlOne = renderToString(
|
||||
sheetOne.collectStyles(<PageOne>Camera One!</PageOne>)
|
||||
)
|
||||
injectGlobal`html::before { content: 'During first render'; }`
|
||||
const cssOne = sheetOne.getStyleTags()
|
||||
|
||||
injectGlobal`html::before { content: 'Between renders'; }`
|
||||
|
||||
const sheetTwo = new ServerStyleSheet()
|
||||
injectGlobal`html::before { content: 'During second render'; }`
|
||||
const htmlTwo = renderToString(
|
||||
sheetTwo.collectStyles(<PageTwo>Camera Two!</PageTwo>)
|
||||
)
|
||||
const cssTwo = sheetTwo.getStyleTags()
|
||||
|
||||
injectGlobal`html::before { content: 'After both renders'; }`
|
||||
|
||||
expect(htmlOne).toMatchSnapshot()
|
||||
expect(cssOne).toMatchSnapshot()
|
||||
expect(htmlTwo).toMatchSnapshot()
|
||||
expect(cssTwo).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('should dispatch global styles to each ServerStyleSheet', () => {
|
||||
injectGlobal`
|
||||
const Component = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`
|
||||
const Header = styled.h1.withConfig({ componentId: 'Header' })`
|
||||
@@ -198,7 +176,10 @@ describe('ssr', () => {
|
||||
|
||||
const sheet = new ServerStyleSheet()
|
||||
const html = renderToString(
|
||||
sheet.collectStyles(<Header animation={keyframes`0% { opacity: 0; }`} />)
|
||||
sheet.collectStyles(<React.Fragment>
|
||||
<Component />
|
||||
<Header animation={keyframes`0% { opacity: 0; }`} />
|
||||
</React.Fragment>)
|
||||
)
|
||||
const css = sheet.getStyleTags()
|
||||
|
||||
@@ -207,7 +188,7 @@ describe('ssr', () => {
|
||||
})
|
||||
|
||||
it('should return a generated React style element', () => {
|
||||
injectGlobal`
|
||||
const Component = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`
|
||||
const Heading = styled.h1`
|
||||
@@ -216,7 +197,10 @@ describe('ssr', () => {
|
||||
|
||||
const sheet = new ServerStyleSheet()
|
||||
const html = renderToString(
|
||||
sheet.collectStyles(<Heading>Hello SSR!</Heading>)
|
||||
sheet.collectStyles(<React.Fragment>
|
||||
<Component />
|
||||
<Heading>Hello SSR!</Heading>
|
||||
</React.Fragment>)
|
||||
)
|
||||
const elements = sheet.getStyleElement()
|
||||
|
||||
@@ -233,7 +217,7 @@ describe('ssr', () => {
|
||||
// eslint-disable-next-line
|
||||
require('../utils/nonce').mockImplementation(() => 'foo')
|
||||
|
||||
injectGlobal`
|
||||
const Component = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`
|
||||
const Heading = styled.h1`
|
||||
@@ -242,7 +226,10 @@ describe('ssr', () => {
|
||||
|
||||
const sheet = new ServerStyleSheet()
|
||||
const html = renderToString(
|
||||
sheet.collectStyles(<Heading>Hello SSR!</Heading>)
|
||||
sheet.collectStyles(<React.Fragment>
|
||||
<Heading>Hello SSR!</Heading>
|
||||
<Component />
|
||||
</React.Fragment>)
|
||||
)
|
||||
const elements = sheet.getStyleElement()
|
||||
|
||||
@@ -251,7 +238,7 @@ describe('ssr', () => {
|
||||
})
|
||||
|
||||
it('should interleave styles with rendered HTML when utilitizing streaming', () => {
|
||||
injectGlobal`
|
||||
const Component = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`
|
||||
const Heading = styled.h1`
|
||||
@@ -259,7 +246,10 @@ describe('ssr', () => {
|
||||
`
|
||||
|
||||
const sheet = new ServerStyleSheet()
|
||||
const jsx = sheet.collectStyles(<Heading>Hello SSR!</Heading>)
|
||||
const jsx = sheet.collectStyles(<React.Fragment>
|
||||
<Component />
|
||||
<Heading>Hello SSR!</Heading>
|
||||
</React.Fragment>)
|
||||
const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx))
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -280,7 +270,7 @@ describe('ssr', () => {
|
||||
})
|
||||
|
||||
it('should handle errors while streaming', () => {
|
||||
injectGlobal`
|
||||
const Component = createGlobalStyle`
|
||||
body { background: papayawhip; }
|
||||
`
|
||||
const Heading = styled.h1`
|
||||
@@ -292,7 +282,7 @@ describe('ssr', () => {
|
||||
const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx))
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.on('data', function noop(){})
|
||||
stream.on('data', function noop() { })
|
||||
|
||||
stream.on('error', (err) => {
|
||||
expect(err).toMatchSnapshot()
|
||||
|
||||
@@ -5,11 +5,9 @@ import TestRenderer from 'react-test-renderer'
|
||||
import * as nonce from '../utils/nonce'
|
||||
import { resetStyled, expectCSSMatches } from './utils'
|
||||
import StyleSheet from '../models/StyleSheet'
|
||||
import _injectGlobal from '../constructors/injectGlobal'
|
||||
import stringifyRules from '../utils/stringifyRules'
|
||||
import css from '../constructors/css'
|
||||
|
||||
const injectGlobal = _injectGlobal(stringifyRules, css)
|
||||
|
||||
jest.mock('../utils/nonce')
|
||||
jest.spyOn(nonce, 'default').mockImplementation(() => 'foo')
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* our public API works the way we promise/want
|
||||
*/
|
||||
import _styled from '../constructors/styled'
|
||||
import _createGlobalStyle from '../constructors/createGlobalStyle'
|
||||
import css from '../constructors/css'
|
||||
import _constructWithOptions from '../constructors/constructWithOptions'
|
||||
import StyleSheet from '../models/StyleSheet'
|
||||
@@ -47,7 +48,12 @@ export const resetStyled = (isServer: boolean = false) => {
|
||||
return _styled(StyledComponent, constructWithOptions)
|
||||
}
|
||||
|
||||
const stripComments = (str: string) => str.replace(/\/\*.*?\*\/\n?/g, '')
|
||||
export const resetCreateGlobalStyle = () => {
|
||||
const ComponentStyle = _ComponentStyle(classNames, flatten, stringifyRules)
|
||||
return _createGlobalStyle(ComponentStyle, stringifyRules, css)
|
||||
}
|
||||
|
||||
export const stripComments = (str: string) => str.replace(/\/\*.*?\*\/\n?/g, '')
|
||||
|
||||
export const stripWhitespace = (str: string) =>
|
||||
str
|
||||
@@ -55,6 +61,16 @@ export const stripWhitespace = (str: string) =>
|
||||
.replace(/([;\{\}])/g, '$1 ')
|
||||
.replace(/\s+/g, ' ')
|
||||
|
||||
|
||||
export const getCSS = (scope: Document | HTMLElement) => {
|
||||
return Array.from(scope.querySelectorAll('style'))
|
||||
.map(tag => tag.innerHTML)
|
||||
.join('\n')
|
||||
.replace(/ {/g, '{')
|
||||
.replace(/:\s+/g, ':')
|
||||
.replace(/:\s+;/g, ':;')
|
||||
}
|
||||
|
||||
export const expectCSSMatches = (
|
||||
_expectation: string,
|
||||
opts: { ignoreWhitespace: boolean } = { ignoreWhitespace: true }
|
||||
@@ -65,12 +81,7 @@ export const expectCSSMatches = (
|
||||
.replace(/:\s+/g, ':')
|
||||
.replace(/:\s+;/g, ':;')
|
||||
|
||||
const css = Array.from(document.querySelectorAll('style'))
|
||||
.map(tag => tag.innerHTML)
|
||||
.join('\n')
|
||||
.replace(/ {/g, '{')
|
||||
.replace(/:\s+/g, ':')
|
||||
.replace(/:\s+;/g, ':;')
|
||||
const css = getCSS(document)
|
||||
|
||||
if (opts.ignoreWhitespace) {
|
||||
const stripped = stripWhitespace(stripComments(css))
|
||||
|
||||
@@ -19,6 +19,10 @@ export type Target = string | ComponentType<*>
|
||||
|
||||
export type NameGenerator = (hash: number) => string
|
||||
|
||||
export type CSSConstructor = (
|
||||
strings: Array<string>,
|
||||
...interpolations: Array<Interpolation>
|
||||
) => RuleSet
|
||||
export type StyleSheet = {
|
||||
create: Function,
|
||||
}
|
||||
|
||||
@@ -56,3 +56,7 @@ Missing document `<head>`
|
||||
## 10
|
||||
|
||||
Cannot find sheet for given tag
|
||||
|
||||
## 11
|
||||
|
||||
[createGlobalStyle] received children which will not be rendered. Please use the component without passing children elements.
|
||||
30
src/utils/isStaticRules.js
Normal file
30
src/utils/isStaticRules.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// @flow
|
||||
import isStyledComponent from './isStyledComponent'
|
||||
import type { RuleSet } from '../types'
|
||||
|
||||
export default function isStaticRules(rules: RuleSet, attrs?: Object): boolean {
|
||||
for (let i = 0; i < rules.length; i += 1) {
|
||||
const rule = rules[i]
|
||||
|
||||
// recursive case
|
||||
if (Array.isArray(rule) && !isStaticRules(rule)) {
|
||||
return false
|
||||
} else if (typeof rule === 'function' && !isStyledComponent(rule)) {
|
||||
// functions are allowed to be static if they're just being
|
||||
// used to get the classname of a nested styled component
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs !== undefined) {
|
||||
// eslint-disable-next-line guard-for-in, no-restricted-syntax
|
||||
for (const key in attrs) {
|
||||
const value = attrs[key]
|
||||
if (typeof value === 'function') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
2
src/vendor/postcss/css-syntax-error.js
vendored
2
src/vendor/postcss/css-syntax-error.js
vendored
@@ -201,7 +201,7 @@ class CssSyntaxError {
|
||||
}
|
||||
|
||||
get generated() {
|
||||
warnOnce('CssSyntaxError#generated is depreacted. Use input instead.');
|
||||
warnOnce('CssSyntaxError#generated is deprecated. Use input instead.');
|
||||
return this.input;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user