From b3805a0d9bf93a5c7157a436df2146eb55321cd6 Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Thu, 29 Dec 2016 17:05:28 +0100 Subject: [PATCH 1/6] Add support for expressions within template literals --- lib/style-transform.js | 2 +- src/babel.js | 87 +++++++++++++++++++++++--------- test/fixtures/expressions.js | 14 +++++ test/fixtures/expressions.out.js | 12 +++++ test/index.js | 14 ++++- 5 files changed, 102 insertions(+), 27 deletions(-) create mode 100644 test/fixtures/expressions.js create mode 100644 test/fixtures/expressions.out.js diff --git a/lib/style-transform.js b/lib/style-transform.js index 22402467..2e70fbd5 100644 --- a/lib/style-transform.js +++ b/lib/style-transform.js @@ -74,7 +74,7 @@ } // {, }, ; characters - if (code === 123 || code === 125 || code === 59) { + if (styles.charAt(i-1) !== '$' && (code === 123 || code === 125 || code === 59)) { line += styles[i]; var first = line.charCodeAt(0); diff --git a/src/babel.js b/src/babel.js index 34cb6ae4..1b80a141 100644 --- a/src/babel.js +++ b/src/babel.js @@ -3,6 +3,7 @@ import jsx from 'babel-plugin-syntax-jsx' import hash from 'string-hash' import {SourceMapGenerator} from 'source-map' import convert from 'convert-source-map' +import {transform as parse} from 'babel-core' // Ours import transform from '../lib/style-transform' @@ -17,23 +18,56 @@ const STYLE_COMPONENT_CSS = 'css' export default function ({types: t}) { const findStyles = children => ( children.filter(el => ( - t.isJSXElement(el) && - el.openingElement.name.name === 'style' && - el.openingElement.attributes.some(attr => ( + t.isJSXElement(el.node) && + el.node.openingElement.name.name === 'style' && + el.node.openingElement.attributes.some(attr => ( attr.name.name === STYLE_ATTRIBUTE )) )) ) - const getExpressionText = expr => ( - t.isTemplateLiteral(expr) ? - expr.quasis[0].value.raw : - // assume string literal - expr.value - ) + const getExpressionText = expr => { + const node = expr.node + + // assuming string literal + if (t.isStringLiteral(node)) { + return node.value + } + + const expressions = node.expressions + + // simple template literal without expressions + if (expressions.length === 0) { + return node.quasis[0].value.cooked + } + + const errors = expressions.reduce((errors, expression) => { + if ( + t.isArrowFunctionExpression(expression) || + t.isFunctionExpression(expression) + ) { + errors.push(`styled-jsx cannot contain function expressions:\n${expr.getSource()}`) + } + return errors + }, []) - const makeStyledJsxTag = (id, transformedCss) => ( - t.JSXElement( + if (errors.length > 0) { + throw expr.buildCodeFrameError(`\n${errors.join('\n')}`) + } + + // strip out ` and return the template literal source + return expr.getSource().slice(1, -1) + } + + const makeStyledJsxTag = (id, transformedCss, isTemplateLiteral) => { + let css + if (isTemplateLiteral) { + // build the expression from transformedCss + css = parse(`\`${transformedCss}\``).ast.program.body[0].expression + } else { + css = t.stringLiteral(transformedCss) + } + return t.JSXElement( t.JSXOpeningElement( t.JSXIdentifier(STYLE_COMPONENT), [ @@ -43,7 +77,7 @@ export default function ({types: t}) { ), t.JSXAttribute( t.JSXIdentifier(STYLE_COMPONENT_CSS), - t.JSXExpressionContainer(t.stringLiteral(transformedCss)) + t.JSXExpressionContainer(css) ) ], true @@ -51,7 +85,7 @@ export default function ({types: t}) { null, [] ) - ) + } return { inherits: jsx, @@ -97,7 +131,7 @@ export default function ({types: t}) { return } - const styles = findStyles(path.node.children) + const styles = findStyles(path.get('children')) if (styles.length === 0) { if (state.file.hasJSXStyle) { @@ -110,10 +144,10 @@ export default function ({types: t}) { for (const style of styles) { // compute children excluding whitespace - const children = style.children.filter(c => ( - t.isJSXExpressionContainer(c) || + const children = style.get('children').filter(c => ( + t.isJSXExpressionContainer(c.node) || // ignore whitespace around the expression container - (t.isJSXText(c) && c.value.trim() !== '') + (t.isJSXText(c.node) && c.node.value.trim() !== '') )) if (children.length !== 1) { @@ -130,23 +164,24 @@ export default function ({types: t}) { `(eg: ), got ${child.type}`) } - const expression = child.expression + const expression = child.node.expression - if (!t.isTemplateLiteral(child.expression) && - !t.isStringLiteral(child.expression)) { + if (!t.isTemplateLiteral(expression) && + !t.isStringLiteral(expression)) { throw path.buildCodeFrameError(`Expected a template ` + `literal or String literal as the child of the ` + `JSX Style tag (eg: ),` + ` but got ${expression.type}`) } - const styleText = getExpressionText(expression) + const styleText = getExpressionText(child.get('expression')) const styleId = hash(styleText) state.styles.push([ styleId, styleText, - expression.loc + expression.loc, + t.isTemplateLiteral(expression) && expression.expressions.length > 0 ]) } @@ -171,14 +206,14 @@ export default function ({types: t}) { } // we replace styles with the function call - const [id, css, loc] = state.styles.shift() + const [id, css, loc, isTemplateLiteral] = state.styles.shift() const isGlobal = el.attributes.some(attr => ( attr.name.name === GLOBAL_ATTRIBUTE )) if (isGlobal) { - path.replaceWith(makeStyledJsxTag(id, css)) + path.replaceWith(makeStyledJsxTag(id, css, isTemplateLiteral)) return } @@ -203,7 +238,9 @@ export default function ({types: t}) { transformedCss = transform(state.jsxId, css) } - path.replaceWith(makeStyledJsxTag(id, transformedCss)) + path.replaceWith( + makeStyledJsxTag(id, transformedCss, isTemplateLiteral) + ) } }, Program: { diff --git a/test/fixtures/expressions.js b/test/fixtures/expressions.js new file mode 100644 index 00000000..e710dbcc --- /dev/null +++ b/test/fixtures/expressions.js @@ -0,0 +1,14 @@ +const color = 'red' + +export default () => ( +
+

test

+ + + + + + +
+) + diff --git a/test/fixtures/expressions.out.js b/test/fixtures/expressions.out.js new file mode 100644 index 00000000..562590d2 --- /dev/null +++ b/test/fixtures/expressions.out.js @@ -0,0 +1,12 @@ +import _JSXStyle from 'styled-jsx/style'; +const color = 'red'; + +export default (() =>
+

test

+ <_JSXStyle styleId={188072295} css={"p[data-jsx=\"1748646287\"] {color: red }"} /> + <_JSXStyle styleId={188072295} css={"p[data-jsx=\"1748646287\"] {color: red }"} /> + <_JSXStyle styleId={806016056} css={`body { background: ${ color } }`} /> + <_JSXStyle styleId={924167211} css={`p[data-jsx="1748646287"] {color: ${ color }}`} /> + <_JSXStyle styleId={3469794077} css={`p[data-jsx="1748646287"] {color: ${ darken(color) }}`} /> + <_JSXStyle styleId={945380644} css={`p[data-jsx="1748646287"] {color: ${ darken(color) + 2 }}`} /> +
); diff --git a/test/index.js b/test/index.js index 977b0f16..145aae96 100644 --- a/test/index.js +++ b/test/index.js @@ -76,8 +76,15 @@ test('works with multiple jsx blocks', async t => { t.is(code, out.trim()) }) +test('works with expressions', async t => { + const {code} = await transform('./fixtures/expressions.js') + const out = await read('./fixtures/expressions.out.js') + t.is(code, out.trim()) +}) + test('server rendering', t => { function App() { + const color = 'green' return React.createElement('div', null, React.createElement(JSXStyle, { css: 'p { color: red }', @@ -86,13 +93,18 @@ test('server rendering', t => { React.createElement(JSXStyle, { css: 'div { color: blue }', styleId: 2 + }), + React.createElement(JSXStyle, { + css: `div { color: ${color} }`, + styleId: 3 }) ) } // expected CSS const expected = '' + - '' + '' + + '' // render using react ReactDOM.renderToString(React.createElement(App)) From ef8a01cf5e9177c13abef30d553aa0f270925840 Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Fri, 30 Dec 2016 18:12:29 +0100 Subject: [PATCH 2/6] Add support for expressions in selectors and media queries --- lib/style-transform.js | 2 +- src/babel.js | 80 ++++++++++++++++++++++++++------ test/fixtures/expressions.js | 12 ++++- test/fixtures/expressions.out.js | 17 ++++--- 4 files changed, 88 insertions(+), 23 deletions(-) diff --git a/lib/style-transform.js b/lib/style-transform.js index 2e70fbd5..22402467 100644 --- a/lib/style-transform.js +++ b/lib/style-transform.js @@ -74,7 +74,7 @@ } // {, }, ; characters - if (styles.charAt(i-1) !== '$' && (code === 123 || code === 125 || code === 59)) { + if (code === 123 || code === 125 || code === 59) { line += styles[i]; var first = line.charCodeAt(0); diff --git a/src/babel.js b/src/babel.js index 1b80a141..24ec0da7 100644 --- a/src/babel.js +++ b/src/babel.js @@ -3,7 +3,7 @@ import jsx from 'babel-plugin-syntax-jsx' import hash from 'string-hash' import {SourceMapGenerator} from 'source-map' import convert from 'convert-source-map' -import {transform as parse} from 'babel-core' +import {transform as parse, traverse} from 'babel-core' // Ours import transform from '../lib/style-transform' @@ -34,7 +34,7 @@ export default function ({types: t}) { return node.value } - const expressions = node.expressions + const expressions = expr.get('expressions') // simple template literal without expressions if (expressions.length === 0) { @@ -55,18 +55,60 @@ export default function ({types: t}) { throw expr.buildCodeFrameError(`\n${errors.join('\n')}`) } - // strip out ` and return the template literal source - return expr.getSource().slice(1, -1) + // Special treatment for template literals that contain expressions: + // + // Expressions are replaced with a placeholder + // so that the CSS compiler can parse and + // transform the css source string + // without having to know about js literal expressions. + // Later expressions are restored + // by doing a replacement on the transformed css string. + // + // e.g. + // p { color: ${myConstant}; } + // becomes + // p { color: ___styledjsxexpression0___; } + + const replacements = expressions.map((e, id) => ({ + replacement: `___styledjsxexpression${id}___`, + initial: `$\{${e.getSource()}}` + })) + + const source = expr.getSource().slice(1, -1) + + const modified = replacements.reduce((source, currentReplacement) => { + source = source.replace( + currentReplacement.initial, + currentReplacement.replacement + ) + return source + }, source) + + return { + source, + modified, + replacements + } } const makeStyledJsxTag = (id, transformedCss, isTemplateLiteral) => { let css if (isTemplateLiteral) { // build the expression from transformedCss - css = parse(`\`${transformedCss}\``).ast.program.body[0].expression + traverse( + parse(`\`${transformedCss}\``).ast, + { + TemplateLiteral(path) { + if (!css) { + css = path.node + } + } + } + ) } else { css = t.stringLiteral(transformedCss) } + return t.JSXElement( t.JSXOpeningElement( t.JSXIdentifier(STYLE_COMPONENT), @@ -175,13 +217,12 @@ export default function ({types: t}) { } const styleText = getExpressionText(child.get('expression')) - const styleId = hash(styleText) + const styleId = hash(styleText.source || styleText) state.styles.push([ styleId, styleText, - expression.loc, - t.isTemplateLiteral(expression) && expression.expressions.length > 0 + expression.loc ]) } @@ -206,14 +247,14 @@ export default function ({types: t}) { } // we replace styles with the function call - const [id, css, loc, isTemplateLiteral] = state.styles.shift() + const [id, css, loc] = state.styles.shift() const isGlobal = el.attributes.some(attr => ( attr.name.name === GLOBAL_ATTRIBUTE )) if (isGlobal) { - path.replaceWith(makeStyledJsxTag(id, css, isTemplateLiteral)) + path.replaceWith(makeStyledJsxTag(id, css.source || css, css.modified)) return } @@ -228,18 +269,31 @@ export default function ({types: t}) { }) generator.setSourceContent(filename, state.file.code) transformedCss = [ - transform(state.jsxId, css, generator, loc.start, filename), + transform(state.jsxId, css.modified || css, generator, loc.start, filename), convert .fromObject(generator) .toComment({multiline: true}), `/*@ sourceURL=${filename} */` ].join('\n') } else { - transformedCss = transform(state.jsxId, css) + transformedCss = transform(state.jsxId, css.modified || css) + } + + if (css.modified) { + transformedCss = css.replacements.reduce( + (transformedCss, currentReplacement) => { + transformedCss = transformedCss.replace( + currentReplacement.replacement, + currentReplacement.initial + ) + return transformedCss + }, + transformedCss + ) } path.replaceWith( - makeStyledJsxTag(id, transformedCss, isTemplateLiteral) + makeStyledJsxTag(id, transformedCss, css.modified) ) } }, diff --git a/test/fixtures/expressions.js b/test/fixtures/expressions.js index e710dbcc..3743fb00 100644 --- a/test/fixtures/expressions.js +++ b/test/fixtures/expressions.js @@ -1,14 +1,22 @@ const color = 'red' +const otherColor = 'green' +const mediumScreen = '680px' export default () => (

test

- + +
) - diff --git a/test/fixtures/expressions.out.js b/test/fixtures/expressions.out.js index 562590d2..cc317c4d 100644 --- a/test/fixtures/expressions.out.js +++ b/test/fixtures/expressions.out.js @@ -1,12 +1,15 @@ import _JSXStyle from 'styled-jsx/style'; const color = 'red'; +const otherColor = 'green'; +const mediumScreen = '680px'; -export default (() =>
-

test

- <_JSXStyle styleId={188072295} css={"p[data-jsx=\"1748646287\"] {color: red }"} /> - <_JSXStyle styleId={188072295} css={"p[data-jsx=\"1748646287\"] {color: red }"} /> +export default (() =>
+

test

+ <_JSXStyle styleId={414042974} css={`p.${ color }[data-jsx="2520901095"] {color: ${ otherColor } }`} /> + <_JSXStyle styleId={188072295} css={"p[data-jsx=\"2520901095\"] {color: red }"} /> <_JSXStyle styleId={806016056} css={`body { background: ${ color } }`} /> - <_JSXStyle styleId={924167211} css={`p[data-jsx="1748646287"] {color: ${ color }}`} /> - <_JSXStyle styleId={3469794077} css={`p[data-jsx="1748646287"] {color: ${ darken(color) }}`} /> - <_JSXStyle styleId={945380644} css={`p[data-jsx="1748646287"] {color: ${ darken(color) + 2 }}`} /> + <_JSXStyle styleId={924167211} css={`p[data-jsx="2520901095"] {color: ${ color } }`} /> + <_JSXStyle styleId={3469794077} css={`p[data-jsx="2520901095"] {color: ${ darken(color) } }`} /> + <_JSXStyle styleId={945380644} css={`p[data-jsx="2520901095"] {color: ${ darken(color) + 2 } }`} /> + <_JSXStyle styleId={4106311606} css={`@media (min-width: ${ mediumScreen }) {p[data-jsx="2520901095"] {color: green }p[data-jsx="2520901095"] {color ${ `red` }}}p[data-jsx="2520901095"] {color: red }`} />
); From fdc518cfbabd0dd19b27ce635f6b2d4445134902 Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Fri, 30 Dec 2016 18:19:24 +0100 Subject: [PATCH 3/6] Replace longer expressions first to avoid substring replacements --- src/babel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/babel.js b/src/babel.js index 24ec0da7..fd3cb657 100644 --- a/src/babel.js +++ b/src/babel.js @@ -72,7 +72,7 @@ export default function ({types: t}) { const replacements = expressions.map((e, id) => ({ replacement: `___styledjsxexpression${id}___`, initial: `$\{${e.getSource()}}` - })) + })).sort((a, b) => a.initial.length < b.initial.length) const source = expr.getSource().slice(1, -1) From 9f6c4511dd115c91487eefdfac4e6fc43a3fcbef Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Fri, 30 Dec 2016 19:10:56 +0100 Subject: [PATCH 4/6] Destructure param in findStyles --- src/babel.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/babel.js b/src/babel.js index fd3cb657..1ad359d1 100644 --- a/src/babel.js +++ b/src/babel.js @@ -17,10 +17,10 @@ const STYLE_COMPONENT_CSS = 'css' export default function ({types: t}) { const findStyles = children => ( - children.filter(el => ( - t.isJSXElement(el.node) && - el.node.openingElement.name.name === 'style' && - el.node.openingElement.attributes.some(attr => ( + children.filter(({node}) => ( + t.isJSXElement(node) && + node.openingElement.name.name === 'style' && + node.openingElement.attributes.some(attr => ( attr.name.name === STYLE_ATTRIBUTE )) )) From f622fcbe6a17a337e5904a70ef602d27867ff3e1 Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Sun, 1 Jan 2017 23:15:47 +0100 Subject: [PATCH 5/6] Throw upon usage of vars from the closure --- src/babel.js | 67 +++++++++++++++++++------- test/fixtures/invalid-expressions/1.js | 8 +++ test/fixtures/invalid-expressions/2.js | 9 ++++ test/fixtures/invalid-expressions/3.js | 8 +++ test/fixtures/invalid-expressions/4.js | 20 ++++++++ test/index.js | 12 ++++- 6 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 test/fixtures/invalid-expressions/1.js create mode 100644 test/fixtures/invalid-expressions/2.js create mode 100644 test/fixtures/invalid-expressions/3.js create mode 100644 test/fixtures/invalid-expressions/4.js diff --git a/src/babel.js b/src/babel.js index 1ad359d1..e293427a 100644 --- a/src/babel.js +++ b/src/babel.js @@ -26,10 +26,45 @@ export default function ({types: t}) { )) ) + // 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). + const validateExpressionVisitor = { + MemberExpression(path) { + const {node} = path + if ( + t.isThisExpression(node.object) && + t.isIdentifier(node.property) && + ( + node.property.name === 'props' || + node.property.name === 'state' + ) + ) { + throw path.buildCodeFrameError( + `Expected a constant ` + + `as part of the template literal expression ` + + `(eg: ), ` + + `but got a MemberExpression: this.${node.property.name}`) + } + }, + Identifier(path, scope) { + const {name} = path.node + if (scope.hasOwnBinding(name)) { + throw path.buildCodeFrameError( + `Expected \`${name}\` ` + + `to not come from the closest scope.\n` + + `Styled JSX encourages the use of constants ` + + `instead of \`props\` or dynamic values ` + + `which are better set via inline styles or \`className\` toggling. ` + + `See https://github.com/zeit/styled-jsx#dynamic-styles`) + } + } + } + const getExpressionText = expr => { const node = expr.node - // assuming string literal + // assume string literal if (t.isStringLiteral(node)) { return node.value } @@ -41,20 +76,6 @@ export default function ({types: t}) { return node.quasis[0].value.cooked } - const errors = expressions.reduce((errors, expression) => { - if ( - t.isArrowFunctionExpression(expression) || - t.isFunctionExpression(expression) - ) { - errors.push(`styled-jsx cannot contain function expressions:\n${expr.getSource()}`) - } - return errors - }, []) - - if (errors.length > 0) { - throw expr.buildCodeFrameError(`\n${errors.join('\n')}`) - } - // Special treatment for template literals that contain expressions: // // Expressions are replaced with a placeholder @@ -184,6 +205,12 @@ export default function ({types: t}) { state.styles = [] + const scope = (path.findParent(path => ( + path.isFunctionDeclaration() || + path.isArrowFunctionExpression() || + path.isClassMethod() + )) || path).scope + for (const style of styles) { // compute children excluding whitespace const children = style.get('children').filter(c => ( @@ -206,7 +233,7 @@ export default function ({types: t}) { `(eg: ), got ${child.type}`) } - const expression = child.node.expression + const expression = child.get('expression') if (!t.isTemplateLiteral(expression) && !t.isStringLiteral(expression)) { @@ -216,13 +243,17 @@ export default function ({types: t}) { ` but got ${expression.type}`) } - const styleText = getExpressionText(child.get('expression')) + // Validate MemberExpressions and Identifiers + // to ensure that are constants not defined in the closest scope + child.get('expression').traverse(validateExpressionVisitor, scope) + + const styleText = getExpressionText(expression) const styleId = hash(styleText.source || styleText) state.styles.push([ styleId, styleText, - expression.loc + expression.node.loc ]) } diff --git a/test/fixtures/invalid-expressions/1.js b/test/fixtures/invalid-expressions/1.js new file mode 100644 index 00000000..080f525d --- /dev/null +++ b/test/fixtures/invalid-expressions/1.js @@ -0,0 +1,8 @@ +export const Test = (p) => { + return ( +
+

test

+ +
+ ) +} diff --git a/test/fixtures/invalid-expressions/2.js b/test/fixtures/invalid-expressions/2.js new file mode 100644 index 00000000..575240ee --- /dev/null +++ b/test/fixtures/invalid-expressions/2.js @@ -0,0 +1,9 @@ +export function Test(props) { + const {darken} = props + return ( +
+

test

+ +
+ ) +} diff --git a/test/fixtures/invalid-expressions/3.js b/test/fixtures/invalid-expressions/3.js new file mode 100644 index 00000000..154f18ff --- /dev/null +++ b/test/fixtures/invalid-expressions/3.js @@ -0,0 +1,8 @@ +export function Test({color}) { + return ( +
+

test

+ +
+ ) +} diff --git a/test/fixtures/invalid-expressions/4.js b/test/fixtures/invalid-expressions/4.js new file mode 100644 index 00000000..b8244790 --- /dev/null +++ b/test/fixtures/invalid-expressions/4.js @@ -0,0 +1,20 @@ +export class Test { + test() { + const aaaa = 'red' + return ( +
+

test

+ +
+ ) + } + + render() { + return ( +
+

test

+ +
+ ) + } +} diff --git a/test/index.js b/test/index.js index 145aae96..18eff1a9 100644 --- a/test/index.js +++ b/test/index.js @@ -76,12 +76,22 @@ test('works with multiple jsx blocks', async t => { t.is(code, out.trim()) }) -test('works with expressions', async t => { +test('works with expressions in template literals', async t => { const {code} = await transform('./fixtures/expressions.js') const out = await read('./fixtures/expressions.out.js') t.is(code, out.trim()) }) +test('throws when using `props` or constants ' + + 'defined in the closest scope', async t => { + [1, 2, 3, 4].forEach(i => { + t.throws( + transform(`./fixtures/invalid-expressions/${i}.js`), + SyntaxError + ) + }) +}) + test('server rendering', t => { function App() { const color = 'green' From 643d853f61faa68c58b9c529a9035591810e2aec Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Tue, 3 Jan 2017 22:20:38 +0100 Subject: [PATCH 6/6] Use babylon and babel-traverse instead of babel-core --- package.json | 2 ++ src/babel.js | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 43f13ba7..862d6463 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ ], "dependencies": { "babel-plugin-syntax-jsx": "^6.18.0", + "babel-traverse": "^6.21.0", + "babylon": "^6.14.1", "convert-source-map": "^1.3.0", "object.entries": "^1.0.4", "source-map": "^0.5.6", diff --git a/src/babel.js b/src/babel.js index 68093681..f149c8fa 100644 --- a/src/babel.js +++ b/src/babel.js @@ -3,7 +3,8 @@ import jsx from 'babel-plugin-syntax-jsx' import hash from 'string-hash' import {SourceMapGenerator} from 'source-map' import convert from 'convert-source-map' -import {transform as parse, traverse} from 'babel-core' +import traverse from 'babel-traverse' +import {parse} from 'babylon' // Ours import transform from '../lib/style-transform' @@ -117,7 +118,7 @@ export default function ({types: t}) { if (isTemplateLiteral) { // build the expression from transformedCss traverse( - parse(`\`${transformedCss}\``).ast, + parse(`\`${transformedCss}\``), { TemplateLiteral(path) { if (!css) {