Browse Source

Rendering

dynamic-styles
Giuseppe Gurgone 7 years ago
parent
commit
5f512328ba
  1. 32
      src/_utils.js
  2. 1
      src/babel.js
  3. 13
      src/render.js
  4. 37
      src/style.js
  5. 24
      test/__snapshots__/index.js.snap

32
src/_utils.js

@ -30,7 +30,6 @@ export const findStyles = path => {
return path.get('children').filter(isStyledJsx)
}
// We only allow constants to be used in template literals.
// The following visitor ensures that MemberExpressions and Identifiers
// are not in the scope of the current Method (render) or function (Component).
export const validateExpressionVisitor = {
@ -69,9 +68,7 @@ export const validateExpressionVisitor = {
}
}
export const validateExpression = (expr, scope) =>
expr.traverse(validateExpressionVisitor, scope)
// Use `validateExpressionVisitor` to determine whether the `expr`ession has dynamic values.
export const isDynamic = (expr, scope) => {
try {
expr.traverse(validateExpressionVisitor, scope)
@ -121,13 +118,15 @@ export const getJSXStyleInfo = (expr, scope) => {
// p { color: %%styled-jsx-placeholder-${id}%%; }
const { quasis, expressions } = node
const dynamic = scope && isDynamic(expr, scope)
const css = quasis.reduce((css, quasi, index) => {
return `${css}${quasi.value.cooked}${quasis.length === index + 1
? ''
: `%%styled-jsx-placeholder-${index}%%`}`
}, '')
const hash = String(hashString(expr.getSource().slice(1, -1)))
const dynamic = scope ? isDynamic(expr, scope) : false
const css = quasis.reduce(
(css, quasi, index) =>
`${css}${quasi.value.cooked}${quasis.length === index + 1
? ''
: `%%styled-jsx-placeholder-${index}%%`}`,
''
)
return {
hash,
@ -167,9 +166,9 @@ export const buildJsxId = (styles, externalJsxId) => {
return t.stringLiteral(hashes.static.join(' '))
}
// _JSXStyle.get([ ['1234', [props.foo, bar, fn(props)]], ... ])
// _JSXStyle.dynamic([ ['1234', [props.foo, bar, fn(props)]], ... ])
const dynamic = t.callExpression(
// Callee: _JSXStyle.get
// Callee: _JSXStyle.dynamic
t.memberExpression(t.identifier(STYLE_COMPONENT), t.identifier('dynamic')),
// Arguments
[t.arrayExpression(hashes.dynamic)]
@ -179,11 +178,14 @@ export const buildJsxId = (styles, externalJsxId) => {
return dynamic
}
// `1234 5678 ${_JSXStyle.get([ ['1234', [props.foo, bar, fn(props)]], ... ])}`
// `1234 5678 ${_JSXStyle.dynamic([ ['1234', [props.foo, bar, fn(props)]], ... ])}`
return t.templateLiteral(
[
t.templateElement(
{ raw: hashes.static.join(' ') + ' ', cooked: hashes.static },
{
raw: hashes.static.join(' ') + ' ',
cooked: hashes.static.join(' ') + ' '
},
false
),
t.templateElement({ raw: '', cooked: '' }, true)
@ -198,7 +200,7 @@ export const templateLiteralFromPreprocessedCss = (css, expressions) => {
const parts = css.split(/(?:%%styled-jsx-placeholder-(\d+)%%)/g)
parts.forEach((part, index) => {
if (index % 2 > 0) {
// This is necessary because after preprocessing declarations might have been alternate.
// This is necessary because, after preprocessing, declarations might have been alterate.
// eg. properties are auto prefixed and therefore expressions need to match.
finalExpressions.push(expressions[part])
} else {

1
src/babel.js

@ -30,6 +30,7 @@ import {
const getPrefix = (isDynamic, id) =>
`[${MARKUP_ATTRIBUTE}~="${isDynamic ? '?' : id}"]`
const callExternalVisitor = (visitor, path, state) => {
const { file } = state
const { opts } = file

13
src/render.js

@ -1,5 +1,6 @@
const tags = new Map()
let prevStyles = new Map()
const mountedInstancesCount = {}
export default (typeof window === 'undefined' ? renderOnServer : renderOnClient)
@ -25,11 +26,19 @@ function patch([added, removed]) {
fromServer.set(id, document.getElementById(`__jsx-style-${id}`))
}
const tag = fromServer.get(id) || makeStyleTag(css)
tags.set(id, tag)
mountedInstancesCount[id] = (mountedInstancesCount[id] || 0) + 1
if (mountedInstancesCount[id] === 1) {
const tag = fromServer.get(id) || makeStyleTag(css)
tags.set(id, tag)
}
}
for (const [id] of removed) {
mountedInstancesCount[id] -= 1
if (mountedInstancesCount[id] > 0) {
continue
}
delete mountedInstancesCount[id]
const t = tags.get(id)
tags.delete(id)
t.parentNode.removeChild(t)

37
src/style.js

@ -1,9 +1,24 @@
import { Component } from 'react'
import hashString from 'string-hash'
import render from './render'
let components = []
function hashArray(arr) {
return hashString(arr.join(','))
}
export default class extends Component {
static dynamic(arr) {
return arr
.map(tagInfo => {
const [styleId, expressions] = tagInfo
const hash = hashArray(expressions)
return `${styleId}-${hash}`
})
.join(' ')
}
componentWillMount() {
mount(this)
}
@ -14,7 +29,8 @@ export default class extends Component {
update({
instance: this,
styleId: nextProps.styleId,
css: nextProps.css
css: nextProps.css,
dynamic: nextProps.dynamic
})
}
@ -30,13 +46,16 @@ export default class extends Component {
function stylesMap(updated) {
const ret = new Map()
for (const c of components) {
if (updated && c === updated.instance) {
// On `componentWillUpdate`
// we use `styleId` and `css` from updated component rather than reading `props`
// from the component since they haven't been updated yet.
ret.set(updated.styleId, updated.css)
// On `componentWillUpdate`
// we use `styleId` and `css` from updated component rather than reading `props`
// from the component since they haven't been updated yet.
const props = updated && c === updated.instance ? updated : c.props
if (props.dynamic) {
const styleId = `${props.styleId}-${hashArray(props.dynamic)}`
ret.set(styleId, scopeCss(styleId, props.css))
} else {
ret.set(c.props.styleId, c.props.css)
ret.set(props.styleId, props.css)
}
}
return ret
@ -66,3 +85,7 @@ function unmount(component) {
function update(updates) {
render(stylesMap(updates))
}
function scopeCss(id, css) {
return css.replace(/\[data-jsx~="\?"]/g, `[data-jsx~="${id}"]`)
}

24
test/__snapshots__/index.js.snap

@ -92,21 +92,21 @@ const animationName = 'my-cool-animation';
const obj = { display: 'block' };
// eslint-disable-next-line no-unused-vars
export default (({ display }) => <div data-jsx={\`2129566164 188072295 806016056 806016056 924167211 924167211 3469794077 945380644 3617592140 2369334310 3168033860 \${_JSXStyle.dynamic([['2799014000', [display ? 'block' : 'none']]])}\`}>
<p data-jsx={\`2129566164 188072295 806016056 806016056 924167211 924167211 3469794077 945380644 3617592140 2369334310 3168033860 \${_JSXStyle.dynamic([['2799014000', [display ? 'block' : 'none']]])}\`}>test</p>
<_JSXStyle styleId={\\"2129566164\\"} css={\`p.\${color}[data-jsx~=\\"2129566164\\"]{color:\${otherColor};display:\${obj.display}}\`} />
export default (({ display }) => <div data-jsx={\`3864570373 188072295 964903971 964903971 2993630608 2993630608 2683050534 1376588607 3617592140 4197553757 3032198101 \${_JSXStyle.dynamic([['3588394506', [display ? 'block' : 'none']]])}\`}>
<p data-jsx={\`3864570373 188072295 964903971 964903971 2993630608 2993630608 2683050534 1376588607 3617592140 4197553757 3032198101 \${_JSXStyle.dynamic([['3588394506', [display ? 'block' : 'none']]])}\`}>test</p>
<_JSXStyle styleId={\\"3864570373\\"} css={\`p.\${color}[data-jsx~=\\"3864570373\\"]{color:\${otherColor};display:\${obj.display}}\`} />
<_JSXStyle styleId={\\"188072295\\"} css={\\"p[data-jsx~=\\\\\\"188072295\\\\\\"]{color:red}\\"} />
<_JSXStyle styleId={\\"806016056\\"} css={\`body{background:\${color}}\`} />
<_JSXStyle styleId={\\"806016056\\"} css={\`body{background:\${color}}\`} />
<_JSXStyle styleId={\\"924167211\\"} css={\`p[data-jsx~=\\"924167211\\"]{color:\${color}}\`} />
<_JSXStyle styleId={\\"924167211\\"} css={\`p[data-jsx~=\\"924167211\\"]{color:\${color}}\`} />
<_JSXStyle styleId={\\"3469794077\\"} css={\`p[data-jsx~=\\"3469794077\\"]{color:\${darken(color)}}\`} />
<_JSXStyle styleId={\\"945380644\\"} css={\`p[data-jsx~=\\"945380644\\"]{color:\${darken(color) + 2}}\`} />
<_JSXStyle styleId={\\"964903971\\"} css={\`body{background:\${color}}\`} />
<_JSXStyle styleId={\\"964903971\\"} css={\`body{background:\${color}}\`} />
<_JSXStyle styleId={\\"2993630608\\"} css={\`p[data-jsx~=\\"2993630608\\"]{color:\${color}}\`} />
<_JSXStyle styleId={\\"2993630608\\"} css={\`p[data-jsx~=\\"2993630608\\"]{color:\${color}}\`} />
<_JSXStyle styleId={\\"2683050534\\"} css={\`p[data-jsx~=\\"2683050534\\"]{color:\${darken(color)}}\`} />
<_JSXStyle styleId={\\"1376588607\\"} css={\`p[data-jsx~=\\"1376588607\\"]{color:\${darken(color) + 2}}\`} />
<_JSXStyle styleId={\\"3617592140\\"} css={\`@media (min-width:\${mediumScreen}){p[data-jsx~=\\"3617592140\\"]{color:green}p[data-jsx~=\\"3617592140\\"]{color:\${\`red\`}}}p[data-jsx~=\\"3617592140\\"]{color:red}\`} />
<_JSXStyle styleId={\\"2369334310\\"} css={\`p[data-jsx~=\\"2369334310\\"]{-webkit-animation-duration:\${animationDuration};animation-duration:\${animationDuration}}\`} />
<_JSXStyle styleId={\\"3168033860\\"} css={\`p[data-jsx~=\\"3168033860\\"]{-webkit-animation:\${animationDuration} forwards \${animationName};animation:\${animationDuration} forwards \${animationName}}\`} />
<_JSXStyle styleId={\\"4197553757\\"} css={\`p[data-jsx~=\\"4197553757\\"]{-webkit-animation-duration:\${animationDuration};animation-duration:\${animationDuration}}\`} />
<_JSXStyle styleId={\\"3032198101\\"} css={\`p[data-jsx~=\\"3032198101\\"]{-webkit-animation:\${animationDuration} forwards \${animationName};animation:\${animationDuration} forwards \${animationName}}\`} />
<_JSXStyle styleId={\\"2799014000\\"} css={\`span[data-jsx~=\\"?\\"]{display:\${display ? 'block' : 'none'}}\`} dynamic={[display ? 'block' : 'none']} />
<_JSXStyle styleId={\\"3588394506\\"} css={\`span[data-jsx~=\\"?\\"]{display:\${display ? 'block' : 'none'}}\`} dynamic={[display ? 'block' : 'none']} />
</div>);"
`;

Loading…
Cancel
Save