Support css functions
This commit is contained in:
parent
7072dc493d
commit
d0f032edc1
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@aet/tailwind",
|
||||
"version": "0.0.1-beta.11",
|
||||
"version": "0.0.1-beta.14",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { basename, dirname, extname, join } from "node:path";
|
||||
import type babel from "@babel/core";
|
||||
import hash from "@emotion/hash";
|
||||
import { isPlainObject } from "lodash";
|
||||
import { type NodePath, type types as t } from "@babel/core";
|
||||
import type { SourceLocation, StyleMapEntry } from "./shared";
|
||||
import { type ResolveTailwindOptions, getClassName } from "./index";
|
||||
@ -71,6 +72,34 @@ export function babelTailwind(
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateArgs(paths: NodePath[]) {
|
||||
return paths
|
||||
.flatMap(path => {
|
||||
const { confident, value } = path.evaluate();
|
||||
if (!confident) {
|
||||
throw new Error(`${macroFunction} argument cannot be statically evaluated`);
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
return trim(value);
|
||||
}
|
||||
|
||||
if (isPlainObject(value)) {
|
||||
return Object.entries(value).flatMap(([modifier, classes]) => {
|
||||
if (typeof classes !== "string") {
|
||||
throw new Error(`Value for "${modifier}" should be a string`);
|
||||
}
|
||||
return trim(classes)
|
||||
.split(" ")
|
||||
.map(cls => modifier + ":" + cls);
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(`${macroFunction} argument has an invalid type`);
|
||||
})
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
return definePlugin<BabelPluginState>(({ types: t }) => ({
|
||||
Program: {
|
||||
enter(path, state) {
|
||||
@ -155,38 +184,10 @@ export function babelTailwind(
|
||||
|
||||
const { node } = path;
|
||||
|
||||
const { callee, arguments: args } = node;
|
||||
const { callee } = node;
|
||||
if (!t.isIdentifier(callee, { name: macroFunction })) return;
|
||||
|
||||
const trimmed = args
|
||||
.flatMap((arg, i) => {
|
||||
if (t.isStringLiteral(arg)) {
|
||||
return trim(arg.value);
|
||||
}
|
||||
if (!t.isObjectExpression(arg)) {
|
||||
throw new Error(`${macroFunction} should be called with an object literal`);
|
||||
}
|
||||
|
||||
const ev = path.get("arguments")[i].evaluate();
|
||||
if (!ev.confident || typeof ev.value !== "object" || ev.value == null) {
|
||||
throw new Error(
|
||||
`${macroFunction} should be called with a static object literal`
|
||||
);
|
||||
}
|
||||
|
||||
return Object.entries(ev.value).flatMap(([modifier, classes]) => {
|
||||
if (typeof classes !== "string") {
|
||||
throw new Error(`Value for "${modifier}" should be a string`);
|
||||
}
|
||||
return classes
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
.split(" ")
|
||||
.map(cls => modifier + ":" + cls);
|
||||
});
|
||||
})
|
||||
.join(" ");
|
||||
|
||||
const trimmed = evaluateArgs(path.get("arguments"));
|
||||
const className = getClass(trimmed);
|
||||
recordIfAbsent({
|
||||
key: className,
|
||||
@ -218,7 +219,7 @@ export function babelTailwind(
|
||||
const { value } = node;
|
||||
|
||||
if (value) {
|
||||
const trimmed = value.replace(/\s+/g, " ").trim();
|
||||
const trimmed = trim(value);
|
||||
const className = getClass(trimmed);
|
||||
recordIfAbsent({
|
||||
key: className,
|
||||
@ -233,6 +234,16 @@ export function babelTailwind(
|
||||
go(element);
|
||||
}
|
||||
},
|
||||
ObjectExpression(path) {
|
||||
const trimmed = evaluateArgs([path]);
|
||||
const className = getClass(trimmed);
|
||||
recordIfAbsent({
|
||||
key: className,
|
||||
className: trimmed,
|
||||
location: sliceText(path.node),
|
||||
});
|
||||
path.replaceWith(t.stringLiteral(className));
|
||||
},
|
||||
JSXExpressionContainer(path) {
|
||||
go(path.get("expression"));
|
||||
},
|
||||
@ -250,11 +261,21 @@ export function babelTailwind(
|
||||
},
|
||||
}));
|
||||
|
||||
let valuePathNode = extractJSXContainer(valuePath.node);
|
||||
if (
|
||||
t.isArrayExpression(valuePathNode) &&
|
||||
valuePathNode.elements.every(node => t.isStringLiteral(node))
|
||||
) {
|
||||
valuePathNode = t.stringLiteral(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
(valuePathNode.elements as t.StringLiteral[]).map(node => node.value).join(" ")
|
||||
);
|
||||
}
|
||||
|
||||
if (classNameAttribute) {
|
||||
const attrValue = classNameAttribute.value!;
|
||||
const valuePathNode = valuePath.node;
|
||||
const wrap = (originalValue: babel.types.Expression) =>
|
||||
t.callExpression(getCx(), [originalValue, extractJSXContainer(valuePathNode)]);
|
||||
t.callExpression(getCx(), [originalValue, valuePathNode]);
|
||||
|
||||
// If both are string literals, we can merge them directly here
|
||||
if (t.isStringLiteral(attrValue) && t.isStringLiteral(valuePathNode)) {
|
||||
@ -273,7 +294,10 @@ export function babelTailwind(
|
||||
}
|
||||
} else {
|
||||
parent.attributes.push(
|
||||
t.jsxAttribute(t.jsxIdentifier("className"), valuePath.node)
|
||||
t.jsxAttribute(
|
||||
t.jsxIdentifier("className"),
|
||||
valuePathNode as (typeof valuePath)["node"]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -159,23 +159,26 @@ describe("babel-tailwind", () => {
|
||||
expect(files.css.text).toMatch(`.${clsName} {\n text-align: center;\n}`);
|
||||
});
|
||||
|
||||
it.only("supports grouped tw", async () => {
|
||||
it("supports grouped tw", async () => {
|
||||
const { files } = await compileESBuild({
|
||||
clsx: "emotion",
|
||||
expectFiles: 2,
|
||||
javascript: /* tsx */ `
|
||||
export default tw("text-sm", {
|
||||
export default tw("text-sm", \`flex\`, {
|
||||
"group-hover": "text-center",
|
||||
"[&>div]": "font-semibold",
|
||||
"[&>div]": \`font-semibold\`,
|
||||
})
|
||||
`,
|
||||
});
|
||||
|
||||
const clsName = getClassName("text-sm group-hover:text-center [&>div]:font-semibold");
|
||||
const clsName = getClassName(
|
||||
"text-sm flex group-hover:text-center [&>div]:font-semibold"
|
||||
);
|
||||
expect(files.js.text).toContain(`= "${clsName}"`);
|
||||
expect(files.css.text).toMatch(
|
||||
[
|
||||
`.${clsName} {`,
|
||||
" display: flex;",
|
||||
" font-size: 0.875rem;",
|
||||
" line-height: 1.25rem;",
|
||||
"}",
|
||||
@ -189,6 +192,35 @@ describe("babel-tailwind", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("supports grouped array css jsx attribute like tw function", async () => {
|
||||
const { files } = await compileESBuild({
|
||||
clsx: "emotion",
|
||||
expectFiles: 2,
|
||||
javascript: /* tsx */ `
|
||||
export function Hello() {
|
||||
return (
|
||||
<div css={["text-center", { hover: "font-semibold" }]}>
|
||||
Hello, world!
|
||||
</div>
|
||||
);
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
const clsName = ["text-center", "hover:font-semibold"].map(getClassName);
|
||||
expect(files.js.text).toContain(`className: "${clsName.join(" ")}"`);
|
||||
expect(files.css.text).toMatch(
|
||||
[
|
||||
`.${clsName[0]} {`,
|
||||
" text-align: center;",
|
||||
"}",
|
||||
`.${clsName[1]}:hover {`,
|
||||
" font-weight: 600;",
|
||||
"}",
|
||||
].join("\n")
|
||||
);
|
||||
});
|
||||
|
||||
it("supports importing tailwind/base", async () => {
|
||||
const postcss = createPostCSS({
|
||||
tailwindConfig: {},
|
||||
|
@ -19,7 +19,7 @@ type GetClassName = (className: string) => string;
|
||||
*/
|
||||
export interface TailwindFunction {
|
||||
(strings: TemplateStringsArray): string;
|
||||
(group: { [modifier: string]: string }): string;
|
||||
(...args: (string | { [modifier: string]: string })[]): string;
|
||||
}
|
||||
|
||||
export interface TailwindPluginOptions {
|
||||
|
Loading…
x
Reference in New Issue
Block a user