Update
This commit is contained in:
parent
2b3812b2ba
commit
d4404f7ae2
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@aet/tailwind",
|
"name": "@aet/tailwind",
|
||||||
"version": "0.0.1-beta.30",
|
"version": "0.0.1-beta.33",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "./scripts/index.ts",
|
"build": "./scripts/index.ts",
|
||||||
|
@ -38,13 +38,13 @@ await Promise.all([
|
|||||||
2
|
2
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Bun.write(`dist/base.d.ts`, `/**\n * \`@tailwind base\` component.\n */\nexport {};`),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fs.copyFile("README.md", "dist/README.md"),
|
fs.copyFile("README.md", "dist/README.md"),
|
||||||
fs.copyFile("LICENSE.md", "dist/LICENSE.md"),
|
fs.copyFile("LICENSE.md", "dist/LICENSE.md"),
|
||||||
fs.copyFile("src/macro.d.ts", "dist/macro.d.ts"),
|
fs.copyFile("src/macro.d.ts", "dist/macro.d.ts"),
|
||||||
|
Bun.write(`dist/base.d.ts`, `/**\n * \`@tailwind base\` component.\n */\nexport {};`),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
@ -42,4 +42,51 @@ describe("babel-tailwind", () => {
|
|||||||
);
|
);
|
||||||
expect(files.js.text).toContain(`style: ${clsName}`);
|
expect(files.js.text).toContain(`style: ${clsName}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("supports .hover, .focus, .active, .group-hover, .group-focus, .group-active", async () => {
|
||||||
|
const { files } = await compileESBuild({
|
||||||
|
clsx: "emotion",
|
||||||
|
expectFiles: 1,
|
||||||
|
javascript: `
|
||||||
|
import { tws } from "@aet/tailwind/macro";
|
||||||
|
|
||||||
|
export const style = [
|
||||||
|
tws.hover\`font-semibold\`,
|
||||||
|
tws.focus\`font-bold\`,
|
||||||
|
tws.active\`font-light\`,
|
||||||
|
tws.hover.active\`p-2\`,
|
||||||
|
];
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const semibold = getClassName("hover:font-semibold").replace(/^tw-/, "tw_");
|
||||||
|
const bold = getClassName("focus:font-bold").replace(/^tw-/, "tw_");
|
||||||
|
const light = getClassName("active:font-light").replace(/^tw-/, "tw_");
|
||||||
|
const p = getClassName("active:hover:p-2").replace(/^tw-/, "tw_");
|
||||||
|
|
||||||
|
expect(files.js.text).toContain(
|
||||||
|
[
|
||||||
|
`var ${bold} = {`,
|
||||||
|
' "&:focus": {',
|
||||||
|
' fontWeight: "700"',
|
||||||
|
" }",
|
||||||
|
"};",
|
||||||
|
`var ${light} = {`,
|
||||||
|
' "&:active": {',
|
||||||
|
' fontWeight: "300"',
|
||||||
|
" }",
|
||||||
|
"};",
|
||||||
|
`var ${p} = {`,
|
||||||
|
' "&:hover:active": {',
|
||||||
|
' padding: "0.5rem"',
|
||||||
|
" }",
|
||||||
|
"};",
|
||||||
|
`var ${semibold} = {`,
|
||||||
|
' "&:hover": {',
|
||||||
|
' fontWeight: "600"',
|
||||||
|
" }",
|
||||||
|
"};",
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -51,4 +51,22 @@ describe("babel-tailwind", () => {
|
|||||||
].join("\n")
|
].join("\n")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("passes through `group` className", async () => {
|
||||||
|
const { files } = await compileESBuild({
|
||||||
|
clsx: "emotion",
|
||||||
|
expectFiles: 2,
|
||||||
|
javascript: `
|
||||||
|
import { tw } from "@aet/tailwind/macro";
|
||||||
|
|
||||||
|
export default tw\`group hover:text-center\`;
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const clsName = getClassName("group hover:text-center");
|
||||||
|
expect(files.js.text).toContain(`"${clsName} group"`);
|
||||||
|
expect(files.css.text).toMatch(
|
||||||
|
[`.${clsName}:hover {`, " text-align: center;", "}"].join("\n")
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -42,8 +42,6 @@ export function getBuild(name: string) {
|
|||||||
}) {
|
}) {
|
||||||
const tailwind = getTailwindPlugins({
|
const tailwind = getTailwindPlugins({
|
||||||
tailwindConfig: {},
|
tailwindConfig: {},
|
||||||
macroFunction: "tw",
|
|
||||||
macroStyleFunction: "tws",
|
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
const result = await esbuild.build({
|
const result = await esbuild.build({
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { basename, dirname, extname, join } from "node:path";
|
import { basename, dirname, extname, join } from "node:path";
|
||||||
import type babel from "@babel/core";
|
import type b from "@babel/core";
|
||||||
import hash from "@emotion/hash";
|
import hash from "@emotion/hash";
|
||||||
import { isPlainObject } from "lodash";
|
import { isPlainObject } from "lodash";
|
||||||
import invariant from "tiny-invariant";
|
import invariant from "tiny-invariant";
|
||||||
@ -8,23 +8,25 @@ import { type SourceLocation, type StyleMapEntry, macroName } from "./shared";
|
|||||||
import { type ResolveTailwindOptions, getClassName } from "./index";
|
import { type ResolveTailwindOptions, getClassName } from "./index";
|
||||||
|
|
||||||
export type ClassNameCollector = (path: string, entries: StyleMapEntry[]) => void;
|
export type ClassNameCollector = (path: string, entries: StyleMapEntry[]) => void;
|
||||||
type BabelTypes = typeof babel.types;
|
type BabelTypes = typeof b.types;
|
||||||
type Type = "css" | "js";
|
type Type = "css" | "js";
|
||||||
|
|
||||||
export function babelTailwind(
|
export function babelTailwind(
|
||||||
{
|
options: ResolveTailwindOptions,
|
||||||
|
onCollect: ClassNameCollector | undefined
|
||||||
|
) {
|
||||||
|
const {
|
||||||
styleMap,
|
styleMap,
|
||||||
clsx,
|
clsx,
|
||||||
getClassName: getClass = getClassName,
|
getClassName: getClass = getClassName,
|
||||||
jsxAttributeAction = "delete",
|
jsxAttributeAction = "delete",
|
||||||
jsxAttributeName = "css",
|
jsxAttributeName = "css",
|
||||||
vite,
|
vite: bustCache,
|
||||||
}: ResolveTailwindOptions,
|
} = options;
|
||||||
onCollect: ClassNameCollector | undefined
|
|
||||||
) {
|
|
||||||
type BabelPluginState = ReturnType<typeof getState>;
|
|
||||||
|
|
||||||
function getState(path: NodePath<t.Program>, state: babel.PluginPass, t: BabelTypes) {
|
type BabelPluginUtils = ReturnType<typeof getUtils>;
|
||||||
|
|
||||||
|
function getUtils(path: NodePath<t.Program>, state: b.PluginPass, t: BabelTypes) {
|
||||||
let cx: t.Identifier;
|
let cx: t.Identifier;
|
||||||
let styleImport: t.Identifier;
|
let styleImport: t.Identifier;
|
||||||
|
|
||||||
@ -58,7 +60,15 @@ export function babelTailwind(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
replaceWithImport(type: Type, path: NodePath, className: string) {
|
replaceWithImport({
|
||||||
|
type,
|
||||||
|
path,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
type: Type;
|
||||||
|
path: NodePath;
|
||||||
|
className: string;
|
||||||
|
}) {
|
||||||
if (type === "css") {
|
if (type === "css") {
|
||||||
path.replaceWith(t.stringLiteral(className));
|
path.replaceWith(t.stringLiteral(className));
|
||||||
} else {
|
} else {
|
||||||
@ -86,7 +96,7 @@ export function babelTailwind(
|
|||||||
const cssName = basename(filename, extname(filename)) + ".css";
|
const cssName = basename(filename, extname(filename)) + ".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(vite, value);
|
const importee = `tailwind:./${cssName}` + getSuffix(bustCache, value);
|
||||||
|
|
||||||
node.body.unshift(t.importDeclaration([], t.stringLiteral(importee)));
|
node.body.unshift(t.importDeclaration([], t.stringLiteral(importee)));
|
||||||
|
|
||||||
@ -98,7 +108,7 @@ export function babelTailwind(
|
|||||||
const jsName = basename(filename, extname(filename)) + ".tailwindStyle.js";
|
const jsName = basename(filename, extname(filename)) + ".tailwindStyle.js";
|
||||||
const path = join(dirname(filename), jsName);
|
const path = join(dirname(filename), jsName);
|
||||||
const value = Array.from(jsMap.values());
|
const value = Array.from(jsMap.values());
|
||||||
const importee = `tailwind:./${jsName}` + getSuffix(vite, value);
|
const importee = `tailwind:./${jsName}` + getSuffix(bustCache, value);
|
||||||
|
|
||||||
node.body.unshift(
|
node.body.unshift(
|
||||||
t.importDeclaration(
|
t.importDeclaration(
|
||||||
@ -113,23 +123,21 @@ export function babelTailwind(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return definePlugin<BabelPluginState>(({ types: t }) => ({
|
return definePlugin<BabelPluginUtils>(({ types: t }) => ({
|
||||||
Program: {
|
Program: {
|
||||||
enter(path, state) {
|
enter(path, state) {
|
||||||
const _ = getState(path, state, t);
|
const _ = getUtils(path, state, t);
|
||||||
Object.assign(state, _);
|
Object.assign(state, _);
|
||||||
|
|
||||||
for (const {
|
for (const { callee, imported, prefix } of getMacros(t, path, macroName).map(
|
||||||
local: { parentPath: local },
|
macro => mapMacro(t, macro)
|
||||||
imported,
|
)) {
|
||||||
} of getMacros(t, path, macroName)) {
|
|
||||||
const type = imported === "tw" ? "css" : imported === "tws" ? "js" : undefined;
|
const type = imported === "tw" ? "css" : imported === "tws" ? "js" : undefined;
|
||||||
if (!type) continue;
|
if (!type) continue;
|
||||||
|
|
||||||
if (isNodePath(local, t.isTaggedTemplateExpression)) {
|
if (isNodePath(callee, t.isTaggedTemplateExpression)) {
|
||||||
const { node } = local;
|
const { node } = callee;
|
||||||
const { tag, quasi } = node;
|
const { quasi } = node;
|
||||||
if (!t.isIdentifier(tag)) continue;
|
|
||||||
|
|
||||||
invariant(
|
invariant(
|
||||||
!quasi.expressions.length,
|
!quasi.expressions.length,
|
||||||
@ -138,28 +146,35 @@ export function babelTailwind(
|
|||||||
|
|
||||||
const value = quasi.quasis[0].value.cooked;
|
const value = quasi.quasis[0].value.cooked;
|
||||||
if (value) {
|
if (value) {
|
||||||
const trimmed = trim(value);
|
const list = trimPrefix(value, prefix ? prefix + ":" : undefined);
|
||||||
const className = _.getClass(type, trimmed);
|
const className = _.getClass(type, list.join(" "));
|
||||||
_.recordIfAbsent(type, {
|
_.recordIfAbsent(type, {
|
||||||
key: className,
|
key: className,
|
||||||
className: trimmed,
|
classNames: list,
|
||||||
location: _.sliceText(node),
|
location: _.sliceText(node),
|
||||||
});
|
});
|
||||||
_.replaceWithImport(type, local, className);
|
_.replaceWithImport({
|
||||||
|
type,
|
||||||
|
path: callee,
|
||||||
|
className: addIf(className, list.includes("group") && " group"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (isNodePath(local, t.isCallExpression)) {
|
} else if (isNodePath(callee, t.isCallExpression)) {
|
||||||
const { node } = local;
|
const { node } = callee;
|
||||||
const { callee } = node;
|
if (!t.isIdentifier(node.callee)) continue;
|
||||||
if (!t.isIdentifier(callee)) continue;
|
|
||||||
|
|
||||||
const trimmed = local.get("arguments").flatMap(evaluateArgs).join(" ");
|
const list = callee.get("arguments").flatMap(evaluateArgs);
|
||||||
const className = getClass(trimmed);
|
const className = getClass(list.join(" "));
|
||||||
_.recordIfAbsent(type, {
|
_.recordIfAbsent(type, {
|
||||||
key: className,
|
key: className,
|
||||||
className: trimmed,
|
classNames: list,
|
||||||
location: _.sliceText(node),
|
location: _.sliceText(node),
|
||||||
});
|
});
|
||||||
_.replaceWithImport(type, local, className);
|
_.replaceWithImport({
|
||||||
|
type,
|
||||||
|
path: callee,
|
||||||
|
className: addIf(className, list.includes("group") && " group"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -190,11 +205,11 @@ export function babelTailwind(
|
|||||||
const { node } = path;
|
const { node } = path;
|
||||||
const { value } = node;
|
const { value } = node;
|
||||||
const trimmed = trim(value);
|
const trimmed = trim(value);
|
||||||
if (trimmed) {
|
if (trimmed.length) {
|
||||||
const className = getClass(trimmed);
|
const className = getClass(trimmed.join(" "));
|
||||||
_.recordIfAbsent("css", {
|
_.recordIfAbsent("css", {
|
||||||
key: className,
|
key: className,
|
||||||
className: trimmed,
|
classNames: trimmed,
|
||||||
location: _.sliceText(node),
|
location: _.sliceText(node),
|
||||||
});
|
});
|
||||||
path.replaceWith(t.stringLiteral(className));
|
path.replaceWith(t.stringLiteral(className));
|
||||||
@ -206,11 +221,11 @@ export function babelTailwind(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
ObjectExpression(path) {
|
ObjectExpression(path) {
|
||||||
const trimmed = evaluateArgs(path).join(" ");
|
const trimmed = evaluateArgs(path);
|
||||||
const className = getClass(trimmed);
|
const className = getClass(trimmed.join(" "));
|
||||||
_.recordIfAbsent("css", {
|
_.recordIfAbsent("css", {
|
||||||
key: className,
|
key: className,
|
||||||
className: trimmed,
|
classNames: trimmed,
|
||||||
location: _.sliceText(path.node),
|
location: _.sliceText(path.node),
|
||||||
});
|
});
|
||||||
path.replaceWith(t.stringLiteral(className));
|
path.replaceWith(t.stringLiteral(className));
|
||||||
@ -245,7 +260,7 @@ export function babelTailwind(
|
|||||||
|
|
||||||
if (classNameAttribute) {
|
if (classNameAttribute) {
|
||||||
const attrValue = classNameAttribute.value!;
|
const attrValue = classNameAttribute.value!;
|
||||||
const wrap = (originalValue: babel.types.Expression) =>
|
const wrap = (originalValue: b.types.Expression) =>
|
||||||
t.callExpression(_.getCx(), [originalValue, valuePathNode]);
|
t.callExpression(_.getCx(), [originalValue, valuePathNode]);
|
||||||
|
|
||||||
// If both are string literals, we can merge them directly here
|
// If both are string literals, we can merge them directly here
|
||||||
@ -308,7 +323,7 @@ function evaluateArgs(path: NodePath) {
|
|||||||
invariant(confident, "Argument cannot be statically evaluated");
|
invariant(confident, "Argument cannot be statically evaluated");
|
||||||
|
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
return [trim(value)];
|
return trim(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPlainObject(value)) {
|
if (isPlainObject(value)) {
|
||||||
@ -384,13 +399,32 @@ function getMacros(
|
|||||||
return macros;
|
return macros;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapMacro(t: BabelTypes, macro: ReturnType<typeof getMacros>[number]) {
|
||||||
|
let callee = macro.local.parentPath;
|
||||||
|
const prefix: string[] = [];
|
||||||
|
|
||||||
|
while (isNodePath(callee, t.isMemberExpression)) {
|
||||||
|
invariant(t.isIdentifier(callee.node.property), "Invalid member expression");
|
||||||
|
prefix.unshift(
|
||||||
|
callee.node.property.name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()
|
||||||
|
);
|
||||||
|
callee = callee.parentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
callee,
|
||||||
|
imported: macro.imported,
|
||||||
|
prefix: prefix.length ? prefix.join(":") : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const definePlugin =
|
const definePlugin =
|
||||||
<T>(fn: (runtime: typeof babel) => babel.Visitor<babel.PluginPass & T>) =>
|
<T>(fn: (runtime: typeof b) => b.Visitor<b.PluginPass & T>) =>
|
||||||
(runtime: typeof babel) => {
|
(runtime: typeof b) => {
|
||||||
const plugin: babel.PluginObj<babel.PluginPass & T> = {
|
const plugin: b.PluginObj<b.PluginPass & T> = {
|
||||||
visitor: fn(runtime),
|
visitor: fn(runtime),
|
||||||
};
|
};
|
||||||
return plugin as babel.PluginObj;
|
return plugin as b.PluginObj;
|
||||||
};
|
};
|
||||||
|
|
||||||
const extractJSXContainer = (attr: NonNullable<t.JSXAttribute["value"]>): t.Expression =>
|
const extractJSXContainer = (attr: NonNullable<t.JSXAttribute["value"]>): t.Expression =>
|
||||||
@ -398,13 +432,17 @@ const extractJSXContainer = (attr: NonNullable<t.JSXAttribute["value"]>): t.Expr
|
|||||||
|
|
||||||
function matchPath(
|
function matchPath(
|
||||||
nodePath: NodePath<t.Node | null | undefined>,
|
nodePath: NodePath<t.Node | null | undefined>,
|
||||||
fns: (dig: (nodePath: NodePath<t.Node | null | undefined>) => void) => babel.Visitor
|
fns: (dig: (nodePath: NodePath<t.Node | null | undefined>) => void) => b.Visitor
|
||||||
) {
|
) {
|
||||||
if (!nodePath.node) return;
|
if (!nodePath.node) return;
|
||||||
const fn = fns(path => matchPath(path, fns))[nodePath.node.type] as any;
|
const fn = fns(path => matchPath(path, fns))[nodePath.node.type] as any;
|
||||||
fn?.(nodePath);
|
fn?.(nodePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addIf(text: string, suffix: string | false) {
|
||||||
|
return suffix ? text + suffix : text;
|
||||||
|
}
|
||||||
|
|
||||||
const isNodePath = <T extends t.Node>(
|
const isNodePath = <T extends t.Node>(
|
||||||
nodePath: NodePath<t.Node | null | undefined> | null,
|
nodePath: NodePath<t.Node | null | undefined> | null,
|
||||||
predicate: (node: t.Node) => node is T
|
predicate: (node: t.Node) => node is T
|
||||||
@ -413,15 +451,12 @@ const isNodePath = <T extends t.Node>(
|
|||||||
function getSuffix(add: boolean | undefined, entries: StyleMapEntry[]) {
|
function getSuffix(add: boolean | undefined, entries: StyleMapEntry[]) {
|
||||||
if (!add) return "";
|
if (!add) return "";
|
||||||
|
|
||||||
const cacheKey = hash(entries.map(x => x.className).join(","));
|
const cacheKey = hash(entries.map(x => x.classNames).join(","));
|
||||||
return `?${cacheKey}`;
|
return `?${cacheKey}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const trim = (value: string) => value.replace(/\s+/g, " ").trim();
|
const trim = (value: string) => value.replace(/\s+/g, " ").trim().split(" ");
|
||||||
const trimPrefix = (cls: string, prefix: string) =>
|
const trimPrefix = (cls: string, prefix = "") => trim(cls).map(value => prefix + value);
|
||||||
trim(cls)
|
|
||||||
.split(" ")
|
|
||||||
.map(value => prefix + value);
|
|
||||||
|
|
||||||
const flatMapEntries = <K extends string | number, V, R>(
|
const flatMapEntries = <K extends string | number, V, R>(
|
||||||
map: Record<K, V>,
|
map: Record<K, V>,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import cx from "clsx";
|
|
||||||
import { type FunctionComponent, forwardRef } from "react";
|
import { type FunctionComponent, forwardRef } from "react";
|
||||||
|
import cx from "clsx";
|
||||||
|
|
||||||
interface WithClassName<Props = object> extends FunctionComponent<Props> {
|
interface WithClassName<Props = object> extends FunctionComponent<Props> {
|
||||||
className: string;
|
className: string;
|
||||||
@ -22,12 +22,12 @@ export const classed: {
|
|||||||
type: "input",
|
type: "input",
|
||||||
className: PresetClassName<InputProps>,
|
className: PresetClassName<InputProps>,
|
||||||
defaultProps?: Partial<InputProps>
|
defaultProps?: Partial<InputProps>
|
||||||
): InputProps;
|
): React.FunctionComponent<InputProps>;
|
||||||
<K extends keyof JSX.IntrinsicElements>(
|
<K extends keyof JSX.IntrinsicElements>(
|
||||||
type: K,
|
type: K,
|
||||||
className: PresetClassName<JSX.IntrinsicElements[K]>,
|
className: PresetClassName<JSX.IntrinsicElements[K]>,
|
||||||
defaultProps?: Partial<JSX.IntrinsicElements[K]>
|
defaultProps?: Partial<JSX.IntrinsicElements[K]>
|
||||||
): JSX.IntrinsicElements[K];
|
): React.FunctionComponent<JSX.IntrinsicElements[K]>;
|
||||||
(
|
(
|
||||||
type: string,
|
type: string,
|
||||||
className: PresetClassName,
|
className: PresetClassName,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { dirname, join } from "node:path";
|
import { dirname, join } from "node:path";
|
||||||
import type * as esbuild from "esbuild";
|
import type * as esbuild from "esbuild";
|
||||||
import { CssSyntaxError } from "postcss";
|
import { CssSyntaxError } from "postcss";
|
||||||
import { type Compile, type StyleMap, pkgName } from "./shared";
|
import { type Compile, type StyleMap, type StyleMapEntry, pkgName } from "./shared";
|
||||||
import type { BuildStyleFile } from "./index";
|
import type { BuildStyleFile } from "./index";
|
||||||
|
|
||||||
const PLUGIN_NAME = "tailwind";
|
const PLUGIN_NAME = "tailwind";
|
||||||
@ -43,7 +43,6 @@ export const esbuildPlugin = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!styleMap.has(path)) return;
|
if (!styleMap.has(path)) return;
|
||||||
|
|
||||||
const styles = styleMap.get(path)!;
|
const styles = styleMap.get(path)!;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -51,47 +50,51 @@ export const esbuildPlugin = ({
|
|||||||
return { contents, loader };
|
return { contents, loader };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof CssSyntaxError) {
|
if (e instanceof CssSyntaxError) {
|
||||||
const lines = e.source!.split("\n");
|
return buildError(e, styles);
|
||||||
const cls = lines
|
|
||||||
.at(e.line! - 2)!
|
|
||||||
.slice(1, -1)
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
const entry = styles.find(s => s.key === cls)!;
|
|
||||||
if (!entry) {
|
|
||||||
console.error(e);
|
|
||||||
throw new Error("Could not find entry for CSS");
|
|
||||||
}
|
|
||||||
|
|
||||||
const { location: loc } = entry;
|
|
||||||
const errLoc: Partial<esbuild.Location> = {
|
|
||||||
file: loc.filename,
|
|
||||||
line: loc.start.line,
|
|
||||||
column: loc.start.column,
|
|
||||||
length: loc.end.column - loc.start.column,
|
|
||||||
lineText: loc.text,
|
|
||||||
};
|
|
||||||
|
|
||||||
const doesNotExist = e.reason.match(/The `(.+)` class does not exist/)?.[1];
|
|
||||||
if (doesNotExist) {
|
|
||||||
const index = loc.text.indexOf(doesNotExist, loc.start.column);
|
|
||||||
if (index !== -1) {
|
|
||||||
errLoc.column = index;
|
|
||||||
errLoc.length = doesNotExist.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
text: e.reason,
|
|
||||||
location: errLoc,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function buildError(e: CssSyntaxError, styles: StyleMapEntry[]) {
|
||||||
|
const lines = e.source!.split("\n");
|
||||||
|
const cls = lines
|
||||||
|
.at(e.line! - 2)!
|
||||||
|
.slice(1, -1)
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
const entry = styles.find(s => s.key === cls)!;
|
||||||
|
if (!entry) {
|
||||||
|
console.error(e);
|
||||||
|
throw new Error("Could not find entry for CSS");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { location: loc } = entry;
|
||||||
|
const errLoc: Partial<esbuild.Location> = {
|
||||||
|
file: loc.filename,
|
||||||
|
line: loc.start.line,
|
||||||
|
column: loc.start.column,
|
||||||
|
length: loc.end.column - loc.start.column,
|
||||||
|
lineText: loc.text,
|
||||||
|
};
|
||||||
|
|
||||||
|
const doesNotExist = e.reason.match(/The `(.+)` class does not exist/)?.[1];
|
||||||
|
if (doesNotExist) {
|
||||||
|
const index = loc.text.indexOf(doesNotExist, loc.start.column);
|
||||||
|
if (index !== -1) {
|
||||||
|
errLoc.column = index;
|
||||||
|
errLoc.length = doesNotExist.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
text: e.reason,
|
||||||
|
location: errLoc,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ import hash from "@emotion/hash";
|
|||||||
import type { Config } from "tailwindcss";
|
import type { Config } from "tailwindcss";
|
||||||
import type { SetRequired } from "type-fest";
|
import type { SetRequired } from "type-fest";
|
||||||
import type postcss from "postcss";
|
import type postcss from "postcss";
|
||||||
import { memoize } from "lodash";
|
import { memoize, without } from "lodash";
|
||||||
import { type ClassNameCollector, babelTailwind } from "./babel-tailwind";
|
import { type ClassNameCollector, babelTailwind } from "./babel-tailwind";
|
||||||
import { esbuildPlugin } from "./esbuild-postcss";
|
import { esbuildPlugin } from "./esbuild-postcss";
|
||||||
import { vitePlugin } from "./vite-plugin";
|
import { vitePlugin } from "./vite-plugin";
|
||||||
@ -130,7 +130,12 @@ export function getTailwindPlugins(options: TailwindPluginOptions) {
|
|||||||
const buildStyleFile: BuildStyleFile = async path => {
|
const buildStyleFile: BuildStyleFile = async path => {
|
||||||
const styles = styleMap.get(path)!;
|
const styles = styleMap.get(path)!;
|
||||||
const compiled = await compile(
|
const compiled = await compile(
|
||||||
styles.map(({ className, key }) => `.${key} {\n @apply ${className}\n}`).join("\n")
|
styles
|
||||||
|
.map(
|
||||||
|
({ classNames, key }) =>
|
||||||
|
`.${key} {\n @apply ${without(classNames, "group").join(" ")}\n}`
|
||||||
|
)
|
||||||
|
.join("\n")
|
||||||
);
|
);
|
||||||
if (path.endsWith(".css")) {
|
if (path.endsWith(".css")) {
|
||||||
return ["css", compiled] as const;
|
return ["css", compiled] as const;
|
||||||
|
62
src/macro.d.ts
vendored
62
src/macro.d.ts
vendored
@ -4,23 +4,77 @@ interface RecursiveStringObject {
|
|||||||
|
|
||||||
type CSSAttributeValue = string | (string | RecursiveStringObject)[];
|
type CSSAttributeValue = string | (string | RecursiveStringObject)[];
|
||||||
|
|
||||||
|
type Modifier =
|
||||||
|
| "2xl"
|
||||||
|
| "active"
|
||||||
|
| "after"
|
||||||
|
| "backdrop"
|
||||||
|
| "before"
|
||||||
|
| "contrastMore"
|
||||||
|
| "dark"
|
||||||
|
| "default"
|
||||||
|
| "disabled"
|
||||||
|
| "even"
|
||||||
|
| "file"
|
||||||
|
| "first"
|
||||||
|
| "firstLetter"
|
||||||
|
| "firstLine"
|
||||||
|
| "firstOfType"
|
||||||
|
| "focus"
|
||||||
|
| "focusVisible"
|
||||||
|
| "focusWithin"
|
||||||
|
| "forcedColors"
|
||||||
|
| "hover"
|
||||||
|
| "indeterminate"
|
||||||
|
| "invalid"
|
||||||
|
| "last"
|
||||||
|
| "lastOfType"
|
||||||
|
| "ltr"
|
||||||
|
| "marker"
|
||||||
|
| "max2xl"
|
||||||
|
| "maxLg"
|
||||||
|
| "maxMd"
|
||||||
|
| "maxSm"
|
||||||
|
| "maxXl"
|
||||||
|
| "md"
|
||||||
|
| "motionReduce"
|
||||||
|
| "motionSafe"
|
||||||
|
| "odd"
|
||||||
|
| "only"
|
||||||
|
| "open"
|
||||||
|
| "placeholder"
|
||||||
|
| "prefersContrast"
|
||||||
|
| "print"
|
||||||
|
| "readOnly"
|
||||||
|
| "required"
|
||||||
|
| "rtl"
|
||||||
|
| "selection"
|
||||||
|
| "sm"
|
||||||
|
| "target"
|
||||||
|
| "visited"
|
||||||
|
| "xl";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tagged template macro function combining Tailwind classes
|
* Tagged template macro function combining Tailwind classes
|
||||||
* @example "tw" => tw`p-2 text-center`
|
* @example "tw" => tw`p-2 text-center`
|
||||||
*/
|
*/
|
||||||
export interface TailwindFunction {
|
export type TailwindFunction = {
|
||||||
(strings: TemplateStringsArray): string;
|
(strings: TemplateStringsArray): string;
|
||||||
(...args: (string | RecursiveStringObject)[]): string;
|
(...args: (string | RecursiveStringObject)[]): string;
|
||||||
}
|
} & {
|
||||||
|
[key in Modifier]: TailwindFunction;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tagged template macro function compiling Tailwind styles
|
* Tagged template macro function compiling Tailwind styles
|
||||||
* @example "tws" => tws`p-2 text-center` // { padding: 2, textAlign: "center" }
|
* @example "tws" => tws`p-2 text-center` // { padding: 2, textAlign: "center" }
|
||||||
*/
|
*/
|
||||||
export interface TailwindStyleFunction {
|
export type TailwindStyleFunction = {
|
||||||
(strings: TemplateStringsArray): TailwindStyleFunctionReturn;
|
(strings: TemplateStringsArray): TailwindStyleFunctionReturn;
|
||||||
(...args: (string | RecursiveStringObject)[]): TailwindStyleFunctionReturn;
|
(...args: (string | RecursiveStringObject)[]): TailwindStyleFunctionReturn;
|
||||||
}
|
} & {
|
||||||
|
[key in Modifier]: TailwindStyleFunction;
|
||||||
|
};
|
||||||
|
|
||||||
export const tw: TailwindFunction;
|
export const tw: TailwindFunction;
|
||||||
export const tws: TailwindStyleFunction;
|
export const tws: TailwindStyleFunction;
|
||||||
|
@ -19,7 +19,7 @@ export interface SourceLocation {
|
|||||||
|
|
||||||
export interface StyleMapEntry {
|
export interface StyleMapEntry {
|
||||||
key: string;
|
key: string;
|
||||||
className: string;
|
classNames: string[];
|
||||||
location: SourceLocation;
|
location: SourceLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,15 +47,3 @@ export function createPostCSS({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type Compile = ReturnType<typeof createPostCSS>;
|
export type Compile = ReturnType<typeof createPostCSS>;
|
||||||
|
|
||||||
export function toCSSText(tailwindMap: StyleMapEntry[]) {
|
|
||||||
return tailwindMap
|
|
||||||
.map(({ className, key }) => `.${key} {\n @apply ${className}\n}`)
|
|
||||||
.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toJSText(tailwindMap: StyleMapEntry[]) {
|
|
||||||
return tailwindMap
|
|
||||||
.map(({ className, key }) => `"${key}": "${className}"`)
|
|
||||||
.join(",\n ");
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user