Fix issue

This commit is contained in:
Alex 2025-02-03 00:05:24 -05:00
parent 2c4b75aa6c
commit 1f5e7fa049
14 changed files with 135 additions and 22 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@aet/tailwind", "name": "@aet/tailwind",
"version": "1.0.20", "version": "1.0.23",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@ -0,0 +1,27 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`emit > supports emitting as CSS module 1`] = `
"// babel-tailwind:/Users/aet/Documents/Git/babel-tailwind/src/.temp-attr/index.module.css
var index_default = {
"tw-gqn2k6": "index_tw-gqn2k6",
"tw-1qtvvjy": "index_tw-1qtvvjy"
};
import { cx as _cx } from "@emotion/css";
import { jsx } from "react/jsx-runtime";
function Hello() {
return /* @__PURE__ */ jsx("div", { className: _cx([index_default["tw-gqn2k6"], index_default["tw-1qtvvjy"]]), children: "Hello, world!" });
}
export {
Hello
};"
`;
exports[`emit > supports emitting as CSS module 2`] = `
".index_tw-gqn2k6 {
text-align: center;
}
.index_tw-1qtvvjy:hover {
font-weight: 600;
}"
`;

View File

@ -1,4 +1,5 @@
import { describe, it } from "vitest"; import { describe, it } from "vitest";
import { getBuild, matchSnapshot } from "./utils"; import { getBuild, matchSnapshot } from "./utils";
describe("attr", () => { describe("attr", () => {
@ -61,7 +62,7 @@ describe("attr", () => {
matchSnapshot(files); matchSnapshot(files);
}); });
it.only("fails", async () => { it("fails", async () => {
const { files } = await compileESBuild({ const { files } = await compileESBuild({
clsx: "emotion", clsx: "emotion",
expectFiles: 2, expectFiles: 2,

View File

@ -1,5 +1,7 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { createPostCSS } from "../index"; import { createPostCSS } from "../index";
import { getBuild, minCSS, name } from "./utils"; import { getBuild, minCSS, name } from "./utils";
describe("babel-tailwind", () => { describe("babel-tailwind", () => {

View File

@ -0,0 +1,26 @@
import { describe, it } from "vitest";
import { getBuild, matchSnapshot } from "./utils";
describe("emit", () => {
const compileESBuild = getBuild("attr");
it("supports emitting as CSS module", async () => {
const { files } = await compileESBuild({
clsx: "emotion",
expectFiles: 2,
cssModules: true,
javascript: /* tsx */ `
export function Hello() {
return (
<div css={["text-center", { hover: "font-semibold" }]}>
Hello, world!
</div>
);
}
`,
});
matchSnapshot(files);
});
});

View File

@ -1,6 +1,8 @@
/* eslint-disable unicorn/string-content */ /* eslint-disable unicorn/string-content */
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { getClassName } from "../index"; import { getClassName } from "../index";
import { getBuild } from "./utils"; import { getBuild } from "./utils";
describe("merges with existing className attribute", () => { describe("merges with existing className attribute", () => {

View File

@ -1,4 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { getBuild, matchSnapshot } from "./utils"; import { getBuild, matchSnapshot } from "./utils";
describe("options", () => { describe("options", () => {

View File

@ -1,4 +1,5 @@
import { describe, it } from "vitest"; import { describe, it } from "vitest";
import { getBuild, matchSnapshot } from "./utils"; import { getBuild, matchSnapshot } from "./utils";
describe("babel-tailwind", () => { describe("babel-tailwind", () => {

View File

@ -1,4 +1,5 @@
import { describe, it } from "vitest"; import { describe, it } from "vitest";
import { getBuild, matchSnapshot } from "./utils"; import { getBuild, matchSnapshot } from "./utils";
describe("babel-tailwind", () => { describe("babel-tailwind", () => {

View File

@ -1,8 +1,10 @@
import { promises as fs } from "node:fs"; import { promises as fs } from "node:fs";
import { join, resolve } from "node:path"; import { join, resolve } from "node:path";
import { afterEach, beforeEach, expect } from "vitest";
import * as esbuild from "esbuild";
import dedent from "dedent"; import dedent from "dedent";
import * as esbuild from "esbuild";
import { afterEach, beforeEach, expect } from "vitest";
import { type TailwindPluginOptions, babelPlugin, getTailwindPlugins } from "../index"; import { type TailwindPluginOptions, babelPlugin, getTailwindPlugins } from "../index";
export { name } from "../../package.json" with { type: "json" }; export { name } from "../../package.json" with { type: "json" };

View File

@ -35,12 +35,14 @@ function getUtils({
clsx, clsx,
getClassName: getClass = getClassName, getClassName: getClass = getClassName,
vite: bustCache, vite: bustCache,
cssModules,
} = options; } = options;
let cx: t.Identifier; let cx: t.Identifier;
let tslibImport: t.Identifier; let tslibImport: t.Identifier;
let styleImport: t.Identifier; let styleImport: t.Identifier;
let classedImport: t.Identifier; let classedImport: t.Identifier;
let cssModuleImport: t.Identifier;
const cssMap = new Map<string, StyleMapEntry>(); const cssMap = new Map<string, StyleMapEntry>();
const jsMap = new Map<string, StyleMapEntry>(); const jsMap = new Map<string, StyleMapEntry>();
@ -50,6 +52,13 @@ function getUtils({
return t.cloneNode(styleImport); return t.cloneNode(styleImport);
} }
const getCssModuleImport = () => {
if (cssModuleImport == null) {
cssModuleImport = path.scope.generateUidIdentifier("cssModule");
}
return t.cloneNode(cssModuleImport);
};
return { return {
getClass(type: Type, value: string) { getClass(type: Type, value: string) {
return type === "css" ? getClass(value) : "tw_" + hash(value); return type === "css" ? getClass(value) : "tw_" + hash(value);
@ -65,7 +74,7 @@ function getUtils({
.join("\n"), .join("\n"),
}), }),
recordIfAbsent(type: Type, entry: StyleMapEntry) { recordIfAbsent(type: "css", entry: StyleMapEntry) {
const map = type === "css" ? cssMap : jsMap; const map = type === "css" ? cssMap : jsMap;
if (!map.has(entry.key)) { if (!map.has(entry.key)) {
map.set(entry.key, entry); map.set(entry.key, entry);
@ -125,18 +134,44 @@ function getUtils({
return t.cloneNode(classedImport); return t.cloneNode(classedImport);
}, },
getCssModuleImport,
getClassNameValue: (className: string) => {
const validId = t.isValidIdentifier(className);
return cssModules
? t.memberExpression(
getCssModuleImport(),
validId ? t.identifier(className) : t.stringLiteral(className),
!validId
)
: t.stringLiteral(className);
},
finish(node: t.Program) { finish(node: t.Program) {
const { filename } = state; const { filename } = state;
if (!cssMap.size && !jsMap.size) return; if (!cssMap.size && !jsMap.size) return;
invariant(filename, "babel: missing state.filename"); invariant(filename, "babel: missing state.filename");
if (cssMap.size) { if (cssMap.size) {
const cssName = basename(filename, extname(filename)) + ".css"; const cssName =
basename(filename, extname(filename)) +
(cssModuleImport ? ".module" : "") +
".css";
const path = join(dirname(filename), cssName); const path = join(dirname(filename), cssName);
const value = Array.from(cssMap.values()); const value = Array.from(cssMap.values());
const importee = `tailwind:./${cssName}` + getSuffix(bustCache, value);
if (cssModuleImport) {
const importee = `tailwind:./${cssName}` + getSuffix(bustCache, value);
node.body.unshift(
t.importDeclaration(
[t.importDefaultSpecifier(cssModuleImport)],
t.stringLiteral(importee)
)
);
} else {
const importee = `tailwind:./${cssName}` + getSuffix(bustCache, value);
node.body.unshift(t.importDeclaration([], t.stringLiteral(importee))); node.body.unshift(t.importDeclaration([], t.stringLiteral(importee)));
}
styleMap.set(path, value); styleMap.set(path, value);
onCollect?.(path, value); onCollect?.(path, value);
@ -212,7 +247,7 @@ export function babelTailwind(
classNames: trimmed, classNames: trimmed,
location: _.sliceText(node), location: _.sliceText(node),
}); });
path.replaceWith(t.stringLiteral(className)); path.replaceWith(_.getClassNameValue(className));
} }
}, },
ArrayExpression(path) { ArrayExpression(path) {
@ -228,7 +263,7 @@ export function babelTailwind(
classNames: trimmed, classNames: trimmed,
location: _.sliceText(path.node), location: _.sliceText(path.node),
}); });
path.replaceWith(t.stringLiteral(className)); path.replaceWith(_.getClassNameValue(className));
}, },
JSXExpressionContainer(path) { JSXExpressionContainer(path) {
go(path.get("expression")); go(path.get("expression"));

View File

@ -1,9 +1,10 @@
import { readFileSync } from "node:fs"; import { readFileSync } from "node:fs";
import { extname } from "node:path"; import { extname } from "node:path";
import { once } from "lodash-es";
import type babel from "@babel/core"; import type babel from "@babel/core";
import type * as esbuild from "esbuild";
import { transformSync } from "@babel/core"; import { transformSync } from "@babel/core";
import type * as esbuild from "esbuild";
import { once } from "lodash-es";
/** /**
* An esbuild plugin that processes files with Babel if `plugins` is not empty. * An esbuild plugin that processes files with Babel if `plugins` is not empty.

View File

@ -19,7 +19,7 @@ export { createPostCSS } from "./shared";
type GetClassName = (className: string) => string; type GetClassName = (className: string) => string;
export type BuildStyleFile = ( export type BuildStyleFile = (
path: string path: string
) => Promise<readonly ["css", string] | readonly ["js", string]>; ) => Promise<readonly ["css" | "local-css", string] | readonly ["js", string]>;
export interface TailwindPluginOptions { export interface TailwindPluginOptions {
/** /**
@ -86,6 +86,17 @@ export interface TailwindPluginOptions {
* Keep the original classnames in the CSS output * Keep the original classnames in the CSS output
*/ */
addSourceAsComment?: boolean; addSourceAsComment?: boolean;
/**
* Emit as CSS modules
*/
cssModules?: boolean;
/**
* Emit type. `css-import` for plain CSS import,
* `css-module` for CSS modules, `css-in-js` for JS.
*/
// emitType: "css-import" | "css-module" | "css-in-js";
} }
export type ResolveTailwindOptions = SetRequired< export type ResolveTailwindOptions = SetRequired<
@ -121,7 +132,7 @@ export const getClassName: GetClassName = cls => "tw-" + hash(cls);
* }); * });
*/ */
export function getTailwindPlugins(options: TailwindPluginOptions) { export function getTailwindPlugins(options: TailwindPluginOptions) {
const { addSourceAsComment, compile: _compile } = options; const { addSourceAsComment, compile: _compile, cssModules } = options;
const resolvedOptions: ResolveTailwindOptions = { const resolvedOptions: ResolveTailwindOptions = {
getClassName, getClassName,
jsxAttributeAction: "delete", jsxAttributeAction: "delete",
@ -155,7 +166,10 @@ export function getTailwindPlugins(options: TailwindPluginOptions) {
.join("\n") .join("\n")
); );
if (path.endsWith(".css")) { if (path.endsWith(".css")) {
return ["css", transformSync(compiled, { loader: "css" }).code] as const; return [
cssModules ? "local-css" : "css",
transformSync(compiled, { loader: "css" }).code,
] as const;
} else if (path.endsWith(".js")) { } else if (path.endsWith(".js")) {
const js = toJSCode(compiled, x => x.slice(1)); const js = toJSCode(compiled, x => x.slice(1));
return ["js", js] as const; return ["js", js] as const;
@ -176,6 +190,7 @@ export function getTailwindPlugins(options: TailwindPluginOptions) {
styleMap, styleMap,
options, options,
getCompiler, getCompiler,
buildStyleFile,
[Symbol.dispose]() { [Symbol.dispose]() {
styleMap.clear(); styleMap.clear();
}, },

13
src/vendor/animate.ts vendored
View File

@ -1,4 +1,5 @@
// https://github.com/jamiebuilds/tailwindcss-animate/commit/ac0dd3a3c81681b78f1d8ea5e7478044213995e1 // https://github.com/jamiebuilds/tailwindcss-animate/commit/ac0dd3a3c81681b78f1d8ea5e7478044213995e1
// https://github.com/tailwindlabs/tailwindcss/discussions/11164#discussioncomment-5819097
import plugin from "tailwindcss/plugin.js"; import plugin from "tailwindcss/plugin.js";
import type { PluginAPI } from "tailwindcss/types/config"; import type { PluginAPI } from "tailwindcss/types/config";
@ -11,11 +12,7 @@ function filterDefault<T extends object>(values: T) {
export default plugin( export default plugin(
({ addUtilities, matchUtilities, theme }) => { ({ addUtilities, matchUtilities, theme }) => {
addUtilities({ addUtilities({
"@keyframes enter": theme("keyframes.enter"),
"@keyframes exit": theme("keyframes.exit"),
".animate-in": { ".animate-in": {
animationName: "enter",
animationDuration: theme("animationDuration.DEFAULT"),
"--tw-enter-opacity": "initial", "--tw-enter-opacity": "initial",
"--tw-enter-scale": "initial", "--tw-enter-scale": "initial",
"--tw-enter-rotate": "initial", "--tw-enter-rotate": "initial",
@ -23,8 +20,6 @@ export default plugin(
"--tw-enter-translate-y": "initial", "--tw-enter-translate-y": "initial",
}, },
".animate-out": { ".animate-out": {
animationName: "exit",
animationDuration: theme("animationDuration.DEFAULT"),
"--tw-exit-opacity": "initial", "--tw-exit-opacity": "initial",
"--tw-exit-scale": "initial", "--tw-exit-scale": "initial",
"--tw-exit-rotate": "initial", "--tw-exit-rotate": "initial",
@ -168,6 +163,10 @@ export default plugin(
1: "1", 1: "1",
infinite: "infinite", infinite: "infinite",
}, },
animation: ({ theme }) => ({
out: `leave ${theme("animationDuration.DEFAULT")}`,
in: `enter ${theme("animationDuration.DEFAULT")}`,
}),
keyframes: { keyframes: {
enter: { enter: {
from: { from: {
@ -176,7 +175,7 @@ export default plugin(
"translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))", "translate3d(var(--tw-enter-translate-x, 0), var(--tw-enter-translate-y, 0), 0) scale3d(var(--tw-enter-scale, 1), var(--tw-enter-scale, 1), var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))",
}, },
}, },
exit: { leave: {
to: { to: {
opacity: "var(--tw-exit-opacity, 1)", opacity: "var(--tw-exit-opacity, 1)",
transform: transform: