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