Add extra processing options
This commit is contained in:
parent
8c789367af
commit
ee14d81e8e
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@aet/babel-tailwind",
|
"name": "@aet/babel-tailwind",
|
||||||
"version": "0.0.1-beta.4",
|
"version": "0.0.1-beta.5",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
@ -12,7 +12,7 @@
|
|||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aet/eslint-rules": "^0.0.19",
|
"@aet/eslint-rules": "^0.0.21",
|
||||||
"@types/babel__core": "^7.20.5",
|
"@types/babel__core": "^7.20.5",
|
||||||
"@types/bun": "^1.0.12",
|
"@types/bun": "^1.0.12",
|
||||||
"@types/lodash": "^4.17.0",
|
"@types/lodash": "^4.17.0",
|
||||||
|
51
src/esbuild-babel.ts
Normal file
51
src/esbuild-babel.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { extname } from "node:path";
|
||||||
|
import { once } from "lodash";
|
||||||
|
import type babel from "@babel/core";
|
||||||
|
import type * as esbuild from "esbuild";
|
||||||
|
import { transformSync } from "@babel/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An esbuild plugin that processes files with Babel if `getPlugins` returns any plugins.
|
||||||
|
*/
|
||||||
|
export const babelPlugin = ({
|
||||||
|
filter = /\.[jt]sx?$/,
|
||||||
|
plugins: getPlugins,
|
||||||
|
}: {
|
||||||
|
filter?: RegExp;
|
||||||
|
plugins:
|
||||||
|
| babel.PluginItem[]
|
||||||
|
| ((file: { path: string; contents: string }) => babel.PluginItem[]);
|
||||||
|
}): esbuild.Plugin => ({
|
||||||
|
name: "babel-plugin",
|
||||||
|
setup(build) {
|
||||||
|
build.onLoad({ filter }, ({ path }) => {
|
||||||
|
const load = once(() => readFileSync(path, "utf-8"));
|
||||||
|
const plugins = Array.isArray(getPlugins)
|
||||||
|
? getPlugins
|
||||||
|
: getPlugins({
|
||||||
|
path,
|
||||||
|
get contents() {
|
||||||
|
return load();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!plugins.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { code } = transformSync(load(), {
|
||||||
|
parserOpts: {
|
||||||
|
plugins: ["jsx", "decorators", "typescript", "importAttributes"],
|
||||||
|
},
|
||||||
|
filename: path,
|
||||||
|
plugins,
|
||||||
|
})!;
|
||||||
|
|
||||||
|
return {
|
||||||
|
contents: code!,
|
||||||
|
loader: extname(path).slice(1) as "js" | "ts",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@ -1,14 +1,14 @@
|
|||||||
import { promises as fs } from "node:fs";
|
import { promises as fs } from "node:fs";
|
||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import { build, transformSync } from "esbuild";
|
import { type OutputFile, build, transformSync } from "esbuild";
|
||||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { name } from "../package.json" with { type: "json" };
|
import { name } from "../package.json" with { type: "json" };
|
||||||
import {
|
import {
|
||||||
type TailwindPluginOptions,
|
type TailwindPluginOptions,
|
||||||
babelPlugin,
|
babelPlugin,
|
||||||
|
createPostCSS,
|
||||||
getClassName,
|
getClassName,
|
||||||
getTailwindPlugins,
|
getTailwindPlugins,
|
||||||
createPostCSS,
|
|
||||||
} from "./index";
|
} from "./index";
|
||||||
|
|
||||||
const folder = resolve(import.meta.dirname, "temp");
|
const folder = resolve(import.meta.dirname, "temp");
|
||||||
@ -28,8 +28,17 @@ describe("babel-tailwind", () => {
|
|||||||
return resolved;
|
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) {
|
async function compileESBuild(options: TailwindPluginOptions, javascript: string) {
|
||||||
const tailwind = getTailwindPlugins(options);
|
const tailwind = getTailwindPlugins({
|
||||||
|
tailwindConfig: {},
|
||||||
|
...options,
|
||||||
|
});
|
||||||
const result = await build({
|
const result = await build({
|
||||||
bundle: true,
|
bundle: true,
|
||||||
write: false,
|
write: false,
|
||||||
@ -37,7 +46,7 @@ describe("babel-tailwind", () => {
|
|||||||
outdir: "dist",
|
outdir: "dist",
|
||||||
format: "esm",
|
format: "esm",
|
||||||
entryPoints: [await write("index.tsx", javascript)],
|
entryPoints: [await write("index.tsx", javascript)],
|
||||||
plugins: [babelPlugin({ getPlugins: () => [tailwind.babel] }), tailwind.esbuild],
|
plugins: [babelPlugin({ plugins: [tailwind.babel] }), tailwind.esbuild],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { errors, warnings, outputFiles } = result;
|
const { errors, warnings, outputFiles } = result;
|
||||||
@ -49,10 +58,7 @@ describe("babel-tailwind", () => {
|
|||||||
|
|
||||||
it("supports ESBuild", async () => {
|
it("supports ESBuild", async () => {
|
||||||
const outputFiles = await compileESBuild(
|
const outputFiles = await compileESBuild(
|
||||||
{
|
{ clsx: "emotion" },
|
||||||
tailwindConfig: {},
|
|
||||||
clsx: "emotion",
|
|
||||||
},
|
|
||||||
/* tsx */ `
|
/* tsx */ `
|
||||||
export function Hello() {
|
export function Hello() {
|
||||||
return (
|
return (
|
||||||
@ -65,33 +71,69 @@ describe("babel-tailwind", () => {
|
|||||||
);
|
);
|
||||||
expect(outputFiles).toHaveLength(2);
|
expect(outputFiles).toHaveLength(2);
|
||||||
|
|
||||||
const js = outputFiles.find(file => file.path.endsWith(".js"))!;
|
const js = findByExt(outputFiles, ".js");
|
||||||
const css = outputFiles.find(file => file.path.endsWith(".css"))!;
|
const css = findByExt(outputFiles, ".css");
|
||||||
|
|
||||||
const clsName = getClassName("text-center");
|
const clsName = getClassName("text-center");
|
||||||
expect(js.text).toContain(`className: "${clsName}"`);
|
expect(js.text).toContain(`className: "${clsName}"`);
|
||||||
expect(css.text).toMatch(`.${clsName} {\n text-align: center;\n}`);
|
expect(css.text).toMatch(`.${clsName} {\n text-align: center;\n}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
const minCSS = (text: string) =>
|
it("does not remove the attribute if `preserveAttribute` is true", async () => {
|
||||||
transformSync(text, { minify: true, loader: "css" })!.code;
|
const outputFiles = await compileESBuild(
|
||||||
|
{ clsx: "emotion", jsxAttributeAction: "preserve" },
|
||||||
|
/* tsx */ `
|
||||||
|
export function Hello() {
|
||||||
|
return (
|
||||||
|
<div css="text-center">
|
||||||
|
Hello, world!
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
expect(outputFiles).toHaveLength(2);
|
||||||
|
|
||||||
|
const js = findByExt(outputFiles, ".js");
|
||||||
|
expect(js.text).toContain(`css: "text-center"`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports custom jsxAttributeName", async () => {
|
||||||
|
const outputFiles = await compileESBuild(
|
||||||
|
{ clsx: "emotion", jsxAttributeName: "tw" },
|
||||||
|
/* tsx */ `
|
||||||
|
export function Hello() {
|
||||||
|
return (
|
||||||
|
<div tw="text-center">
|
||||||
|
Hello, world!
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
expect(outputFiles).toHaveLength(2);
|
||||||
|
|
||||||
|
const js = findByExt(outputFiles, ".js");
|
||||||
|
const css = findByExt(outputFiles, ".css");
|
||||||
|
|
||||||
|
const clsName = getClassName("text-center");
|
||||||
|
expect(js.text).toContain(`className: "${clsName}"`);
|
||||||
|
expect(css.text).toMatch(`.${clsName} {\n text-align: center;\n}`);
|
||||||
|
});
|
||||||
|
|
||||||
it("supports importing tailwind/base", async () => {
|
it("supports importing tailwind/base", async () => {
|
||||||
const postcss = createPostCSS({ tailwindConfig: {} });
|
const postcss = createPostCSS({ tailwindConfig: {} });
|
||||||
const base = (await postcss("@tailwind base;")).css;
|
const base = (await postcss("@tailwind base;")).css;
|
||||||
const outputFiles = await compileESBuild(
|
const outputFiles = await compileESBuild(
|
||||||
{
|
{ clsx: "emotion" },
|
||||||
tailwindConfig: {},
|
|
||||||
clsx: "emotion",
|
|
||||||
},
|
|
||||||
/* tsx */ `
|
/* tsx */ `
|
||||||
import "${name}/base";
|
import "${name}/base";
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
expect(outputFiles).toHaveLength(2);
|
expect(outputFiles).toHaveLength(2);
|
||||||
|
|
||||||
const js = outputFiles.find(file => file.path.endsWith(".js"))!;
|
const js = findByExt(outputFiles, ".js");
|
||||||
const css = outputFiles.find(file => file.path.endsWith(".css"))!;
|
const css = findByExt(outputFiles, ".css");
|
||||||
|
|
||||||
// expect(js.text).toContain(`import "./base.css";`);
|
// expect(js.text).toContain(`import "./base.css";`);
|
||||||
expect(js.text).toBe("");
|
expect(js.text).toBe("");
|
||||||
|
77
src/index.ts
77
src/index.ts
@ -1,14 +1,14 @@
|
|||||||
import { readFileSync } from "node:fs";
|
|
||||||
import { basename, dirname, extname, join } from "node:path";
|
import { basename, dirname, extname, join } from "node:path";
|
||||||
import { once } from "lodash";
|
|
||||||
import hash from "@emotion/hash";
|
import hash from "@emotion/hash";
|
||||||
import type babel from "@babel/core";
|
import type babel from "@babel/core";
|
||||||
import type * as vite from "vite";
|
import type * as vite from "vite";
|
||||||
import type * as esbuild from "esbuild";
|
import type * as esbuild from "esbuild";
|
||||||
import { type NodePath, type types as t, transformSync } from "@babel/core";
|
import { type NodePath, type types as t } from "@babel/core";
|
||||||
import tailwind, { type Config } from "tailwindcss";
|
import tailwind, { type Config } from "tailwindcss";
|
||||||
import postcss from "postcss";
|
import postcss from "postcss";
|
||||||
|
|
||||||
|
export { babelPlugin } from "./esbuild-babel";
|
||||||
|
|
||||||
const PLUGIN_NAME = "tailwind";
|
const PLUGIN_NAME = "tailwind";
|
||||||
const ESBUILD_NAMESPACE = "babel-tailwind";
|
const ESBUILD_NAMESPACE = "babel-tailwind";
|
||||||
const ROLLUP_PREFIX = "\0tailwind:";
|
const ROLLUP_PREFIX = "\0tailwind:";
|
||||||
@ -50,7 +50,7 @@ export interface TailwindPluginOptions {
|
|||||||
/**
|
/**
|
||||||
* Tailwind CSS configuration
|
* Tailwind CSS configuration
|
||||||
*/
|
*/
|
||||||
tailwindConfig: Omit<Config, "content">;
|
tailwindConfig?: Omit<Config, "content">;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directives to prefix to all Tailwind stylesheets
|
* Directives to prefix to all Tailwind stylesheets
|
||||||
@ -68,6 +68,12 @@ export interface TailwindPluginOptions {
|
|||||||
*/
|
*/
|
||||||
jsxAttributeName?: string;
|
jsxAttributeName?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What to do with the original attribute after processing
|
||||||
|
* @default "delete"
|
||||||
|
*/
|
||||||
|
jsxAttributeAction?: "delete" | "preserve" | ["rename", string];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tagged template macro function to use for Tailwind classes
|
* Tagged template macro function to use for Tailwind classes
|
||||||
* @default "tw"
|
* @default "tw"
|
||||||
@ -108,6 +114,7 @@ function babelTailwind(
|
|||||||
clsx,
|
clsx,
|
||||||
getClassName: getClass = getClassName,
|
getClassName: getClass = getClassName,
|
||||||
taggedTemplateName,
|
taggedTemplateName,
|
||||||
|
jsxAttributeAction = "delete",
|
||||||
jsxAttributeName = "css",
|
jsxAttributeName = "css",
|
||||||
}: TailwindPluginOptions
|
}: TailwindPluginOptions
|
||||||
) {
|
) {
|
||||||
@ -190,8 +197,13 @@ function babelTailwind(
|
|||||||
|
|
||||||
JSXAttribute(path, { tailwindMap, getCx }) {
|
JSXAttribute(path, { tailwindMap, getCx }) {
|
||||||
const { name } = path.node;
|
const { name } = path.node;
|
||||||
|
if (name.name !== jsxAttributeName) return;
|
||||||
|
|
||||||
const valuePath = path.get("value");
|
const valuePath = path.get("value");
|
||||||
if (name.name !== jsxAttributeName || !valuePath.node) return;
|
if (!valuePath.node) return;
|
||||||
|
|
||||||
|
const copy =
|
||||||
|
jsxAttributeAction === "delete" ? undefined : t.cloneNode(valuePath.node, true);
|
||||||
|
|
||||||
const parent = path.parent as t.JSXOpeningElement;
|
const parent = path.parent as t.JSXOpeningElement;
|
||||||
const classNameAttribute = parent.attributes.find(
|
const classNameAttribute = parent.attributes.find(
|
||||||
@ -246,56 +258,19 @@ function babelTailwind(
|
|||||||
t.jsxAttribute(t.jsxIdentifier("className"), valuePath.node)
|
t.jsxAttribute(t.jsxIdentifier("className"), valuePath.node)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
path.remove();
|
|
||||||
|
if (jsxAttributeAction === "delete") {
|
||||||
|
path.remove();
|
||||||
|
} else {
|
||||||
|
path.node.value = copy!;
|
||||||
|
if (Array.isArray(jsxAttributeAction) && jsxAttributeAction[0] === "rename") {
|
||||||
|
path.node.name.name = jsxAttributeAction[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An esbuild plugin that processes files with Babel if `getPlugins` returns any plugins.
|
|
||||||
*/
|
|
||||||
export const babelPlugin = ({
|
|
||||||
filter = /\.[jt]sx?$/,
|
|
||||||
plugins: getPlugins,
|
|
||||||
}: {
|
|
||||||
filter?: RegExp;
|
|
||||||
plugins:
|
|
||||||
| babel.PluginItem[]
|
|
||||||
| ((file: { path: string; contents: string }) => babel.PluginItem[]);
|
|
||||||
}): esbuild.Plugin => ({
|
|
||||||
name: "babel-plugin",
|
|
||||||
setup(build) {
|
|
||||||
build.onLoad({ filter }, ({ path }) => {
|
|
||||||
const load = once(() => readFileSync(path, "utf-8"));
|
|
||||||
const plugins = Array.isArray(getPlugins)
|
|
||||||
? getPlugins
|
|
||||||
: getPlugins({
|
|
||||||
path,
|
|
||||||
get contents() {
|
|
||||||
return load();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!plugins.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { code } = transformSync(load(), {
|
|
||||||
parserOpts: {
|
|
||||||
plugins: ["jsx", "decorators", "typescript", "importAttributes"],
|
|
||||||
},
|
|
||||||
filename: path,
|
|
||||||
plugins,
|
|
||||||
})!;
|
|
||||||
|
|
||||||
return {
|
|
||||||
contents: code!,
|
|
||||||
loader: extname(path).slice(1) as "js" | "ts",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
type Compile = ReturnType<typeof createPostCSS>;
|
type Compile = ReturnType<typeof createPostCSS>;
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user