Add style object support

This commit is contained in:
Alex
2024-07-02 21:09:10 -04:00
parent 835c5b7810
commit 398f2a7c69
17 changed files with 896 additions and 435 deletions

View File

@ -0,0 +1,25 @@
import { describe, expect, it } from "vitest";
import { createPostCSS } from "../index";
import { getBuild, minCSS, name } from "./utils";
describe("babel-tailwind", () => {
const compileESBuild = getBuild("base");
it("supports importing tailwind/base", async () => {
const postcss = createPostCSS({
tailwindConfig: {},
postCSSPlugins: [],
});
const base = await postcss("@tailwind base;");
const { files } = await compileESBuild({
clsx: "emotion",
expectFiles: 2,
javascript: /* tsx */ `
import "${name}/base";
`,
});
expect(files.js.text).toBe("");
expect(minCSS(files.css.text)).toContain(minCSS(base));
});
});

View File

@ -0,0 +1,36 @@
import { describe, expect, it } from "vitest";
import { getBuild } from "./utils";
describe("babel-tailwind", () => {
const compileESBuild = getBuild("error");
it("reports errors with correct position", async () => {
try {
await compileESBuild({
clsx: "emotion",
jsxAttributeAction: "preserve",
esbuild: {
logLevel: "silent",
},
javascript: /* tsx */ `
export function Hello() {
return (
<div css="text-center2 m-0">
Hello, world!
</div>
);
}
`,
});
throw new Error("Expected an error");
} catch (e) {
expect(e.errors).toHaveLength(1);
const [error] = e.errors;
expect(error.location).toMatchObject({
column: 14,
length: 12,
line: 3,
lineText: ' <div css="text-center2 m-0">',
});
}
});
});

View File

@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import { getBuild } from "./utils";
import { getClassName } from "../index";
describe.only("babel-tailwind", () => {
const compileESBuild = getBuild("styleObject");
it("supports conversion into CSSProperties", async () => {
const { files } = await compileESBuild({
clsx: "emotion",
expectFiles: 1,
javascript: `
export function Hello() {
return (
<div style={tws\`p-2 text-center\`}>
Hello, world!
</div>
);
}
`,
});
const clsName = getClassName("p-2 text-center").replace(/^tw-/, "tw_");
expect(files.js.text).toContain(
`var ${clsName} = {\n "padding": "0.5rem",\n "textAlign": "center"\n}`
);
expect(files.js.text).toContain(`style: ${clsName}`);
});
});

51
src/__tests__/tw.test.ts Normal file
View File

@ -0,0 +1,51 @@
import { describe, expect, it } from "vitest";
import { getClassName } from "../index";
import { getBuild } from "./utils";
describe("babel-tailwind", () => {
const compileESBuild = getBuild("tw");
it("supports grouped tw", async () => {
const { files } = await compileESBuild({
clsx: "emotion",
expectFiles: 2,
javascript: `
export default tw("text-sm", \`flex\`, {
"group-hover": "text-center",
"[&>div]": \`font-semibold\`,
data: {
"name='hello'": "text-right",
nested: {
true: "border",
}
},
})
`,
});
const clsName = getClassName(
"text-sm flex group-hover:text-center [&>div]:font-semibold data-[name='hello']:text-right data-[nested=true]:border"
);
expect(files.js.text).toContain(`= "${clsName}"`);
expect(files.css.text).toMatch(
[
`.${clsName} {`,
" display: flex;",
" font-size: 0.875rem;",
" line-height: 1.25rem;",
"}",
`.group:hover .${clsName} {`,
" text-align: center;",
"}",
`.${clsName}[data-nested=true] {`,
" border-width: 1px;",
"}",
`.${clsName}[data-name=hello] {`,
" text-align: right;",
"}",
`.${clsName} > div {`,
" font-weight: 600;",
"}",
].join("\n")
);
});
});

75
src/__tests__/utils.ts Normal file
View File

@ -0,0 +1,75 @@
import { promises as fs } from "node:fs";
import { resolve } from "node:path";
import { afterEach, beforeEach, expect } from "vitest";
import * as esbuild from "esbuild";
import dedent from "dedent";
import { type TailwindPluginOptions, babelPlugin, getTailwindPlugins } from "../index";
export { name } from "../../package.json" with { type: "json" };
export const minCSS = (text: string) =>
esbuild.transformSync(text, { minify: true, loader: "css" }).code;
const findByExt = (outputFiles: esbuild.OutputFile[], ext: string) =>
outputFiles.find(file => file.path.endsWith(ext))!;
export function getBuild(name: string) {
const folder = resolve(import.meta.dirname, "..", ".temp-" + name);
async function write(path: string, content: string) {
const resolved = resolve(folder, path);
await fs.writeFile(resolved, content);
return resolved;
}
beforeEach(async () => {
await fs.mkdir(folder, { recursive: true });
});
afterEach(async () => {
await fs.rm(folder, { recursive: true, force: true });
});
return async function compileESBuild({
javascript,
esbuild: esbuildOptions,
expectFiles,
...options
}: Omit<TailwindPluginOptions, "compile"> & {
esbuild?: esbuild.BuildOptions;
javascript: string;
expectFiles?: number;
}) {
const tailwind = getTailwindPlugins({
tailwindConfig: {},
macroFunction: "tw",
macroStyleFunction: "tws",
...options,
});
const result = await esbuild.build({
bundle: true,
write: false,
external: ["react/jsx-runtime", "@emotion/css", "clsx"],
outdir: "dist",
format: "esm",
entryPoints: [await write("index.tsx", dedent(javascript))],
plugins: [babelPlugin({ plugins: [tailwind.babel()] }), tailwind.esbuild()],
...esbuildOptions,
});
const { errors, warnings, outputFiles } = result;
expect(errors).toHaveLength(0);
expect(warnings).toHaveLength(0);
if (expectFiles != null) {
expect(outputFiles).toHaveLength(expectFiles);
}
return {
outputFiles: outputFiles!,
files: new Proxy({} as Record<string, esbuild.OutputFile>, {
get: (_, ext: string) => findByExt(outputFiles!, ext),
}),
};
};
}