Improve error handling

This commit is contained in:
Alex
2024-04-07 03:13:05 -04:00
parent ee14d81e8e
commit 4114914eea
9 changed files with 591 additions and 424 deletions

View File

@ -1,7 +1,8 @@
import { promises as fs } from "node:fs";
import { resolve } from "node:path";
import { type OutputFile, build, transformSync } from "esbuild";
import { type BuildOptions, type OutputFile, build, transformSync } from "esbuild";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import dedent from "dedent";
import { name } from "../package.json" with { type: "json" };
import {
type TailwindPluginOptions,
@ -22,44 +23,10 @@ describe("babel-tailwind", () => {
await fs.rm(folder, { recursive: true, force: true });
});
async function write(path: string, content: string) {
const resolved = resolve(folder, path);
await fs.writeFile(resolved, content);
return resolved;
}
const minCSS = (text: string) =>
transformSync(text, { minify: true, loader: "css" }).code;
const findByExt = (outputFiles: OutputFile[], ext: string) =>
outputFiles.find(file => file.path.endsWith(ext))!;
async function compileESBuild(options: TailwindPluginOptions, javascript: string) {
const tailwind = getTailwindPlugins({
tailwindConfig: {},
...options,
});
const result = await build({
bundle: true,
write: false,
external: ["react/jsx-runtime"],
outdir: "dist",
format: "esm",
entryPoints: [await write("index.tsx", javascript)],
plugins: [babelPlugin({ plugins: [tailwind.babel] }), tailwind.esbuild],
});
const { errors, warnings, outputFiles } = result;
expect(errors).toHaveLength(0);
expect(warnings).toHaveLength(0);
return outputFiles;
}
it("supports ESBuild", async () => {
const outputFiles = await compileESBuild(
{ clsx: "emotion" },
/* tsx */ `
const outputFiles = await compileESBuild({
clsx: "emotion",
javascript: /* tsx */ `
export function Hello() {
return (
<div css="text-center">
@ -67,8 +34,8 @@ describe("babel-tailwind", () => {
</div>
);
}
`
);
`,
});
expect(outputFiles).toHaveLength(2);
const js = findByExt(outputFiles, ".js");
@ -80,9 +47,10 @@ describe("babel-tailwind", () => {
});
it("does not remove the attribute if `preserveAttribute` is true", async () => {
const outputFiles = await compileESBuild(
{ clsx: "emotion", jsxAttributeAction: "preserve" },
/* tsx */ `
const outputFiles = await compileESBuild({
clsx: "emotion",
jsxAttributeAction: "preserve",
javascript: /* tsx */ `
export function Hello() {
return (
<div css="text-center">
@ -90,18 +58,50 @@ describe("babel-tailwind", () => {
</div>
);
}
`
);
`,
});
expect(outputFiles).toHaveLength(2);
const js = findByExt(outputFiles, ".js");
expect(js.text).toContain(`css: "text-center"`);
});
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">',
});
}
});
it("supports custom jsxAttributeName", async () => {
const outputFiles = await compileESBuild(
{ clsx: "emotion", jsxAttributeName: "tw" },
/* tsx */ `
const outputFiles = await compileESBuild({
clsx: "emotion",
jsxAttributeName: "tw",
javascript: /* tsx */ `
export function Hello() {
return (
<div tw="text-center">
@ -109,8 +109,8 @@ describe("babel-tailwind", () => {
</div>
);
}
`
);
`,
});
expect(outputFiles).toHaveLength(2);
const js = findByExt(outputFiles, ".js");
@ -124,12 +124,12 @@ describe("babel-tailwind", () => {
it("supports importing tailwind/base", async () => {
const postcss = createPostCSS({ tailwindConfig: {} });
const base = (await postcss("@tailwind base;")).css;
const outputFiles = await compileESBuild(
{ clsx: "emotion" },
/* tsx */ `
const outputFiles = await compileESBuild({
clsx: "emotion",
javascript: /* tsx */ `
import "${name}/base";
`
);
`,
});
expect(outputFiles).toHaveLength(2);
const js = findByExt(outputFiles, ".js");
@ -141,3 +141,45 @@ describe("babel-tailwind", () => {
expect(minCSS(css.text)).toContain(minCSS(base));
});
});
async function write(path: string, content: string) {
const resolved = resolve(folder, path);
await fs.writeFile(resolved, content);
return resolved;
}
const minCSS = (text: string) =>
transformSync(text, { minify: true, loader: "css" }).code;
const findByExt = (outputFiles: OutputFile[], ext: string) =>
outputFiles.find(file => file.path.endsWith(ext))!;
async function compileESBuild({
javascript,
esbuild,
...options
}: TailwindPluginOptions & {
esbuild?: BuildOptions;
javascript: string;
}) {
const tailwind = getTailwindPlugins({
tailwindConfig: {},
...options,
});
const result = await build({
bundle: true,
write: false,
external: ["react/jsx-runtime"],
outdir: "dist",
format: "esm",
entryPoints: [await write("index.tsx", dedent(javascript))],
plugins: [babelPlugin({ plugins: [tailwind.babel] }), tailwind.esbuild],
...esbuild,
});
const { errors, warnings, outputFiles } = result;
expect(errors).toHaveLength(0);
expect(warnings).toHaveLength(0);
return outputFiles!;
}