Skip to content

Commit

Permalink
Add support for dynamic html elements (#462)
Browse files Browse the repository at this point in the history
Adds support for cases where the html tag is dynamic:

const Heading = `h${props.level}`
<Heading>{props.children}<style jsx>{`h1 { color: red }`}</style></Heading>
  • Loading branch information
twltwl authored and giuseppeg committed Jul 18, 2018
1 parent a55c821 commit 8d9374a
Show file tree
Hide file tree
Showing 11 changed files with 264 additions and 78 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
language: node_js
node_js:
- "6"
- "8"
- "10"
7 changes: 6 additions & 1 deletion src/babel.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,16 @@ export default function({ types: t }) {
state.ignoreClosing = 0
}

const tag = path.get('name')

if (
name &&
name !== 'style' &&
name !== STYLE_COMPONENT &&
name.charAt(0) !== name.charAt(0).toUpperCase()
(name.charAt(0) !== name.charAt(0).toUpperCase() ||
Object.values(path.scope.bindings).some(binding =>
binding.referencePaths.some(r => r === tag)
))
) {
if (state.className) {
addClassName(path, state.className)
Expand Down
80 changes: 43 additions & 37 deletions test/__snapshots__/attribute.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -100,41 +100,47 @@ exports[`rewrites className 1`] = `
"var _this = this;
import _JSXStyle from \\"styled-jsx/style\\";
export default (() => <div className={\\"jsx-2886504620\\"}>
<div {...test.test} className={\\"jsx-2886504620\\" + \\" \\" + (test.test.className != null && test.test.className || \\"test\\")} />
<div {...test.test.test} className={\\"jsx-2886504620\\" + \\" \\" + (test.test.test.className != null && test.test.test.className || \\"test\\")} />
<div {..._this.test.test} className={\\"jsx-2886504620\\" + \\" \\" + (_this.test.test.className != null && _this.test.test.className || \\"test\\")} />
<div data-test=\\"test\\" className={\\"jsx-2886504620\\"} />
<div className={\\"jsx-2886504620\\" + \\" \\" + \\"test\\"} />
<div className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div className={\\"jsx-2886504620\\" + \\" \\" + \`test\`} />
<div className={\\"jsx-2886504620\\" + \\" \\" + \`test\${true ? ' test2' : ''}\`} />
<div className={\\"jsx-2886504620\\" + \\" \\" + ('test ' + test || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (['test', 'test2'].join(' ') || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (true && 'test' || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + ((test ? 'test' : null) || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (test || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (test && 'test' || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (test && test('test') || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (undefined || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (null || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (false || \\"\\")} />
<div data-test className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div data-test className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div data-test=\\"test\\" className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || 'test')} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || props.className != null && props.className || 'test')} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || \`test \${test ? 'test' : ''}\`)} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || test && test('test') || \\"\\")} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || test && test('test') && 'test' || \\"\\")} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || test && test('test') && test2('test') || \\"\\")} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || 'test')} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || \\"\\")} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || props.className != null && props.className || \\"\\")} />
<div {...props} data-foo {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || props.className != null && props.className || \\"\\")} />
<div {...props} data-foo {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || 'test')} />
<_JSXStyle styleId={\\"2886504620\\"} css={\\"div.jsx-2886504620{color:red;}\\"} />
</div>);"
export default (() => {
const Element = 'div';
return <div className={\\"jsx-2886504620\\"}>
<div {...test.test} className={\\"jsx-2886504620\\" + \\" \\" + (test.test.className != null && test.test.className || \\"test\\")} />
<div {...test.test.test} className={\\"jsx-2886504620\\" + \\" \\" + (test.test.test.className != null && test.test.test.className || \\"test\\")} />
<div {..._this.test.test} className={\\"jsx-2886504620\\" + \\" \\" + (_this.test.test.className != null && _this.test.test.className || \\"test\\")} />
<div data-test=\\"test\\" className={\\"jsx-2886504620\\"} />
<div className={\\"jsx-2886504620\\" + \\" \\" + \\"test\\"} />
<div className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div className={\\"jsx-2886504620\\" + \\" \\" + \`test\`} />
<div className={\\"jsx-2886504620\\" + \\" \\" + \`test\${true ? ' test2' : ''}\`} />
<div className={\\"jsx-2886504620\\" + \\" \\" + ('test ' + test || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (['test', 'test2'].join(' ') || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (true && 'test' || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + ((test ? 'test' : null) || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (test || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (test && 'test' || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (test && test('test') || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (undefined || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (null || \\"\\")} />
<div className={\\"jsx-2886504620\\" + \\" \\" + (false || \\"\\")} />
<div data-test className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div data-test className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div data-test=\\"test\\" className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || 'test')} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || props.className != null && props.className || 'test')} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || \`test \${test ? 'test' : ''}\`)} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || test && test('test') || \\"\\")} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || test && test('test') && 'test' || \\"\\")} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || test && test('test') && test2('test') || \\"\\")} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + 'test'} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || 'test')} />
<div {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || \\"\\")} />
<div {...props} {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || props.className != null && props.className || \\"\\")} />
<div {...props} data-foo {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || props.className != null && props.className || \\"\\")} />
<div {...props} data-foo {...rest} className={\\"jsx-2886504620\\" + \\" \\" + (rest.className != null && rest.className || 'test')} />
<Element className={\\"jsx-2886504620\\"} />
<Element className={\\"jsx-2886504620\\" + \\" \\" + \\"test\\"} />
<Element {...props} className={\\"jsx-2886504620\\" + \\" \\" + (props.className != null && props.className || \\"\\")} />
<_JSXStyle styleId={\\"2886504620\\"} css={\\"div.jsx-2886504620{color:red;}\\"} />
</div>;
});"
`;
14 changes: 14 additions & 0 deletions test/__snapshots__/external.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,20 @@ export default {
};"
`;
exports[`use external stylesheet and dynamic element 1`] = `
"import _JSXStyle from \\"styled-jsx/style\\";
import styles from './styles2';
export default (({ level = 1 }) => {
const Element = \`h\${level}\`;
return <Element className={\`jsx-\${styles.__scopedHash}\` + \\" \\" + \\"root\\"}>
<p className={\`jsx-\${styles.__scopedHash}\`}>dynamic element</p>
<_JSXStyle styleId={styles.__scopedHash} css={styles.__scoped} />
</Element>;
});"
`;
exports[`use external stylesheets (global only) 1`] = `
"import _JSXStyle from 'styled-jsx/style';
import styles, { foo as styles3 } from './styles';
Expand Down
53 changes: 53 additions & 0 deletions test/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,59 @@ class Test extends React.Component {
}"
`;
exports[`works with dynamic element 1`] = `
"import _JSXStyle from \\"styled-jsx/style\\";
export default (({ level = 1 }) => {
const Element = \`h\${level}\`;
return <Element className={\\"jsx-1253978709\\" + \\" \\" + \\"root\\"}>
<p className={\\"jsx-1253978709\\"}>dynamic element</p>
<_JSXStyle styleId={\\"1253978709\\"} css={\\".root.jsx-1253978709{background:red;}\\"} />
</Element>;
});
export const TestLowerCase = ({ level = 1 }) => {
const element = \`h\${level}\`;
return <element className={\\"jsx-1253978709\\" + \\" \\" + \\"root\\"}>
<p className={\\"jsx-1253978709\\"}>dynamic element</p>
<_JSXStyle styleId={\\"1253978709\\"} css={\\".root.jsx-1253978709{background:red;}\\"} />
</element>;
};
const Element2 = 'div';
export const Test2 = () => {
return <Element2 className=\\"root\\">
<p className={\\"jsx-1253978709\\"}>dynamic element</p>
<_JSXStyle styleId={\\"1253978709\\"} css={\\".root.jsx-1253978709{background:red;}\\"} />
</Element2>;
};"
`;
exports[`works with dynamic element in class 1`] = `
"import _JSXStyle from 'styled-jsx/style';
export default class {
render() {
const Element = 'div';
return <Element className={'jsx-1800172487' + ' ' + 'root'}>
<p className={\\"jsx-1800172487\\"}>dynamic element</p>
<_JSXStyle styleId={\\"1800172487\\"} css={\\".root.jsx-1800172487{background:red;}\\"} />
</Element>;
}
}
const Element2 = 'div';
export const Test2 = class {
render() {
return <Element2 className=\\"root\\">
<p className={\\"jsx-1800172487\\"}>dynamic element</p>
<_JSXStyle styleId={\\"1800172487\\"} css={\\".root.jsx-1800172487{background:red;}\\"} />
</Element2>;
}
};"
`;
exports[`works with expressions in template literals 1`] = `
"import _JSXStyle from 'styled-jsx/style';
const darken = c => c;
Expand Down
5 changes: 5 additions & 0 deletions test/external.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,8 @@ test('injects JSXStyle for nested scope', async t => {
`)
t.snapshot(code)
})

test('use external stylesheet and dynamic element', async t => {
const { code } = await transform('./fixtures/dynamic-element-external.js')
t.snapshot(code)
})
84 changes: 45 additions & 39 deletions test/fixtures/attribute-generation-classname-rewriting.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,45 @@
export default () => (
<div>
<div className="test" {...test.test} />
<div className="test" {...test.test.test} />
<div className="test" {...this.test.test} />
<div data-test="test" />
<div className="test" />
<div className={'test'} />
<div className={`test`} />
<div className={`test${true ? ' test2' : ''}`} />
<div className={'test ' + test} />
<div className={['test', 'test2'].join(' ')} />
<div className={true && 'test'} />
<div className={test ? 'test' : null} />
<div className={test} />
<div className={test && 'test'} />
<div className={test && test('test')} />
<div className={undefined} />
<div className={null} />
<div className={false} />
<div className={'test'} data-test />
<div data-test className={'test'} />
<div className={'test'} data-test="test" />
<div className={'test'} {...props} />
<div className={'test'} {...props} {...rest} />
<div className={`test ${test ? 'test' : ''}`} {...props} />
<div className={test && test('test')} {...props} />
<div className={test && test('test') && 'test'} {...props} />
<div className={test && test('test') && test2('test')} {...props} />
<div {...props} className={'test'} />
<div {...props} {...rest} className={'test'} />
<div {...props} className={'test'} {...rest} />
<div {...props} />
<div {...props} {...rest} />
<div {...props} data-foo {...rest} />
<div {...props} className={'test'} data-foo {...rest} />
<style jsx>{'div { color: red }'}</style>
</div>
)
export default () => {
const Element = 'div'
return (
<div>
<div className="test" {...test.test} />
<div className="test" {...test.test.test} />
<div className="test" {...this.test.test} />
<div data-test="test" />
<div className="test" />
<div className={'test'} />
<div className={`test`} />
<div className={`test${true ? ' test2' : ''}`} />
<div className={'test ' + test} />
<div className={['test', 'test2'].join(' ')} />
<div className={true && 'test'} />
<div className={test ? 'test' : null} />
<div className={test} />
<div className={test && 'test'} />
<div className={test && test('test')} />
<div className={undefined} />
<div className={null} />
<div className={false} />
<div className={'test'} data-test />
<div data-test className={'test'} />
<div className={'test'} data-test="test" />
<div className={'test'} {...props} />
<div className={'test'} {...props} {...rest} />
<div className={`test ${test ? 'test' : ''}`} {...props} />
<div className={test && test('test')} {...props} />
<div className={test && test('test') && 'test'} {...props} />
<div className={test && test('test') && test2('test')} {...props} />
<div {...props} className={'test'} />
<div {...props} {...rest} className={'test'} />
<div {...props} className={'test'} {...rest} />
<div {...props} />
<div {...props} {...rest} />
<div {...props} data-foo {...rest} />
<div {...props} className={'test'} data-foo {...rest} />
<Element />
<Element className="test" />
<Element {...props} />
<style jsx>{'div { color: red }'}</style>
</div>
)
}
32 changes: 32 additions & 0 deletions test/fixtures/dynamic-element-class.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export default class {
render() {
const Element = 'div'

return (
<Element className="root">
<p>dynamic element</p>
<style jsx>{`
.root {
background: red;
}
`}</style>
</Element>
)
}
}

const Element2 = 'div'
export const Test2 = class {
render() {
return (
<Element2 className="root">
<p>dynamic element</p>
<style jsx>{`
.root {
background: red;
}
`}</style>
</Element2>
)
}
}
12 changes: 12 additions & 0 deletions test/fixtures/dynamic-element-external.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import styles from './styles2'

export default ({ level = 1 }) => {
const Element = `h${level}`

return (
<Element className="root">
<p>dynamic element</p>
<style jsx>{styles}</style>
</Element>
)
}
43 changes: 43 additions & 0 deletions test/fixtures/dynamic-element.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export default ({ level = 1 }) => {
const Element = `h${level}`

return (
<Element className="root">
<p>dynamic element</p>
<style jsx>{`
.root {
background: red;
}
`}</style>
</Element>
)
}

export const TestLowerCase = ({ level = 1 }) => {
const element = `h${level}`

return (
<element className="root">
<p>dynamic element</p>
<style jsx>{`
.root {
background: red;
}
`}</style>
</element>
)
}

const Element2 = 'div'
export const Test2 = () => {
return (
<Element2 className="root">
<p>dynamic element</p>
<style jsx>{`
.root {
background: red;
}
`}</style>
</Element2>
)
}
10 changes: 10 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ test('works with css tagged template literals in the same file', async t => {
t.snapshot(code)
})

test('works with dynamic element', async t => {
const { code } = await transform('./fixtures/dynamic-element.js')
t.snapshot(code)
})

test('works with dynamic element in class', async t => {
const { code } = await transform('./fixtures/dynamic-element-class.js')
t.snapshot(code)
})

test('does not transpile nested style tags', async t => {
const { message } = await t.throws(
transform('./fixtures/nested-style-tags.js')
Expand Down

0 comments on commit 8d9374a

Please sign in to comment.