diff --git a/packages/macro/src/__snapshots__/macro.spec.ts.snap b/packages/macro/src/__snapshots__/macro.spec.ts.snap
index 61022b8..d39f22d 100644
--- a/packages/macro/src/__snapshots__/macro.spec.ts.snap
+++ b/packages/macro/src/__snapshots__/macro.spec.ts.snap
@@ -56,6 +56,248 @@ css('Button', {
});
+`;
+
+exports[`@trousers/macro correctly interpolates evaluations (BinaryExpression): correctly interpolates evaluations (BinaryExpression) 1`] = `
+
+import { css } from './macro';
+const styles = css('Button', { color: 5+5 });
+
+const App = () => ;
+
+ ↓ ↓ ↓ ↓ ↓ ↓
+
+/** @jsx jsx */
+import { css, jsx } from '@trousers/macro/runtime';
+const styles = css('Button', {
+ '.Button-4214914708': 'color: var(--interpol0);',
+});
+
+const App = () =>
+ /*#__PURE__*/ React.createElement('button', {
+ css: styles,
+ styles: {
+ '--interpol0': 5 + 5,
+ },
+ });
+
+
+`;
+
+exports[`@trousers/macro correctly interpolates functions (CallExpression): correctly interpolates functions (CallExpression) 1`] = `
+
+import { css } from './macro';
+
+const styles = css('Button', { color: foo() });
+
+const App = () => ;
+
+ ↓ ↓ ↓ ↓ ↓ ↓
+
+/** @jsx jsx */
+import { css, jsx } from '@trousers/macro/runtime';
+const styles = css('Button', {
+ '.Button-4214914708': 'color: var(--interpol0);',
+});
+
+const App = () =>
+ /*#__PURE__*/ React.createElement('button', {
+ css: styles,
+ styles: {
+ '--interpol0': foo(),
+ },
+ });
+
+
+`;
+
+exports[`@trousers/macro correctly interpolates reused styles: correctly interpolates reused styles 1`] = `
+
+import { css } from './macro';
+const foo = 'blue';
+const styles = css('Button', { color: foo });
+
+const App = () => (
+
+);
+
+ ↓ ↓ ↓ ↓ ↓ ↓
+
+/** @jsx jsx */
+import { css, jsx } from '@trousers/macro/runtime';
+const foo = 'blue';
+const styles = css('Button', {
+ '.Button-4214914708': 'color: var(--interpol0);',
+});
+
+const App = () =>
+ /*#__PURE__*/ React.createElement(
+ 'button',
+ {
+ css: styles,
+ styles: {
+ '--interpol0': foo,
+ },
+ },
+ /*#__PURE__*/ React.createElement(
+ 'span',
+ {
+ css: styles,
+ styles: {
+ '--interpol0': foo,
+ },
+ },
+ 'Hello, World!',
+ ),
+ );
+
+
+`;
+
+exports[`@trousers/macro correctly interpolates styles used by nested elements: correctly interpolates styles used by nested elements 1`] = `
+
+import { css } from './macro';
+const foo = 'blue';
+const bar = 'green';
+const styles = css('Button', { color: foo });
+const innerStyles = css('ButtonInner', { color: bar });
+
+const App = () => (
+
+);
+
+ ↓ ↓ ↓ ↓ ↓ ↓
+
+/** @jsx jsx */
+import { css, jsx } from '@trousers/macro/runtime';
+const foo = 'blue';
+const bar = 'green';
+const styles = css('Button', {
+ '.Button-4214914708': 'color: var(--interpol0);',
+});
+const innerStyles = css('ButtonInner', {
+ '.ButtonInner-4214944499': 'color: var(--interpol1);',
+});
+
+const App = () =>
+ /*#__PURE__*/ React.createElement(
+ 'button',
+ {
+ css: styles,
+ styles: {
+ '--interpol0': foo,
+ },
+ },
+ /*#__PURE__*/ React.createElement(
+ 'span',
+ {
+ css: innerStyles,
+ styles: {
+ '--interpol1': bar,
+ },
+ },
+ 'Hello, World!',
+ ),
+ );
+
+
+`;
+
+exports[`@trousers/macro correctly interpolates styles used by sibling elements: correctly interpolates styles used by sibling elements 1`] = `
+
+import { css } from './macro';
+const foo = 'blue';
+const bar = 'green';
+const styles = css('Button', { color: foo });
+const siblingStyles = css('ButtonInner', { color: bar });
+
+const App = () => (
+
+
+ Hello, World!
+
+
+
+);
+
+ ↓ ↓ ↓ ↓ ↓ ↓
+
+/** @jsx jsx */
+import { css, jsx } from '@trousers/macro/runtime';
+const foo = 'blue';
+const bar = 'green';
+const styles = css('Button', {
+ '.Button-4214914708': 'color: var(--interpol0);',
+});
+const siblingStyles = css('ButtonInner', {
+ '.ButtonInner-4214944499': 'color: var(--interpol1);',
+});
+
+const App = () =>
+ /*#__PURE__*/ React.createElement(
+ 'div',
+ null,
+ /*#__PURE__*/ React.createElement(
+ 'span',
+ {
+ css: siblingStyles,
+ styles: {
+ '--interpol1': bar,
+ },
+ },
+ 'Hello, World!',
+ ),
+ /*#__PURE__*/ React.createElement(
+ 'button',
+ {
+ css: styles,
+ styles: {
+ '--interpol0': foo,
+ },
+ },
+ 'Submit',
+ ),
+ );
+
+
+`;
+
+exports[`@trousers/macro correctly interpolates variables (Identifier): correctly interpolates variables (Identifier) 1`] = `
+
+import { css } from './macro';
+const foo = 'blue';
+const styles = css('Button', { color: foo });
+
+const App = () => ;
+
+ ↓ ↓ ↓ ↓ ↓ ↓
+
+/** @jsx jsx */
+import { css, jsx } from '@trousers/macro/runtime';
+const foo = 'blue';
+const styles = css('Button', {
+ '.Button-4214914708': 'color: var(--interpol0);',
+});
+
+const App = () =>
+ /*#__PURE__*/ React.createElement('button', {
+ css: styles,
+ styles: {
+ '--interpol0': foo,
+ },
+ });
+
+
`;
exports[`@trousers/macro correctly updates imports: correctly updates imports 1`] = `
@@ -94,6 +336,28 @@ foo('Button', {
});
+`;
+
+exports[`@trousers/macro does not add interpolations if styles are not in use: does not add interpolations if styles are not in use 1`] = `
+
+import { css } from './macro';
+const foo = 'blue';
+const styles = css('Button', { color: foo });
+
+const App = () => ;
+
+ ↓ ↓ ↓ ↓ ↓ ↓
+
+/** @jsx jsx */
+import { css, jsx } from '@trousers/macro/runtime';
+const foo = 'blue';
+const styles = css('Button', {
+ '.Button-4214914708': 'color: var(--interpol0);',
+});
+
+const App = () => /*#__PURE__*/ React.createElement('button', null);
+
+
`;
exports[`@trousers/macro element without identifier: element without identifier 1`] = `
@@ -147,6 +411,83 @@ css('Button', {}).global('global-Button-480010618', {
});
+`;
+
+exports[`@trousers/macro interpolations are correctly added styles directly passed into the css: interpolations are correctly added styles directly passed into the css 1`] = `
+
+import React, { useState } from 'react';
+import { css } from './macro';
+
+const App = () => {
+ const [foo, setFoo] = useState('blue');
+
+ return (
+
+ );
+}
+
+ ↓ ↓ ↓ ↓ ↓ ↓
+
+/** @jsx jsx */
+import { css, jsx } from '@trousers/macro/runtime';
+import React, { useState } from 'react';
+
+const App = () => {
+ const [foo, setFoo] = useState('blue');
+ return /*#__PURE__*/ React.createElement(
+ 'button',
+ {
+ css: css('Button', {
+ '.Button-4214914708': 'color: var(--interpol0);',
+ }),
+ styles: {
+ '--interpol0': foo,
+ },
+ },
+ 'Hello, World!',
+ );
+};
+
+
+`;
+
+exports[`@trousers/macro interpolations are correctly added to an in-use style attribute: interpolations are correctly added to an in-use style attribute 1`] = `
+
+import { css } from './macro';
+const foo = 'blue';
+const styles = css('Button', { color: foo });
+
+const App = () => (
+
+);
+
+ ↓ ↓ ↓ ↓ ↓ ↓
+
+/** @jsx jsx */
+import { css, jsx } from '@trousers/macro/runtime';
+const foo = 'blue';
+const styles = css('Button', {
+ '.Button-4214914708': 'color: var(--interpol0);',
+});
+
+const App = () =>
+ /*#__PURE__*/ React.createElement(
+ 'button',
+ {
+ css: styles,
+ styles: {
+ color: 'red',
+ '--interpol0': foo,
+ },
+ },
+ 'Hello, World!',
+ );
+
+
`;
exports[`@trousers/macro many modifiers: many modifiers 1`] = `
diff --git a/packages/macro/src/macro.js b/packages/macro/src/macro.js
index c286369..7ceb769 100644
--- a/packages/macro/src/macro.js
+++ b/packages/macro/src/macro.js
@@ -3,18 +3,31 @@ const { parse } = require('@babel/parser');
const { process, themify } = require('@trousers/core');
const hash = require('@trousers/hash').default;
-const parseObject = objectExpression =>
+const parseObject = (objectExpression, onInterpolation = () => {}) =>
objectExpression.properties.reduce((accum, { key, value }) => {
let parsedValue;
+ // Raw values
if (
- value.type === 'StringLiteral' ||
- value.type === 'NumericLiteral' ||
- value.type === 'BooleanLiteral'
+ ['StringLiteral', 'NumericLiteral', 'BooleanLiteral'].includes(
+ value.type,
+ )
) {
parsedValue = value.value;
- } else {
- parsedValue = parseObject(value);
+ }
+
+ // Variable & function interpolations
+ if (
+ ['Identifier', 'BinaryExpression', 'CallExpression'].includes(
+ value.type,
+ )
+ ) {
+ parsedValue = onInterpolation(value);
+ }
+
+ // Object interpolations
+ if (value.type === 'ObjectExpression') {
+ parsedValue = parseObject(value, onInterpolation);
}
accum[key.name || key.value] = parsedValue;
@@ -27,7 +40,12 @@ function macro({ references, babel }) {
if (references.css.length === 0) return;
+ const program = references.css[0].findParent(path => path.isProgram());
+
+ let interpolationsCount = 0;
+
references.css.forEach(reference => {
+ const interpolations = [];
const styleBlocks = [];
const importName = reference.node.name;
@@ -50,7 +68,14 @@ function macro({ references, babel }) {
const { arguments: args, callee } = styleBlock.node;
const objectExpression = args.length === 2 ? args[1] : args[0];
const type = callee.name || callee.property.name;
- const rawStyleBlock = parseObject(objectExpression);
+ const rawStyleBlock = parseObject(
+ objectExpression,
+ interpolation => {
+ const id = `--interpol${interpolationsCount++}`;
+ interpolations.push({ reference, id, interpolation });
+ return `var(${id})`;
+ },
+ );
const hashedStyles = hash(JSON.stringify(rawStyleBlock));
let id = args.length === 2 ? args[0].value : '';
@@ -91,9 +116,73 @@ function macro({ references, babel }) {
]),
);
});
+
+ // Dynamic interpolations
+ let jsxOpeningElements = [];
+ const parentJsxElement = reference.find(path =>
+ path.isJSXOpeningElement(),
+ );
+ if (parentJsxElement) jsxOpeningElements.push(parentJsxElement);
+
+ if (!jsxOpeningElements.length) {
+ const styleVariable = reference.findParent(
+ path => path.type === 'VariableDeclarator',
+ );
+ const styleVariableId = styleVariable && styleVariable.node.id.name;
+
+ program.traverse({
+ JSXOpeningElement: path => {
+ const cssAttr = path.node.attributes.find(
+ attr =>
+ attr.name.name === 'css' &&
+ attr.value.expression.name === styleVariableId,
+ );
+ if (!cssAttr) return;
+
+ jsxOpeningElements.push(path);
+ },
+ });
+ }
+
+ jsxOpeningElements.forEach(jsxOpeningElement => {
+ const stylesAttr = jsxOpeningElement.node.attributes.find(
+ attr => attr.name.name === 'styles',
+ );
+
+ const styleProperties = stylesAttr
+ ? stylesAttr.value.expression.properties
+ : [];
+
+ jsxOpeningElement.replaceWith(
+ t.jsxOpeningElement(
+ jsxOpeningElement.node.name,
+ [
+ ...jsxOpeningElement.node.attributes.filter(
+ attr => attr.name.name !== 'styles',
+ ),
+ t.jsxAttribute(
+ t.jsxIdentifier('styles'),
+ t.jsxExpressionContainer(
+ t.objectExpression([
+ ...styleProperties,
+ ...interpolations.map(
+ ({ id, interpolation }) =>
+ t.objectProperty(
+ t.stringLiteral(id),
+ interpolation,
+ ),
+ ),
+ ]),
+ ),
+ ),
+ ],
+ jsxOpeningElement.node.selfClosing,
+ ),
+ );
+ });
});
- const program = references.css[0].findParent(path => path.isProgram());
+ // Import manipulation
const importName = references.css[0].node.name;
program.node.body.unshift(
diff --git a/packages/macro/src/macro.spec.ts b/packages/macro/src/macro.spec.ts
index d293327..a37e2a6 100644
--- a/packages/macro/src/macro.spec.ts
+++ b/packages/macro/src/macro.spec.ts
@@ -5,7 +5,10 @@ pluginTester({
plugin,
title: '@trousers/macro',
snapshot: true,
- babelOptions: { filename: __filename },
+ babelOptions: {
+ filename: __filename,
+ presets: ['@babel/preset-react'],
+ },
tests: [
{
title: 'element',
@@ -124,28 +127,132 @@ pluginTester({
css('Button', { color: 5 });
`,
},
- // {
- // title: 'correctly interpolates variables (Identifier)',
- // code: `
- // import { css } from './macro';
- // const foo = 'blue';
- // css('Button', { color: foo });
- // `,
- // },
- // {
- // title: 'correctly interpolates functions (CallExpression)',
- // code: `
- // import { css } from './macro';
- // const foo = () => 'blue';
- // css('Button', { color: foo() });
- // `,
- // },
- // {
- // title: 'correctly interpolates evaluations (BooleanExpression)',
- // code: `
- // import { css } from './macro';
- // css('Button', { color: 5+5 });
- // `,
- // },
+ {
+ title: 'correctly interpolates variables (Identifier)',
+ code: `
+ import { css } from './macro';
+ const foo = 'blue';
+ const styles = css('Button', { color: foo });
+
+ const App = () => ;
+ `,
+ },
+ {
+ title: 'correctly interpolates functions (CallExpression)',
+ code: `
+ import { css } from './macro';
+
+ const styles = css('Button', { color: foo() });
+
+ const App = () => ;
+ `,
+ },
+ {
+ title: 'correctly interpolates evaluations (BinaryExpression)',
+ code: `
+ import { css } from './macro';
+ const styles = css('Button', { color: 5+5 });
+
+ const App = () => ;
+ `,
+ },
+ {
+ title: 'does not add interpolations if styles are not in use',
+ code: `
+ import { css } from './macro';
+ const foo = 'blue';
+ const styles = css('Button', { color: foo });
+
+ const App = () => ;
+ `,
+ },
+ {
+ title: 'correctly interpolates styles used by nested elements',
+ code: `
+ import { css } from './macro';
+ const foo = 'blue';
+ const bar = 'green';
+ const styles = css('Button', { color: foo });
+ const innerStyles = css('ButtonInner', { color: bar });
+
+ const App = () => (
+
+ );
+ `,
+ },
+ {
+ title: 'correctly interpolates styles used by sibling elements',
+ code: `
+ import { css } from './macro';
+ const foo = 'blue';
+ const bar = 'green';
+ const styles = css('Button', { color: foo });
+ const siblingStyles = css('ButtonInner', { color: bar });
+
+ const App = () => (
+
+
+ Hello, World!
+
+
+
+ );
+ `,
+ },
+ {
+ title: 'correctly interpolates reused styles',
+ code: `
+ import { css } from './macro';
+ const foo = 'blue';
+ const styles = css('Button', { color: foo });
+
+ const App = () => (
+
+ );
+ `,
+ },
+ {
+ title:
+ 'interpolations are correctly added to an in-use style attribute',
+ code: `
+ import { css } from './macro';
+ const foo = 'blue';
+ const styles = css('Button', { color: foo });
+
+ const App = () => (
+
+ );
+ `,
+ },
+ {
+ title:
+ 'interpolations are correctly added styles directly passed into the css',
+ code: `
+ import React, { useState } from 'react';
+ import { css } from './macro';
+
+ const App = () => {
+ const [foo, setFoo] = useState('blue');
+
+ return (
+
+ );
+ }
+ `,
+ },
],
});
diff --git a/packages/macro/src/stub/jsx.tsx b/packages/macro/src/stub/jsx.tsx
index b250e1e..121cfad 100644
--- a/packages/macro/src/stub/jsx.tsx
+++ b/packages/macro/src/stub/jsx.tsx
@@ -1,3 +1,5 @@
+/** TODO: this stub might be redundant because the global types should be enough in dev mode */
+/** IDEA: Maybe just re-export the react JSX module here. The code will never be run so who cares right? Also global types come for freee */
import { createElement, ElementType, ReactNode } from 'react';
import { TrousersProps } from '@trousers/react';