From eaa888e920521f41c1db9c0a3704868407bbfb7b Mon Sep 17 00:00:00 2001 From: Alex <8125011+alex-kinokon@users.noreply.github.com> Date: Sat, 29 Jun 2024 10:47:23 -0400 Subject: [PATCH] Support grouped tw --- package.json | 7 +++--- pnpm-lock.yaml | 40 ++++++--------------------------- scripts/index.ts | 3 --- src/babel-tailwind.ts | 52 +++++++++++++++++++++++++++++++++++++++---- src/index.test.ts | 20 +++++++++++++++++ src/index.ts | 15 ++++++++----- src/shared.ts | 4 +--- 7 files changed, 88 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index ea583d6..d2c228f 100644 --- a/package.json +++ b/package.json @@ -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" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 59bab6c..911fe6b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/scripts/index.ts b/scripts/index.ts index 900c46f..baf1160 100755 --- a/scripts/index.ts +++ b/scripts/index.ts @@ -11,9 +11,6 @@ const tsupConfig = defineConfig({ dts: true, treeshake: true, platform: "node", - define: { - "process.env.BABEL_TAILWIND_BUILD": "true", - }, banner: { js: "/* eslint-disable */", }, diff --git a/src/babel-tailwind.ts b/src/babel-tailwind.ts index a2a7d2b..8068ddb 100644 --- a/src/babel-tailwind.ts +++ b/src/babel-tailwind.ts @@ -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; diff --git a/src/index.test.ts b/src/index.test.ts index c6d2a6b..bd89444 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -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({ diff --git a/src/index.ts b/src/index.ts index 520ff5a..d3ed4db 100644 --- a/src/index.ts +++ b/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, }; diff --git a/src/shared.ts b/src/shared.ts index ff7c8a7..59441eb 100644 --- a/src/shared.ts +++ b/src/shared.ts @@ -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;