Skip to content

Commit

Permalink
fix: fixes jsx opening/closing created elements blowing up with const…
Browse files Browse the repository at this point in the history
…/let
  • Loading branch information
itsdouges committed Dec 31, 2019
1 parent e0828de commit 8e30b7c
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 54 deletions.
4 changes: 2 additions & 2 deletions examples/class-names-dynamic-object.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export default {
title: 'class names dynamic object',
};

export var objectLiteral = () => {
var [color, setColor] = useState('blue');
export const objectLiteral = () => {
const [color, setColor] = useState('blue');

return (
<div>
Expand Down
2 changes: 1 addition & 1 deletion examples/class-names-static-object.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default {
title: 'class names static object',
};

export var objectLiteral = () => (
export const objectLiteral = () => (
<ClassNames>
{({ css }) => <div className={css({ fontSize: '30px' })}>hello world</div>}
</ClassNames>
Expand Down
6 changes: 3 additions & 3 deletions examples/css-prop-dynamic-object.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ export default {
title: 'css prop dynamic object',
};

export var dynamicCssProp = () => {
// works with var, but not let or const ????
var [color, setColor] = useState('red');
export const dynamicCssProp = () => {
// works with const, but not let or const ????
const [color, setColor] = useState('red');

return (
<div
Expand Down
14 changes: 7 additions & 7 deletions examples/css-prop-static-object.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@
import { jsx } from '../src';
import { hover } from './mixins/mixins';

var inlineMixinFunc = () => ({
const inlineMixinFunc = () => ({
color: 'red',
});

var inlineMixinObj = {
const inlineMixinObj = {
color: 'green',
};

export default {
title: 'css prop static object',
};

export var objectLiteral = () => {
export const objectLiteral = () => {
return <div css={{ display: 'flex', fontSize: '50px', color: 'blue' }}>Hello, world!</div>;
};

export var objectLiteralSpreadFromFunc = () => {
export const objectLiteralSpreadFromFunc = () => {
return (
<div
css={{
Expand All @@ -32,7 +32,7 @@ export var objectLiteralSpreadFromFunc = () => {
);
};

export var objectLiteralSpreadFromObj = () => {
export const objectLiteralSpreadFromObj = () => {
return (
<div
css={{
Expand All @@ -46,7 +46,7 @@ export var objectLiteralSpreadFromObj = () => {
);
};

export var objectLiteralLocalObj = () => {
export const objectLiteralLocalObj = () => {
return (
<div
css={{
Expand All @@ -60,7 +60,7 @@ export var objectLiteralLocalObj = () => {
);
};

export var objectLiteralImportedObj = () => {
export const objectLiteralImportedObj = () => {
return (
<div
css={{
Expand Down
6 changes: 3 additions & 3 deletions examples/styled-dynamic-object.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default {
title: 'styled component dynamic object',
};

var Highlight = styled.div<{ primary: string }>({
const Highlight = styled.div<{ primary: string }>({
fontSize: '20px',
color: props => props.primary,
margin: '20px',
Expand All @@ -14,8 +14,8 @@ var Highlight = styled.div<{ primary: string }>({
},
});

export var objectLiteral = () => {
var [color, setColor] = useState('blue');
export const objectLiteral = () => {
const [color, setColor] = useState('blue');

return (
<>
Expand Down
4 changes: 2 additions & 2 deletions examples/styled-static-object.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ export default {
title: 'styled component static object',
};

var Thing = styled.div({
const Thing = styled.div({
fontSize: '20px',
color: 'red',
});

export var objectLiteral = () => <Thing>hello world</Thing>;
export const objectLiteral = () => <Thing>hello world</Thing>;
29 changes: 18 additions & 11 deletions src/ts-transformer/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
import { Transformer } from 'ts-transformer-testing-library';
import { rawTransformers } from '../index';
import * as ts from 'typescript';
import rootTransformer from '../index';
import pkg from '../../../package.json';

const transformer = new Transformer()
.addTransformers(rawTransformers)
.addMock({ name: pkg.name, content: `export const jsx: any = () => null` })
.setFilePath('/index.tsx');

describe('root transformer', () => {
it('should not blow up when transforming with const', () => {
const transformer = rootTransformer({} as ts.Program, {});

expect(() => {
transformer.transform(
ts.transpileModule(
`
/** @jsx jsx */
import { jsx } from '${pkg.name}';
const MyComponent = () => <div css={{ fontSize: '20px' }}>hello world</div>
`
`,
{
transformers: { before: transformer },
compilerOptions: { module: ts.ModuleKind.ESNext, jsx: ts.JsxEmit.React },
}
);
}).not.toThrow();
});

it('should not blow up when transforming with var', () => {
const transformer = rootTransformer({} as ts.Program, {});

expect(() => {
transformer.transform(
ts.transpileModule(
`
/** @jsx jsx */
import { jsx } from '${pkg.name}';
var MyComponent = () => <div css={{ fontSize: '20px' }}>hello world</div>
`
`,
{
transformers: { before: transformer },
compilerOptions: { module: ts.ModuleKind.ESNext, jsx: ts.JsxEmit.React },
}
);
}).not.toThrow();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const visitClassNamesJsxElement = (
selector: '',
css,
cssVariables,
originalNode: classNamesNode,
children: children as any,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -97,20 +97,31 @@ export const visitJsxElementWithCssProp = (

// Create the style element that will precede the node that had the css prop.
const styleNode = ts.createJsxElement(
ts.createJsxOpeningElement(ts.createIdentifier('style'), [], ts.createJsxAttributes([])),
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(
ts.createJsxOpeningElement(ts.createIdentifier('style'), [], ts.createJsxAttributes([])),
node
),
[ts.createJsxText(compiledCss)],
ts.createJsxClosingElement(ts.createIdentifier('style'))
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(ts.createJsxClosingElement(ts.createIdentifier('style')), node)
);

// Create a new fragment that will wrap both the style and the node we found initially.
const newFragmentParent = ts.createJsxFragment(
ts.createJsxOpeningFragment(),
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(ts.createJsxOpeningFragment(), node),
[
// important that the style goes before the node
styleNode,
nodeToTransform,
],
ts.createJsxJsxClosingFragment()
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(ts.createJsxJsxClosingFragment(), node)
);

logger.log('returning fragment with style and parsed jsx element with css prop');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@ export const visitObjectStyledComponent = (

const result = objectLiteralToCssString(objectLiteral, {}, context);

const newElement = createJsxElement(tagNode, {
...result,
children: ts.createJsxExpression(undefined, ts.createIdentifier('props.children')),
});
const newElement = createJsxElement(
tagNode,
{
...result,
originalNode: node,
children: ts.createJsxExpression(undefined, ts.createIdentifier('props.children')),
},
node
);

return ts.createArrowFunction(
undefined,
Expand Down
67 changes: 50 additions & 17 deletions src/ts-transformer/utils/create-jsx-element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,31 @@ import { CssVariableExpressions } from '../types';
interface JsxElementOpts {
css: string;
cssVariables: CssVariableExpressions[];
originalNode: ts.Node;
selector?: string;
children?: ts.JsxChild;
}

export const createStyleFragment = ({
selector = `.${nextClassName()}`,
originalNode,
...opts
}: JsxElementOpts) => {
const compiledCss = stylis(selector, opts.css);

// Create the style element that will precede the node that had the css prop.
const styleNode = ts.createJsxElement(
ts.createJsxOpeningElement(ts.createIdentifier('style'), [], ts.createJsxAttributes([])),
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(
ts.createJsxOpeningElement(ts.createIdentifier('style'), [], ts.createJsxAttributes([])),
originalNode
),
// should this be text or an jsx expression?
[ts.createJsxText(compiledCss)],
ts.createJsxClosingElement(ts.createIdentifier('style'))
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(ts.createJsxClosingElement(ts.createIdentifier('style')), originalNode)
);

const children: ts.JsxChild[] = [
Expand All @@ -32,37 +41,57 @@ export const createStyleFragment = ({

// Create a new fragment that will wrap both the style and the node we found initially.
const newFragmentParent = ts.createJsxFragment(
ts.createJsxOpeningFragment(),
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(ts.createJsxOpeningFragment(), originalNode),
children.concat(opts.children ? opts.children : []),
ts.createJsxJsxClosingFragment()
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(ts.createJsxJsxClosingFragment(), originalNode)
);

return newFragmentParent;
};

export const createJsxElement = (tagNode: string, opts: JsxElementOpts) => {
export const createJsxElement = (tagNode: string, opts: JsxElementOpts, originalNode: ts.Node) => {
const className = nextClassName();
const compiledCss = stylis(`.${className}`, opts.css);

// Create the style element that will precede the node that had the css prop.
const styleNode = ts.createJsxElement(
ts.createJsxOpeningElement(ts.createIdentifier('style'), [], ts.createJsxAttributes([])),
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(
ts.createJsxOpeningElement(ts.createIdentifier('style'), [], ts.createJsxAttributes([])),
originalNode
),
// should this be text or an jsx expression?
[ts.createJsxText(compiledCss)],
ts.createJsxClosingElement(ts.createIdentifier('style'))
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(ts.createJsxClosingElement(ts.createIdentifier('style')), originalNode)
);

const elementNode = ts.createJsxElement(
// todo: we need to set types and shit depending on props used
ts.createJsxOpeningElement(
ts.createIdentifier(tagNode),
[],
ts.createJsxAttributes([
ts.createJsxAttribute(ts.createIdentifier('className'), ts.createStringLiteral(className)),
])
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(
ts.createJsxOpeningElement(
ts.createIdentifier(tagNode),
[],
ts.createJsxAttributes([
ts.createJsxAttribute(
ts.createIdentifier('className'),
ts.createStringLiteral(className)
),
])
),
originalNode
),
opts.children ? [opts.children] : [],
ts.createJsxClosingElement(ts.createIdentifier(tagNode))
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(ts.createJsxClosingElement(ts.createIdentifier(tagNode)), originalNode)
);

if (opts.cssVariables.length) {
Expand All @@ -89,13 +118,17 @@ export const createJsxElement = (tagNode: string, opts: JsxElementOpts) => {

// Create a new fragment that will wrap both the style and the node we found initially.
const newFragmentParent = ts.createJsxFragment(
ts.createJsxOpeningFragment(),
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(ts.createJsxOpeningFragment(), originalNode),
[
// important that the style goes before the node
styleNode,
elementNode,
],
ts.createJsxJsxClosingFragment()
// We use setOriginalNode() here to work around createJsx not working without the original node.
// See: https://github.com/microsoft/TypeScript/issues/35686
ts.setOriginalNode(ts.createJsxJsxClosingFragment(), originalNode)
);

return newFragmentParent;
Expand Down

0 comments on commit 8e30b7c

Please sign in to comment.