From b3805a0d9bf93a5c7157a436df2146eb55321cd6 Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Thu, 29 Dec 2016 17:05:28 +0100 Subject: [PATCH 1/9] 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/9] 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/9] 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/9] 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/9] 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/9] 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) { From 630c92a4fa312b2bbee18a9871f8ad23f66d870f Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Thu, 19 Jan 2017 10:27:12 +0100 Subject: [PATCH 7/9] findStyles should return a path --- src/babel.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/babel.js b/src/babel.js index 9e9ea5db..99bf7098 100644 --- a/src/babel.js +++ b/src/babel.js @@ -33,12 +33,10 @@ export default function ({types: t}) { if (isStyledJsx(path)) { const {node} = path return isGlobalEl(node.openingElement) ? - [node] : [] + [path] : [] } - return path.get('children') - .filter(isStyledJsx) - .map(({node}) => node) + return path.get('children').filter(isStyledJsx) } // We only allow constants to be used in template literals. From 2a161090a8d9b6f34b957753ec76cf617c68e9d3 Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Thu, 19 Jan 2017 11:30:22 +0100 Subject: [PATCH 8/9] Refactor benchmark and add expressions --- benchmark/babel.js | 36 +++++++++++++------- benchmark/fixtures/{babel.js => basic.js} | 0 benchmark/fixtures/with-expressions.js | 41 +++++++++++++++++++++++ 3 files changed, 64 insertions(+), 13 deletions(-) rename benchmark/fixtures/{babel.js => basic.js} (100%) create mode 100644 benchmark/fixtures/with-expressions.js diff --git a/benchmark/babel.js b/benchmark/babel.js index b5d1694a..2e36a13f 100644 --- a/benchmark/babel.js +++ b/benchmark/babel.js @@ -1,20 +1,30 @@ import {readFileSync} from 'fs' import {resolve} from 'path' -import Benchmark from 'benchmark' +import {Suite} from 'benchmark' import {transform as babel} from 'babel-core' import plugin from '../src/babel' -const read = path => readFileSync(resolve(__dirname, path), 'utf8') -const fixture = read('./fixtures/babel.js') +const makeTransform = fixturePath => { + const fixture = readFileSync( + resolve(__dirname, fixturePath), + 'utf8' + ) -module.exports = new Benchmark({ - name: 'Babel transform', - minSamples: 500, - fn: () => { - babel(fixture, { - babelrc: false, - plugins: [plugin] - }) - } -}) + return () => babel(fixture, { + babelrc: false, + plugins: [plugin] + }) +} + +const benchs = { + basic: makeTransform('./fixtures/basic.js'), + withExpressions: makeTransform('./fixtures/with-expressions.js') +} + +const suite = new Suite('style-jsx Babel transform') + +module.exports = + suite + .add('basic', benchs.basic) + .add('with expressions', benchs.withExpressions) diff --git a/benchmark/fixtures/babel.js b/benchmark/fixtures/basic.js similarity index 100% rename from benchmark/fixtures/babel.js rename to benchmark/fixtures/basic.js diff --git a/benchmark/fixtures/with-expressions.js b/benchmark/fixtures/with-expressions.js new file mode 100644 index 00000000..c686bd87 --- /dev/null +++ b/benchmark/fixtures/with-expressions.js @@ -0,0 +1,41 @@ +const c = 'red' +const color = i => i + +export const Test1 = () => ( +
+ test + test +

+

+

+

+

+

+ + +
+) + +export const Test2 = () => test + +export default class { + render() { + return ( +
+

test

+ + + +
+ ) + } +} From fdfbf95a05efe419781e544238a815945edee535 Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Thu, 19 Jan 2017 11:33:49 +0100 Subject: [PATCH 9/9] Fix typo --- benchmark/babel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/babel.js b/benchmark/babel.js index 2e36a13f..ad875792 100644 --- a/benchmark/babel.js +++ b/benchmark/babel.js @@ -22,7 +22,7 @@ const benchs = { withExpressions: makeTransform('./fixtures/with-expressions.js') } -const suite = new Suite('style-jsx Babel transform') +const suite = new Suite('styled-jsx Babel transform') module.exports = suite