Skip to content

Commit

Permalink
Simple css prop
Browse files Browse the repository at this point in the history
  • Loading branch information
Mad-Kat committed Apr 21, 2024
1 parent acb34a1 commit bd3c60e
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 8 deletions.
7 changes: 7 additions & 0 deletions packages/next-yak/loaders/babel-yak-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import getCssName from "./lib/getCssName.js";
import { Declaration, ParserState, parseCss } from "./lib/parseCss.js";
import { toCss } from "./lib/toCss.js";
import appendCssUnitToExpressionValue from "./lib/appendCssUnitToExpressionValue.js";
import { transpileCssProp } from "./lib/transpileCssProp.js";

type YakBabelPluginOptions = {
replaces: Record<string, unknown>;
Expand Down Expand Up @@ -217,6 +218,12 @@ export default function (
}
},
},
JSXElement(path, state) {
if (!this.isImportedInCurrentFile || !this.yakImportPath) {
return;
}
transpileCssProp(t, path);
},
/**
* Store the name of the imported 'css' and 'styled' variables e.g.:
* - `import { css, styled } from 'next-yak'` -> { css: 'css', styled: 'styled' }
Expand Down
29 changes: 21 additions & 8 deletions packages/next-yak/loaders/lib/getStyledComponentName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,33 @@ import type { NodePath, types as babelTypes } from "@babel/core";
const getStyledComponentName = (
taggedTemplateExpressionPath: NodePath<babelTypes.TaggedTemplateExpression>,
) => {
const variableDeclaratorPath = taggedTemplateExpressionPath.findParent(
(path) => path.isVariableDeclarator(),
);
const variableOrFunctionDeclaratorPath =
taggedTemplateExpressionPath.findParent(
(path) => path.isVariableDeclarator() || path.isFunctionDeclaration(),
);

if (
variableOrFunctionDeclaratorPath?.isFunctionDeclaration() &&
"id" in variableOrFunctionDeclaratorPath.node &&
variableOrFunctionDeclaratorPath.node.id === null
) {
const parent = variableOrFunctionDeclaratorPath.parentPath;
if (parent.isExportDefaultDeclaration()) {
return "defaultExp";
}
}

if (
!variableDeclaratorPath ||
!("id" in variableDeclaratorPath.node) ||
variableDeclaratorPath.node.id?.type !== "Identifier"
!variableOrFunctionDeclaratorPath ||
!("id" in variableOrFunctionDeclaratorPath.node) ||
variableOrFunctionDeclaratorPath.node.id?.type !== "Identifier"
) {
throw new Error(
"Could not find variable declaration for styled component at " +
taggedTemplateExpressionPath.node.loc,
JSON.stringify(taggedTemplateExpressionPath.node.loc),
);
}
return variableDeclaratorPath.node.id.name;
return variableOrFunctionDeclaratorPath.node.id.name;
};

export default getStyledComponentName;
134 changes: 134 additions & 0 deletions packages/next-yak/loaders/lib/transpileCssProp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import type { NodePath, types as babelTypes } from "@babel/core";
import type { JSXElement } from "@babel/types";

export const transpileCssProp = (
t: typeof babelTypes,
path: NodePath<JSXElement>,
) => {
const openingElement = path.node.openingElement;
const closingElement = path.node.closingElement;
const cssPropIndex = openingElement.attributes.findIndex(
(prop) =>
t.isJSXAttribute(prop) &&
t.isJSXIdentifier(prop.name) &&
prop.name.name === "css",
);
// if the css prop is not present, we don't need to do anything
if (cssPropIndex === -1) {
return;
}

const cssProp =
/** @type {import("@babel/types").JSXAttribute} */ openingElement
.attributes[cssPropIndex];

//todo: check
if (t.isJSXSpreadAttribute(cssProp)) {
return;
}

// 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(cssProp.value)) {
return;
}

// namespaced JSX is not supported (even by React itself)
// e.g. <MyComponent:div css={css`...`} />
if (t.isJSXNamespacedName(openingElement.name)) {
throw new Error("Namespaced JSX not supported");
}

const cssValue = cssProp.value.expression;

// remove the css prop
// openingElement.attributes.splice(cssPropIndex, 1);

if (t.isJSXEmptyExpression(cssValue)) {
return;
}

// simple case where we don't have any other relevant props
if (
!openingElement.attributes.some(
(prop) =>
t.isJSXSpreadAttribute(prop) ||
prop.name.name === "className" ||
prop.name.name === "style",
)
) {
openingElement.attributes.splice(cssPropIndex, 1);

openingElement.attributes.push(
t.jsxSpreadAttribute(
t.callExpression(t.callExpression(t.identifier("css"), [cssValue]), [
// todo: check if we need access to the other props and maybe theme? Or throw an error?
t.objectExpression([]),
]),
),
);
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 {yakMerge({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
const mapped = relevantProps
.map((prop) => {
if (t.isJSXAttribute(prop)) {
if (t.isJSXNamespacedName(prop.name)) {
throw new Error("Namespaced JSX not supported");
}
if (!prop.value) {
return null;
}
if (prop.name.name === "css") {
return t.callExpression(t.identifier("css"), [cssValue]);
}

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.objectExpression([
t.objectProperty(t.identifier(prop.name.name), prop.value),
]);
}
return t.objectExpression([t.spreadElement(prop.argument)]);
})
.filter(Boolean) as babelTypes.ObjectExpression[];

// console.log(relevantProps, mapped);

// delete the relevant props
relevantProps.forEach((prop) => {
const index = openingElement.attributes.indexOf(prop);
openingElement.attributes.splice(index, 1);
});

// add the spread attribute
openingElement.attributes.push(
t.jsxSpreadAttribute(
t.callExpression(t.identifier("_yak_css_prop"), mapped),
),
);
};

0 comments on commit bd3c60e

Please sign in to comment.