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
102 changes: 81 additions & 21 deletions packages/babel-plugin-emotion/src/index.js
Expand Up @@ -30,20 +30,60 @@ export function replaceCssWithCallExpression(
removePath = false
) {
try {
const { name, hash, src } = inline(
path.node.quasi,
getIdentifierName(path, t),
'css'
)
if (state.extractStatic && !path.node.quasi.expressions.length) {
const cssText = staticCSSTextCreator(name, hash, src)
const { staticCSSRules } = parseCSS(cssText, true, getFilename(path))
const identifierName = getIdentifierName(path, t)
const { name, hash, src } = inline(path.node.quasi, identifierName, 'css')
if (state.extractStatic) {
if (
// we should probably change this
staticCSSTextCreator('css', 'hash', 'thing') === '.css-hash { thing }'
) {
const { name, hash, src } = inline(
path.node.quasi,
identifierName,
'vars' // we don't want these styles to be merged in css``
)

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

state.insertStaticRules(staticCSSRules)
if (!removePath) {
return path.replaceWith(t.stringLiteral(`${name}-${hash}`))
// Help Needed:
// We should read the browser preferences for postcss
// and turn this on only if css vars are supported in their browser targets.
let canUseCssVariables = true

if (
varCount === path.node.quasi.expressions.length &&
canUseCssVariables &&
path.node.quasi.expressions.length !== 0
) {
path.addComment('leading', '#__PURE__')
state.insertStaticRules(
staticCSSRules.map(ruleText =>
ruleText.replace(/xxx(\d+)xxx/gm, `var(--${name}-${hash}-$1)`)
)
)
return path.replaceWith(
t.callExpression(identifier, [
t.arrayExpression([t.stringLiteral(`${name}-${hash}`)]),
t.arrayExpression(path.node.quasi.expressions)
])
)
}
}
if (path.node.quasi.expressions.length === 0) {
const cssText = staticCSSTextCreator(name, hash, src)
const { staticCSSRules } = parseCSS(cssText, true, getFilename(path))

state.insertStaticRules(staticCSSRules)
if (!removePath) {
return path.replaceWith(t.stringLiteral(`${name}-${hash}`))
}
return path.replaceWith(t.identifier('undefined'))
}
return path.replaceWith(t.identifier('undefined'))
}

const { styles, composesCount } = parseCSS(src, false, getFilename(path))
Expand Down Expand Up @@ -144,22 +184,42 @@ 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))

state.insertStaticRules(staticCSSRules)
return t.callExpression(identifier, [
tag,
t.stringLiteral(getComponentId(state, name)),
t.arrayExpression([t.stringLiteral(`${name}-${hash}`)])
])
const { staticCSSRules, varCount } = 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.


let canUseCssVariables = true

if (
(varCount === path.node.quasi.expressions.length && canUseCssVariables) ||
path.node.quasi.expressions.length === 0
) {
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(path.node.quasi.expressions)
])
}
}

const { src, name } = inline(path.node.quasi, identifierName, 'css')
Expand Down
46 changes: 27 additions & 19 deletions packages/babel-plugin-emotion/src/parser.js
Expand Up @@ -35,25 +35,33 @@ export function parseCSS(
} else {
root = parse(css, { from: filename })
}
let vars = 0
let composes: number = 0

let composes: number = 0
let varCount = 0
root.walkDecls((decl: Decl): void => {
if (decl.prop === 'composes') {
if (!/xxx(\d+)xxx/gm.exec(decl.value)) {
throw new Error('composes must be a interpolation')
if (!extractStatic) {
if (!/xxx(\d+)xxx/gm.exec(decl.value)) {
throw new Error('composes must be a interpolation')
}
if (decl.parent.nodes[0] !== decl) {
throw new Error('composes must be the first rule')
}
if (decl.parent.type !== 'root') {
throw new Error('composes cannot be on nested selectors')
}
const composeMatches = decl.value.match(/xxx(\d+)xxx/gm)
const numOfComposes: number = !composeMatches
? 0
: composeMatches.length
composes += numOfComposes
decl.remove()
}
if (decl.parent.nodes[0] !== decl) {
throw new Error('composes must be the first rule')
}
if (decl.parent.type !== 'root') {
throw new Error('composes cannot be on nested selectors')
} else {
const match = decl.value.match(/xxx(?:\d+)xxx/gm)
if (match && decl.prop[0] !== '$') {
varCount += match.length
}
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,11 +71,11 @@ export function parseCSS(

return {
styles,
staticCSSRules:
vars === 0 && extractStatic
? stringifyCSSRoot(postcssJs.parse(styles))
: [],
composesCount: composes
staticCSSRules: extractStatic
? stringifyCSSRoot(postcssJs.parse(styles))
: [],
composesCount: composes,
varCount
}
}

Expand Down
19 changes: 13 additions & 6 deletions packages/babel-plugin-emotion/test/__snapshots__/css.test.js.snap
Expand Up @@ -63,12 +63,6 @@ const cls2 = /*#__PURE__*/css([cls1], [], function createEmotionStyledRules() {
});"
`;

exports[`babel css extract composes with objects 2`] = `
".css-cls1-1q8jsgx {
display: -webkit-box; display: -ms-flexbox; display: flex
}"
`;

exports[`babel css extract css basic 1`] = `
"import \\"./css.test.emotion.css\\";

Expand All @@ -86,6 +80,19 @@ exports[`babel css extract css basic 2`] = `
}"
`;

exports[`babel css extract css with dynamic values 1`] = `
"import \\"./css.test.emotion.css\\";

/*#__PURE__*/css([\\"vars-bnojx5\\"], [color]);"
`;

exports[`babel css extract css with dynamic values 2`] = `
".vars-bnojx5 {
margin: 12px 48px;
color: var(--vars-bnojx5-0)
}"
`;

exports[`babel css extract dynamic property objects 1`] = `
"
css({
Expand Down
Expand Up @@ -13,6 +13,7 @@ Object {
"width": "var(--css-hash-0)",
},
},
"varCount": 0,
}
`;

Expand Down Expand Up @@ -45,6 +46,7 @@ Object {
"width": "var(--css-hash-0)",
},
},
"varCount": 0,
}
`;

Expand All @@ -65,5 +67,6 @@ Object {
"width": "30px",
},
},
"varCount": 0,
}
`;