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; /** * 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; /** * 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(); }, }; }