babel-tailwind/src/index.ts
2024-06-29 12:28:04 -04:00

165 lines
3.9 KiB
TypeScript

import hash from "@emotion/hash";
import type { Config } from "tailwindcss";
import type { SetRequired } from "type-fest";
import type postcss from "postcss";
import { memoize } from "lodash";
import { type ClassNameCollector, babelTailwind } from "./babel-tailwind";
import { esbuildPlugin } from "./esbuild-postcss";
import { vitePlugin } from "./vite-plugin";
import { type StyleMap, createPostCSS } from "./shared";
export { babelPlugin } from "./esbuild-babel";
export { createPostCSS } from "./shared";
type GetClassName = (className: string) => string;
/**
* Tagged template macro function for Tailwind classes
* @example "tw" => tw`p-2 text-center`
*/
export interface TailwindFunction {
(strings: TemplateStringsArray): string;
(
...args: (
| string
| ({ data?: { [key: string]: string } } & { [modifier: string]: string })
)[]
): string;
}
export interface TailwindPluginOptions {
/**
* Tailwind CSS configuration
*/
tailwindConfig?: Omit<Config, "content">;
/**
* Prefix to all Tailwind stylesheets
*
* @example
* tailwind base;
* tailwind components;
* tailwind utilities;
*/
prefix?: string;
/**
* Additional PostCSS plugins (optional)
*/
postCSSPlugins?: postcss.AcceptedPlugin[];
/**
* Attribute to use for tailwind classes in JSX
* @default "css"
*/
jsxAttributeName?: string;
/**
* What to do with the original attribute after processing
* @default "delete"
*/
jsxAttributeAction?: "delete" | "preserve" | ["rename", string];
/**
* Template macro function to use for Tailwind classes
* @default "tw"
* @example
* declare const tw: TailwindFunction;
* "tw" => tw`p-2 text-center`
*/
macroFunction?: string | undefined;
/**
* The prefix to use for the generated class names.
* @default className => `tw-${hash(className)}`
*/
getClassName?: GetClassName;
/**
* Preferred library for classnames
*/
clsx: "clsx" | "classnames" | "emotion";
/**
* @internal
*/
styleMap?: StyleMap;
/**
* Custom CSS compile function
* @example
* async css => (await postcss.process(css, { plugins: [tailwindcss()] })).css
*/
compile?(css: string): Promise<string>;
/**
* Using the vite plugin?
*/
vite?: boolean;
}
export type ResolveTailwindOptions = SetRequired<
TailwindPluginOptions,
| "clsx"
| "jsxAttributeAction"
| "jsxAttributeName"
| "postCSSPlugins"
| "styleMap"
| "tailwindConfig"
| "macroFunction"
>;
/**
* Hashes and prefixes a string of Tailwind class names.
* @example getClassName("p-2 text-center") // "tw-1r6fxxz"
*/
export const getClassName: GetClassName = cls => "tw-" + hash(cls);
/**
* Main entry. Returns the plugins and utilities for processing Tailwind
* classNames in JS.
*
* @example
* import { build } from "esbuild";
* import getTailwindPlugins, { babelPlugin } from "babel-tailwind";
*
* const tailwind = getTailwindPlugins(options);
* const result = await build({
* plugins: [
* babelPlugin({ getPlugins: () => [tailwind.babel] }),
* tailwind.esbuild,
* ],
* });
*/
export function getTailwindPlugins(options: TailwindPluginOptions) {
const resolvedOptions: ResolveTailwindOptions = {
getClassName,
jsxAttributeAction: "delete",
jsxAttributeName: "css",
postCSSPlugins: [],
styleMap: new Map(),
macroFunction: undefined,
tailwindConfig: {},
...options,
};
const getCompiler = () => createPostCSS(resolvedOptions);
const { styleMap } = resolvedOptions;
const compile = options.compile ?? memoize(getCompiler());
return {
compile,
babel: (onCollect?: ClassNameCollector) => babelTailwind(resolvedOptions, onCollect),
esbuild: () => esbuildPlugin(styleMap, compile),
/** Requires `options.vite` to be `true`. */
vite: () => vitePlugin(styleMap, compile),
styleMap,
options,
getCompiler,
[Symbol.dispose]() {
styleMap.clear();
},
};
}