Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add transpilation of the css prop for simple cases #78

Merged
merged 24 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 3 additions & 152 deletions packages/next-yak/loaders/__tests__/tsloader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1674,88 +1674,6 @@ describe("css prop", () => {
css(__styleYak.Elem)({}))} />;"
`);
});
it("when className is set after", async () => {
expect(
await tsloader.call(
loaderContext,
`
import { css, styled } from "next-yak";
const Elem = () => <div css={css\`
padding: 10px;
\`} className="foo" />;
`,
),
).toMatchInlineSnapshot(`
"import { css, styled } from \\"next-yak\\";
import { __yak_mergeCssProp } from \\"next-yak/runtime-internals\\";
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
const Elem = () => <div {...__yak_mergeCssProp(
/*YAK Extracted CSS:
.Elem {
padding: 10px;
}*/
/*#__PURE__*/
css(__styleYak.Elem)({}), {
className: \\"foo\\"
})} />;"
`);
});
it("when style is set after", async () => {
expect(
await tsloader.call(
loaderContext,
`
import { css, styled } from "next-yak";
const Elem = () => <div css={css\`
padding: 10px;
\`} style={{padding: "5px"}}/>;
`,
),
).toMatchInlineSnapshot(`
"import { css, styled } from \\"next-yak\\";
import { __yak_mergeCssProp } from \\"next-yak/runtime-internals\\";
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
const Elem = () => <div {...__yak_mergeCssProp(
/*YAK Extracted CSS:
.Elem {
padding: 10px;
}*/
/*#__PURE__*/
css(__styleYak.Elem)({}), {
style: {
padding: \\"5px\\"
}
})} />;"
`);
});
it("when spreaded property is set after", async () => {
expect(
await tsloader.call(
loaderContext,
`
import { css, styled } from "next-yak";
const Elem = () => <div {...{className: "foo"}} css={css\`
padding: 10px;
\`} />;
`,
),
).toMatchInlineSnapshot(`
"import { css, styled } from \\"next-yak\\";
import { __yak_mergeCssProp } from \\"next-yak/runtime-internals\\";
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
const Elem = () => <div {...__yak_mergeCssProp({
...{
className: \\"foo\\"
}
},
/*YAK Extracted CSS:
.Elem {
padding: 10px;
}*/
/*#__PURE__*/
css(__styleYak.Elem)({}))} />;"
`);
});
it("when class name and style is set", async () => {
expect(
await tsloader.call(
Expand All @@ -1772,8 +1690,7 @@ describe("css prop", () => {
import { __yak_mergeCssProp } from \\"next-yak/runtime-internals\\";
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
const Elem = () => <div {...__yak_mergeCssProp({
className: \\"foo\\"
}, {
className: \\"foo\\",
style: {
padding: \\"5px\\"
}
Expand All @@ -1786,36 +1703,6 @@ describe("css prop", () => {
css(__styleYak.Elem)({}))} />;"
`);
});
it("when class name and style is set after", async () => {
expect(
await tsloader.call(
loaderContext,
`
import { css, styled } from "next-yak";
const Elem = () => <div css={css\`
padding: 10px;
\`} className="foo" style={{padding: "5px"}} />;
`,
),
).toMatchInlineSnapshot(`
"import { css, styled } from \\"next-yak\\";
import { __yak_mergeCssProp } from \\"next-yak/runtime-internals\\";
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
const Elem = () => <div {...__yak_mergeCssProp(
/*YAK Extracted CSS:
.Elem {
padding: 10px;
}*/
/*#__PURE__*/
css(__styleYak.Elem)({}), {
className: \\"foo\\"
}, {
style: {
padding: \\"5px\\"
}
})} />;"
`);
});
it("when class name, style and spreaded property is set", async () => {
expect(
await tsloader.call(
Expand All @@ -1832,12 +1719,10 @@ describe("css prop", () => {
import { __yak_mergeCssProp } from \\"next-yak/runtime-internals\\";
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
const Elem = () => <div {...__yak_mergeCssProp({
className: \\"foo\\"
}, {
className: \\"foo\\",
style: {
padding: \\"5px\\"
}
}, {
},
...{
className: \\"foo2\\"
}
Expand All @@ -1850,39 +1735,5 @@ describe("css prop", () => {
css(__styleYak.Elem)({}))} />;"
Mad-Kat marked this conversation as resolved.
Show resolved Hide resolved
`);
});
it("when class name, style and spreaded property is set after", async () => {
expect(
await tsloader.call(
loaderContext,
`
import { css, styled } from "next-yak";
const Elem = () => <div css={css\`
padding: 10px;
\`} className="foo" style={{padding: "5px"}} {...{className: "foo2"}} />;
`,
),
).toMatchInlineSnapshot(`
"import { css, styled } from \\"next-yak\\";
import { __yak_mergeCssProp } from \\"next-yak/runtime-internals\\";
import __styleYak from \\"./page.yak.module.css!=!./page?./page.yak.module.css\\";
const Elem = () => <div {...__yak_mergeCssProp(
/*YAK Extracted CSS:
.Elem {
padding: 10px;
}*/
/*#__PURE__*/
css(__styleYak.Elem)({}), {
className: \\"foo\\"
}, {
style: {
padding: \\"5px\\"
}
}, {
...{
className: \\"foo2\\"
}
})} />;"
`);
});
});
Mad-Kat marked this conversation as resolved.
Show resolved Hide resolved
});
Mad-Kat marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion packages/next-yak/loaders/babel-yak-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ export default function (
if (!this.isImportedInCurrentFile || !this.yakImportPath) {
return;
}
transpileCssProp(t, path, this.runtimeInternalHelpers);
transpileCssProp(t, path, this.runtimeInternalHelpers, this.file);
},
/**
* Store the name of the imported 'css' and 'styled' variables e.g.:
Expand Down
100 changes: 55 additions & 45 deletions packages/next-yak/loaders/lib/transpileCssProp.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import type { NodePath, types as babelTypes } from "@babel/core";
import type { JSXAttribute, JSXElement } from "@babel/types";
import type { BabelFile, NodePath, types as babelTypes } from "@babel/core";
import {
objectExpression,
type JSXAttribute,
type JSXElement,
} from "@babel/types";
import { InvalidPositionError } from "../babel-yak-plugin.js";

export const transpileCssProp = (
t: typeof babelTypes,
path: NodePath<JSXElement>,
runtimeInternalHelpers: Set<string>,
file: BabelFile,
) => {
const openingElement = path.node.openingElement;
const cssPropIndex = openingElement.attributes.findIndex(
Expand All @@ -25,7 +31,18 @@ export const transpileCssProp = (
// if the css prop is not an expression, we don't need to do anything
// e.g. <div css="..." /> instead of <div css={css`...`} />
if (!t.isJSXExpressionContainer(cssPropValue)) {
return;
if (cssPropValue) {
throw new InvalidPositionError(
`CSS prop must be an expression.`,
cssPropValue,
file,
"Use the css prop like this: <div css={css`...`} />",
);
} else {
throw new Error(
`css prop must be an expression but found ${cssPropValue}. Please use the css prop like this: <div css={css\`...\`} />`,
);
}
}

// namespaced JSX is not supported (even by React itself)
Expand All @@ -40,15 +57,19 @@ export const transpileCssProp = (
return;
}

// relevant props are the ones that we need to merge with the css prop
// like className, style, and other spread props
const relevantProps = openingElement.attributes.filter(
(prop) =>
t.isJSXSpreadAttribute(prop) ||
(t.isJSXAttribute(prop) &&
t.isJSXIdentifier(prop.name) &&
(prop.name.name === "className" || prop.name.name === "style")),
);

// simple case where we don't have any other relevant props
if (
Mad-Kat marked this conversation as resolved.
Show resolved Hide resolved
!openingElement.attributes.some(
(prop) =>
t.isJSXSpreadAttribute(prop) ||
prop.name.name === "className" ||
prop.name.name === "style",
)
) {
// e.g. <div css={css`color: red;`} />
if (relevantProps.length === 0) {
// remove the css prop
openingElement.attributes.splice(cssPropIndex, 1);

Expand All @@ -62,20 +83,9 @@ export const transpileCssProp = (
return;
}

// add the className, styleProp and spreadProps onto the opening element but keep the same ordering
// e.g. <div className="x" css={css`...`} style={{y:true}} {...p} />
// get converted to <div {...mergeCssProp({className: "x"}, {css: css`...`}, {style: {y:true}, {...p})} />
const relevantProps = openingElement.attributes.filter(
(prop) =>
t.isJSXSpreadAttribute(prop) ||
(t.isJSXAttribute(prop) &&
t.isJSXIdentifier(prop.name) &&
(prop.name.name === "className" ||
prop.name.name === "style" ||
prop.name.name === "css")),
);

//map the props to an object
// map the relevant props to an object
// e.g. <div className="x" css={css`...`} style={{y:true}} {...p} /> gets converted to
// { className: "x", style: {y:true}, ...p }
const mapped = relevantProps
.map((prop) => {
if (t.isJSXAttribute(prop)) {
Expand All @@ -85,39 +95,39 @@ export const transpileCssProp = (
if (!prop.value) {
return null;
}
if (prop.name.name === "css") {
return t.callExpression(cssExpression, [t.objectExpression([])]);
}
// if (prop.name.name === "css") {
// return t.callExpression(cssExpression, [t.objectExpression([])]);
// }

Mad-Kat marked this conversation as resolved.
Show resolved Hide resolved
if (t.isJSXExpressionContainer(prop.value)) {
if (t.isJSXEmptyExpression(prop.value.expression)) {
return null;
}
return t.objectExpression([
t.objectProperty(
t.identifier(prop.name.name),
prop.value.expression,
),
]);
return t.objectProperty(
t.identifier(prop.name.name),
prop.value.expression,
);
}
return t.objectExpression([
t.objectProperty(t.identifier(prop.name.name), prop.value),
]);
return t.objectProperty(t.identifier(prop.name.name), prop.value);
}
return t.objectExpression([t.spreadElement(prop.argument)]);
return t.spreadElement(prop.argument);
})
.filter(Boolean) as babelTypes.ObjectExpression[];
.filter(Boolean) as Array<
babelTypes.ObjectProperty | babelTypes.SpreadElement
>;

// delete the relevant props
relevantProps.forEach((prop) => {
const index = openingElement.attributes.indexOf(prop);
openingElement.attributes.splice(index, 1);
});
// remove all properties that are in the relevant props
openingElement.attributes = openingElement.attributes.filter(
(prop, index) => !relevantProps.includes(prop) && index !== cssPropIndex,
);

// add the spread attribute
openingElement.attributes.push(
t.jsxSpreadAttribute(
t.callExpression(t.identifier("__yak_mergeCssProp"), mapped),
t.callExpression(t.identifier("__yak_mergeCssProp"), [
t.objectExpression(mapped),
t.callExpression(cssExpression, [t.objectExpression([])]),
]),
),
);

Expand Down
Loading
Loading