Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dynamic value support for extract static in the styled function. #279

Closed
wants to merge 14 commits into from
Closed
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -51,7 +51,7 @@
"module-alias": "^2.0.1",
"npm-run-all": "^4.0.2",
"polished": "^1.2.1",
"prettier-eslint-cli": "^4.0.3",
"prettier-eslint-cli": "^4.2.1",
"react": "^15.5.4",
"react-addons-test-utils": "^15.5.1",
"react-dom": "^15.5.4",
Expand Down
27 changes: 23 additions & 4 deletions packages/babel-plugin-emotion/src/index.js
Expand Up @@ -144,21 +144,40 @@ const getComponentId = (state, prefix: string = 'css') => {
export function buildStyledCallExpression(identifier, tag, path, state, t) {
const identifierName = getIdentifierName(path, t)

if (state.extractStatic && !path.node.quasi.expressions.length) {
if (state.extractStatic) {
const { name, hash, src } = inline(
path.node.quasi,
identifierName,
'styled' // we don't want these styles to be merged in css``
)

const cssText = `.${name}-${hash} { ${src} }`
const { staticCSSRules } = parseCSS(cssText, true, getFilename(path))
const { staticCSSRules, composesCount } = parseCSS(
cssText,
true,
getFilename(path)
)

// Help Needed:
// We should read the browser preferences for postcss
// and turn this on only if css vars are supported in their browser targets.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

@ai ai Aug 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detection:

browserslist(undefined, { path: processingFileName }).some(i => i.slice(0, 2) === 'ie')

It will detect IE and IE mobile.

const objs = path.node.quasi.expressions.slice(0, composesCount)

if (objs.length) {
throw new Error('Cannot use composes in extractStatic mode.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tkh44 Do you think we should just go to inline mode instead of throwing an error. We also need to check if there are any interpolations in other places before going to extract.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that might be a good idea.

}

state.insertStaticRules(staticCSSRules)
const vars = path.node.quasi.expressions.slice(composesCount)
state.insertStaticRules(
staticCSSRules.map(ruleText =>
ruleText.replace(/xxx(\d+)xxx/gm, `var(--${name}-${hash}-$1)`)
)
)
return t.callExpression(identifier, [
tag,
t.stringLiteral(getComponentId(state, name)),
t.arrayExpression([t.stringLiteral(`${name}-${hash}`)])
t.arrayExpression([t.stringLiteral(`${name}-${hash}`)]),
t.arrayExpression(vars)
])
}

Expand Down
10 changes: 4 additions & 6 deletions packages/babel-plugin-emotion/src/parser.js
Expand Up @@ -35,7 +35,7 @@ export function parseCSS(
} else {
root = parse(css, { from: filename })
}
let vars = 0

let composes: number = 0

root.walkDecls((decl: Decl): void => {
Expand All @@ -52,7 +52,6 @@ export function parseCSS(
const composeMatches = decl.value.match(/xxx(\d+)xxx/gm)
const numOfComposes: number = !composeMatches ? 0 : composeMatches.length
composes += numOfComposes
vars += numOfComposes
decl.remove()
}
})
Expand All @@ -63,10 +62,9 @@ export function parseCSS(

return {
styles,
staticCSSRules:
vars === 0 && extractStatic
? stringifyCSSRoot(postcssJs.parse(styles))
: [],
staticCSSRules: extractStatic
? stringifyCSSRoot(postcssJs.parse(styles))
: [],
composesCount: composes
}
}
Expand Down
Expand Up @@ -2,7 +2,7 @@

exports[`babel styled component extract basic 1`] = `
"import \\"./styled.test.emotion.css\\";
const H1 = styled(\\"h1\\", \\"styled-H1-1qcxcvu0\\", [\\"styled-H1-10x82eg\\"]);"
const H1 = styled(\\"h1\\", \\"styled-H1-1qcxcvu0\\", [\\"styled-H1-10x82eg\\"], []);"
`;

exports[`babel styled component extract basic 2`] = `
Expand Down Expand Up @@ -37,11 +37,46 @@ exports[`babel styled component extract basic 2`] = `

exports[`babel styled component extract no use 1`] = `
"import \\"./styled.test.emotion.css\\";
styled(\\"h1\\", \\"styled-yoisj70\\", [\\"styled-0\\"]);"
styled(\\"h1\\", \\"styled-yoisj70\\", [\\"styled-0\\"], []);"
`;

exports[`babel styled component extract no use 2`] = `".styled-0 {}"`;

exports[`babel styled component extract with interpolation 1`] = `
"import \\"./styled.test.emotion.css\\";
const H1 = styled(\\"h1\\", \\"styled-H1-i6cu680\\", [\\"styled-H1-9acweo\\"], [p => p.justify]);"
`;

exports[`babel styled component extract with interpolation 2`] = `
".styled-H1-9acweo {
display: -webkit-box; display: -ms-flexbox; display: flex;
-webkit-box-pack: var(--styled-H1-9acweo-0);
-ms-flex-pack: var(--styled-H1-9acweo-0);
justify-content: var(--styled-H1-9acweo-0);
width: var(--css-hash-0)
}
.styled-H1-9acweo:hover {
background-color: green
}
@media (max-width: 500px) {
.styled-H1-9acweo {
height: var(--css-hash-1);
position: fixed
}
}
@media print {
.styled-H1-9acweo {
display: none
}
}
.styled-H1-9acweo::before {
color: blue;
width: 20px;
height: 20px;
content: 'pseudo'
}"
`;

exports[`babel styled component inline basic 1`] = `
"const H1 = /*#__PURE__*/styled('h1', 'css-H1-uutogn0', [], [fontSize + 'px'], function createEmotionStyledRules(x0) {
return {
Expand Down
14 changes: 14 additions & 0 deletions packages/babel-plugin-emotion/test/styled.test.js
Expand Up @@ -353,5 +353,19 @@ describe('babel styled component', () => {
expect(fs.writeFileSync).toHaveBeenCalledTimes(2)
expect(fs.writeFileSync.mock.calls[1][1]).toMatchSnapshot()
})

test('with interpolation', () => {
const basic =
"const H1 = styled.h1`display: flex; justify-content: ${p => p.justify}; width: var(--css-hash-0); &:hover { background-color: green; } @media (max-width: 500px) { height: var(--css-hash-1); position: fixed; } @media print { display: none; } &::before { color: blue; width: 20px; height: 20px; content: 'pseudo' }`"
const {code} = babel.transform(basic, {
plugins: [[plugin, {extractStatic: true}]],
filename: __filename,
babelrc: false
})

expect(code).toMatchSnapshot()
expect(fs.writeFileSync).toHaveBeenCalledTimes(3)
expect(fs.writeFileSync.mock.calls[2][1]).toMatchSnapshot()
})
})
})
30 changes: 23 additions & 7 deletions packages/emotion/test/extract/__snapshots__/extract.test.js.snap
Expand Up @@ -2,27 +2,40 @@

exports[`styled basic render nested 1`] = `
<h1
className="styled-H1-1f87tx4 styled-H1-1sjwp4j1"
className="styled-H1-1f87tx4 styled-H1-r6dkuc1"
>
hello world
</h1>
`;
`

exports[`styled name 1`] = `
<h1
className="styled-H1-131pfth styled-H1-1sjwp4j2"
className="styled-H1-131pfth styled-H1-r6dkuc3"
>
hello world
</h1>
`;
`

exports[`styled no dynamic 1`] = `
<h1
className="styled-H1-ijh7uz styled-H1-1sjwp4j0"
className="styled-H1-ijh7uz styled-H1-r6dkuc0"
>
hello world
</h1>
`;
`

exports[`styled with expressions 1`] = `
<h1
className="styled-H1-1t8i2zo styled-H1-r6dkuc2"
style={
Object {
"styled-H1-1t8i2zo-0": 24,
}
}
>
hello world
</h1>
`

exports[`styled writes the correct css 1`] = `
".styled-H1-ijh7uz {
Expand All @@ -40,6 +53,9 @@ exports[`styled writes the correct css 1`] = `
.styled-H1-1f87tx4 span:hover:after {
content: \\"after\\"
}
.styled-H1-1t8i2zo {
font-size: var(--styled-H1-1t8i2zo-0)
}
.styled-H1-131pfth {
name: FancyH1;
font-size: 38px
Expand All @@ -52,4 +68,4 @@ html {
color: yellow;
background-color: purple
}"
`;
`
3 changes: 3 additions & 0 deletions packages/emotion/test/extract/extract.test.emotion.css
Expand Up @@ -13,6 +13,9 @@
.styled-H1-1f87tx4 span:hover:after {
content: "after"
}
.styled-H1-1t8i2zo {
font-size: var(--styled-H1-1t8i2zo-0)
}
.styled-H1-131pfth {
name: FancyH1;
font-size: 38px
Expand Down
8 changes: 8 additions & 0 deletions packages/emotion/test/extract/extract.test.js
Expand Up @@ -36,6 +36,14 @@ describe('styled', () => {
expect(tree).toMatchSnapshot()
})

test('with expressions', () => {
const H1 = styled.h1`font-size: ${p => p.fontSize};`

const tree = renderer.create(<H1 fontSize={24}>hello world</H1>).toJSON()

expect(tree).toMatchSnapshot()
})

test('name', () => {
const H1 = styled.h1`
name: FancyH1;
Expand Down
30 changes: 26 additions & 4 deletions packages/react-emotion/src/index.js
Expand Up @@ -18,6 +18,8 @@ export default function(tag, cls, objs, vars = [], content) {
)
}

const FAST_PATH = vars && vars.length && !content

const componentTag = tag.displayName || tag.name || 'Component'
const spec = {
vars,
Expand Down Expand Up @@ -73,10 +75,30 @@ export default function(tag, cls, objs, vars = [], content) {
return h(
localTag,
omit(
assign({}, props, {
ref: props.innerRef,
className
}),
assign(
{},
props,
{
ref: props.innerRef,
className
},
FAST_PATH && {
style: assign(
{},
props.style,
FAST_PATH
? reduce(
map(vars, getValue),
(accum, value, i) => {
accum[`${objs[0]}-${i}`] = value
return accum
},
{}
)
: null
)
}
),
omitFn
)
)
Expand Down