Support grouped tw
This commit is contained in:
@ -1,9 +1,8 @@
|
||||
{
|
||||
"name": "@aet/tailwind",
|
||||
"version": "0.0.1-beta.8",
|
||||
"version": "0.0.1-beta.10",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "./scripts/index.ts",
|
||||
"test": "vitest"
|
||||
@ -12,7 +11,7 @@
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@aet/eslint-rules": "^0.0.33",
|
||||
"@aet/eslint-rules": "^0.0.34",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/bun": "^1.1.6",
|
||||
"@types/dedent": "^0.7.2",
|
||||
@ -55,4 +54,4 @@
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
}
|
||||
}
|
40
pnpm-lock.yaml
generated
40
pnpm-lock.yaml
generated
@ -25,8 +25,8 @@ importers:
|
||||
version: 4.20.1
|
||||
devDependencies:
|
||||
'@aet/eslint-rules':
|
||||
specifier: ^0.0.33
|
||||
version: 0.0.33(eslint@8.57.0)(typescript@5.5.2)
|
||||
specifier: ^0.0.34
|
||||
version: 0.0.34(eslint@8.57.0)(typescript@5.5.2)
|
||||
'@types/babel__core':
|
||||
specifier: ^7.20.5
|
||||
version: 7.20.5
|
||||
@ -107,8 +107,8 @@ packages:
|
||||
resolution: {integrity: sha512-CpHRfl17loRfmX2yimeFiu72vTG/m+h8Trq2z90yAZr6rGP7kcV/ypsm56cZ/BrK8iK94MWNdsRa3QIgw1Xcrw==}
|
||||
engines: {node: '>=18.0.0', npm: '>=9.0.0'}
|
||||
|
||||
'@aet/eslint-rules@0.0.33':
|
||||
resolution: {integrity: sha512-ti3g5iLm3Jsegwd5RGgaY+8aWRUqnq+ErjyWueOiN/xf1pCAM9HSQ7cCv6Sq4kQlKrLZS7cGf6pwpy2lZZc5Dw==}
|
||||
'@aet/eslint-rules@0.0.34':
|
||||
resolution: {integrity: sha512-3NYOYf1HJg9IRQgvl0kGR6CZlfPzRk/rf8519xOD1bXdmChhpPP+MAHm5G1WKQqAVeAa4J6aV7hKy2iyJcHZTA==}
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0
|
||||
typescript: ^5.4.4
|
||||
@ -121,10 +121,6 @@ packages:
|
||||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@babel/code-frame@7.24.2':
|
||||
resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/code-frame@7.24.7':
|
||||
resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@ -199,10 +195,6 @@ packages:
|
||||
resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/highlight@7.24.2':
|
||||
resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/highlight@7.24.7':
|
||||
resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@ -630,10 +622,6 @@ packages:
|
||||
resolution: {integrity: sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==}
|
||||
engines: {node: ^18.18.0 || >=20.0.0}
|
||||
|
||||
'@typescript-eslint/types@7.9.0':
|
||||
resolution: {integrity: sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==}
|
||||
engines: {node: ^18.18.0 || >=20.0.0}
|
||||
|
||||
'@typescript-eslint/typescript-estree@7.14.1':
|
||||
resolution: {integrity: sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==}
|
||||
engines: {node: ^18.18.0 || >=20.0.0}
|
||||
@ -2166,7 +2154,7 @@ snapshots:
|
||||
|
||||
'@aet/eslint-define-config@0.1.0-beta.1': {}
|
||||
|
||||
'@aet/eslint-rules@0.0.33(eslint@8.57.0)(typescript@5.5.2)':
|
||||
'@aet/eslint-rules@0.0.34(eslint@8.57.0)(typescript@5.5.2)':
|
||||
dependencies:
|
||||
'@aet/eslint-define-config': 0.1.0-beta.1
|
||||
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
|
||||
@ -2218,11 +2206,6 @@ snapshots:
|
||||
'@jridgewell/gen-mapping': 0.3.5
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@babel/code-frame@7.24.2':
|
||||
dependencies:
|
||||
'@babel/highlight': 7.24.2
|
||||
picocolors: 1.0.0
|
||||
|
||||
'@babel/code-frame@7.24.7':
|
||||
dependencies:
|
||||
'@babel/highlight': 7.24.7
|
||||
@ -2322,13 +2305,6 @@ snapshots:
|
||||
'@babel/template': 7.24.7
|
||||
'@babel/types': 7.24.7
|
||||
|
||||
'@babel/highlight@7.24.2':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.24.7
|
||||
chalk: 2.4.2
|
||||
js-tokens: 4.0.0
|
||||
picocolors: 1.0.0
|
||||
|
||||
'@babel/highlight@7.24.7':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.24.7
|
||||
@ -2387,7 +2363,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/eslint': 8.56.10
|
||||
'@types/estree': 1.0.5
|
||||
'@typescript-eslint/types': 7.9.0
|
||||
'@typescript-eslint/types': 7.14.1
|
||||
comment-parser: 1.4.1
|
||||
esquery: 1.5.0
|
||||
jsdoc-type-pratt-parser: 4.0.0
|
||||
@ -2717,8 +2693,6 @@ snapshots:
|
||||
|
||||
'@typescript-eslint/types@7.14.1': {}
|
||||
|
||||
'@typescript-eslint/types@7.9.0': {}
|
||||
|
||||
'@typescript-eslint/typescript-estree@7.14.1(typescript@5.5.2)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 7.14.1
|
||||
@ -3688,7 +3662,7 @@ snapshots:
|
||||
|
||||
parse-json@5.2.0:
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.24.2
|
||||
'@babel/code-frame': 7.24.7
|
||||
error-ex: 1.3.2
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
lines-and-columns: 1.2.4
|
||||
|
@ -11,9 +11,6 @@ const tsupConfig = defineConfig({
|
||||
dts: true,
|
||||
treeshake: true,
|
||||
platform: "node",
|
||||
define: {
|
||||
"process.env.BABEL_TAILWIND_BUILD": "true",
|
||||
},
|
||||
banner: {
|
||||
js: "/* eslint-disable */",
|
||||
},
|
||||
|
@ -40,7 +40,7 @@ export function babelTailwind(
|
||||
styleMap,
|
||||
clsx,
|
||||
getClassName: getClass = getClassName,
|
||||
taggedTemplateName,
|
||||
macroFunction,
|
||||
jsxAttributeAction = "delete",
|
||||
jsxAttributeName = "css",
|
||||
vite,
|
||||
@ -122,17 +122,17 @@ export function babelTailwind(
|
||||
},
|
||||
|
||||
TaggedTemplateExpression(path, { sliceText, recordIfAbsent }) {
|
||||
if (taggedTemplateName == null) return;
|
||||
if (macroFunction == null) return;
|
||||
const { node } = path;
|
||||
|
||||
const {
|
||||
tag,
|
||||
quasi: { quasis, expressions },
|
||||
} = node;
|
||||
if (!t.isIdentifier(tag, { name: taggedTemplateName })) return;
|
||||
if (!t.isIdentifier(tag, { name: macroFunction })) return;
|
||||
|
||||
if (expressions.length) {
|
||||
throw new Error(`${taggedTemplateName}\`\` should not contain expressions`);
|
||||
throw new Error(`${macroFunction}\`\` should not contain expressions`);
|
||||
}
|
||||
|
||||
const value = quasis[0].value.cooked;
|
||||
@ -148,6 +148,50 @@ export function babelTailwind(
|
||||
}
|
||||
},
|
||||
|
||||
CallExpression(path, { sliceText, recordIfAbsent }) {
|
||||
if (macroFunction == null) return;
|
||||
|
||||
const { node } = path;
|
||||
|
||||
const { callee, arguments: args } = node;
|
||||
if (!t.isIdentifier(callee, { name: macroFunction })) return;
|
||||
|
||||
if (args.length !== 1) {
|
||||
throw new Error(`${macroFunction} should be called with exactly one argument`);
|
||||
}
|
||||
|
||||
const [arg] = args;
|
||||
if (!t.isObjectExpression(arg)) {
|
||||
throw new Error(`${macroFunction} should be called with an object literal`);
|
||||
}
|
||||
|
||||
const ev = path.get("arguments")[0].evaluate();
|
||||
if (!ev.confident || typeof ev.value !== "object" || ev.value == null) {
|
||||
throw new Error(`${macroFunction} should be called with a static object literal`);
|
||||
}
|
||||
|
||||
const trimmed = 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 className = getClass(trimmed);
|
||||
recordIfAbsent({
|
||||
key: className,
|
||||
className: trimmed,
|
||||
location: sliceText(node),
|
||||
});
|
||||
path.replaceWith(t.stringLiteral(className));
|
||||
},
|
||||
|
||||
JSXAttribute(path, { sliceText, recordIfAbsent, getCx }) {
|
||||
const { name } = path.node;
|
||||
if (name.name !== jsxAttributeName) return;
|
||||
|
@ -159,6 +159,25 @@ describe("babel-tailwind", () => {
|
||||
expect(files.css.text).toMatch(`.${clsName} {\n text-align: center;\n}`);
|
||||
});
|
||||
|
||||
it("supports grouped tw", async () => {
|
||||
const { files } = await compileESBuild({
|
||||
clsx: "emotion",
|
||||
expectFiles: 2,
|
||||
javascript: /* tsx */ `
|
||||
export default tw({
|
||||
"group-hover": "text-center",
|
||||
"[&>div]": "font-semibold",
|
||||
})
|
||||
`,
|
||||
});
|
||||
|
||||
const clsName = getClassName("group-hover:text-center [&>div]:font-semibold");
|
||||
expect(files.js.text).toContain(`= "${clsName}"`);
|
||||
expect(files.css.text).toMatch(
|
||||
`.group:hover .${clsName} {\n text-align: center;\n}\n.${clsName} > div {\n font-weight: 600;\n}`
|
||||
);
|
||||
});
|
||||
|
||||
it("supports importing tailwind/base", async () => {
|
||||
const postcss = createPostCSS({
|
||||
tailwindConfig: {},
|
||||
@ -202,6 +221,7 @@ async function compileESBuild({
|
||||
}) {
|
||||
const tailwind = getTailwindPlugins({
|
||||
tailwindConfig: {},
|
||||
macroFunction: "tw",
|
||||
...options,
|
||||
});
|
||||
const result = await esbuild.build({
|
||||
|
15
src/index.ts
15
src/index.ts
@ -17,7 +17,10 @@ type GetClassName = (className: string) => string;
|
||||
* Tagged template macro function for Tailwind classes
|
||||
* @example "tw" => tw`p-2 text-center`
|
||||
*/
|
||||
export type TaggedTailwindFunction = (strings: TemplateStringsArray) => string;
|
||||
export interface TailwindFunction {
|
||||
(strings: TemplateStringsArray): string;
|
||||
(group: { [modifier: string]: string }): string;
|
||||
}
|
||||
|
||||
export interface TailwindPluginOptions {
|
||||
/**
|
||||
@ -53,13 +56,13 @@ export interface TailwindPluginOptions {
|
||||
jsxAttributeAction?: "delete" | "preserve" | ["rename", string];
|
||||
|
||||
/**
|
||||
* Tagged template macro function to use for Tailwind classes
|
||||
* Template macro function to use for Tailwind classes
|
||||
* @default "tw"
|
||||
* @example
|
||||
* declare const tw: TaggedTailwindFunction;
|
||||
* declare const tw: TailwindFunction;
|
||||
* "tw" => tw`p-2 text-center`
|
||||
*/
|
||||
taggedTemplateName?: string | undefined;
|
||||
macroFunction?: string | undefined;
|
||||
|
||||
/**
|
||||
* The prefix to use for the generated class names.
|
||||
@ -98,7 +101,7 @@ export type ResolveTailwindOptions = SetRequired<
|
||||
| "postCSSPlugins"
|
||||
| "styleMap"
|
||||
| "tailwindConfig"
|
||||
| "taggedTemplateName"
|
||||
| "macroFunction"
|
||||
>;
|
||||
|
||||
/**
|
||||
@ -130,7 +133,7 @@ export function getTailwindPlugins(options: TailwindPluginOptions) {
|
||||
jsxAttributeName: "css",
|
||||
postCSSPlugins: [],
|
||||
styleMap: new Map(),
|
||||
taggedTemplateName: undefined,
|
||||
macroFunction: undefined,
|
||||
tailwindConfig: {},
|
||||
...options,
|
||||
};
|
||||
|
@ -2,9 +2,7 @@ import tailwind from "tailwindcss";
|
||||
import postcss from "postcss";
|
||||
import type { ResolveTailwindOptions } from "./index";
|
||||
|
||||
export const { name: pkgName } = [require][0](
|
||||
process.env.BABEL_TAILWIND_BUILD ? "./package.json" : "../package.json"
|
||||
);
|
||||
export const { name: pkgName } = [require][0]("../package.json");
|
||||
|
||||
interface LineColumn {
|
||||
line: number;
|
||||
|
Reference in New Issue
Block a user