Update
This commit is contained in:
121
src/index.ts
121
src/index.ts
@ -13,6 +13,10 @@ const PLUGIN_NAME = "tailwind";
|
||||
const ESBUILD_NAMESPACE = "babel-tailwind";
|
||||
const ROLLUP_PREFIX = "\0tailwind:";
|
||||
|
||||
const { name } = [require][0](
|
||||
process.env.BABEL_TAILWIND_BUILD ? "./package.json" : "../package.json"
|
||||
);
|
||||
|
||||
const definePlugin =
|
||||
<T>(fn: (runtime: typeof babel) => babel.Visitor<babel.PluginPass & T>) =>
|
||||
(runtime: typeof babel) => {
|
||||
@ -34,12 +38,25 @@ function matchPath(
|
||||
fn?.(nodePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tagged template macro function for Tailwind classes
|
||||
* @example "tw" => tw`p-2 text-center`
|
||||
*/
|
||||
export type TaggedTailwindFunction = (strings: TemplateStringsArray) => string;
|
||||
|
||||
const tailwindDirectives = ["components", "utilities", "variants"] as const;
|
||||
|
||||
export interface TailwindPluginOptions {
|
||||
/**
|
||||
* Tailwind CSS configuration
|
||||
*/
|
||||
tailwindConfig: Omit<Config, "content">;
|
||||
|
||||
/**
|
||||
* Directives to prefix to all Tailwind stylesheets
|
||||
*/
|
||||
directives?: (typeof tailwindDirectives)[number][] | "all";
|
||||
|
||||
/**
|
||||
* Additional PostCSS plugins (optional)
|
||||
*/
|
||||
@ -51,6 +68,15 @@ export interface TailwindPluginOptions {
|
||||
*/
|
||||
jsxAttributeName?: string;
|
||||
|
||||
/**
|
||||
* Tagged template macro function to use for Tailwind classes
|
||||
* @default "tw"
|
||||
* @example
|
||||
* declare const tw: TaggedTailwindFunction;
|
||||
* "tw" => tw`p-2 text-center`
|
||||
*/
|
||||
taggedTemplateName?: string;
|
||||
|
||||
/**
|
||||
* The prefix to use for the generated class names.
|
||||
* @default className => `tw-${hash(className)}`
|
||||
@ -71,50 +97,51 @@ type GetClassName = (className: string) => string;
|
||||
*/
|
||||
export const getClassName: GetClassName = cls => "tw-" + hash(cls);
|
||||
|
||||
const babelTailwind = (
|
||||
interface BabelPluginState {
|
||||
getCx: () => t.Identifier;
|
||||
tailwindMap: Map<string, string>;
|
||||
}
|
||||
|
||||
function babelTailwind(
|
||||
styleMap: Map<string, string>,
|
||||
{
|
||||
clsx,
|
||||
getClassName: getClass = getClassName,
|
||||
taggedTemplateName,
|
||||
jsxAttributeName = "css",
|
||||
}: TailwindPluginOptions
|
||||
) =>
|
||||
definePlugin<{
|
||||
getCx: () => t.Identifier;
|
||||
tailwindMap: Map<string, string>;
|
||||
}>(({ types: t }) => ({
|
||||
) {
|
||||
function getClsxImport(t: typeof babel.types, cx: t.Identifier) {
|
||||
switch (clsx) {
|
||||
case "emotion":
|
||||
return t.importDeclaration(
|
||||
[t.importSpecifier(cx, t.identifier("cx"))],
|
||||
t.stringLiteral("@emotion/css")
|
||||
);
|
||||
case "clsx":
|
||||
return t.importDeclaration(
|
||||
[t.importDefaultSpecifier(cx)],
|
||||
t.stringLiteral("clsx")
|
||||
);
|
||||
case "classnames":
|
||||
return t.importDeclaration(
|
||||
[t.importDefaultSpecifier(cx)],
|
||||
t.stringLiteral("classnames")
|
||||
);
|
||||
default:
|
||||
throw new Error("Unknown clsx library");
|
||||
}
|
||||
}
|
||||
|
||||
return definePlugin<BabelPluginState>(({ types: t }) => ({
|
||||
Program: {
|
||||
enter(path, state) {
|
||||
let cx: t.Identifier;
|
||||
|
||||
state.tailwindMap = new Map();
|
||||
|
||||
function getClsxImport() {
|
||||
switch (clsx) {
|
||||
case "emotion":
|
||||
return t.importDeclaration(
|
||||
[t.importSpecifier(cx, t.identifier("cx"))],
|
||||
t.stringLiteral("@emotion/css")
|
||||
);
|
||||
case "clsx":
|
||||
return t.importDeclaration(
|
||||
[t.importDefaultSpecifier(cx)],
|
||||
t.stringLiteral("clsx")
|
||||
);
|
||||
case "classnames":
|
||||
return t.importDeclaration(
|
||||
[t.importDefaultSpecifier(cx)],
|
||||
t.stringLiteral("classnames")
|
||||
);
|
||||
default:
|
||||
throw new Error("Unknown clsx library");
|
||||
}
|
||||
}
|
||||
|
||||
state.getCx = () => {
|
||||
if (cx == null) {
|
||||
cx = path.scope.generateUidIdentifier("cx");
|
||||
path.node.body.unshift(getClsxImport());
|
||||
path.node.body.unshift(getClsxImport(t, cx));
|
||||
}
|
||||
return t.cloneNode(cx);
|
||||
};
|
||||
@ -140,14 +167,16 @@ const babelTailwind = (
|
||||
},
|
||||
|
||||
TaggedTemplateExpression(path, { tailwindMap }) {
|
||||
if (taggedTemplateName == null) return;
|
||||
|
||||
const {
|
||||
tag,
|
||||
quasi: { quasis, expressions },
|
||||
} = path.node;
|
||||
if (!t.isIdentifier(tag, { name: "tw" })) return;
|
||||
if (!t.isIdentifier(tag, { name: taggedTemplateName })) return;
|
||||
|
||||
if (expressions.length) {
|
||||
throw new Error("tw`` should not contain expressions");
|
||||
throw new Error(`${taggedTemplateName}\`\` should not contain expressions`);
|
||||
}
|
||||
|
||||
const value = quasis[0].value.cooked;
|
||||
@ -220,6 +249,7 @@ const babelTailwind = (
|
||||
path.remove();
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* An esbuild plugin that processes files with Babel if `getPlugins` returns any plugins.
|
||||
@ -264,7 +294,12 @@ export const babelPlugin = ({
|
||||
|
||||
type Compile = ReturnType<typeof createPostCSS>;
|
||||
|
||||
function createPostCSS({ tailwindConfig, postCSSPlugins = [] }: TailwindPluginOptions) {
|
||||
/** @internal */
|
||||
export function createPostCSS({
|
||||
tailwindConfig,
|
||||
postCSSPlugins = [],
|
||||
directives,
|
||||
}: Pick<TailwindPluginOptions, "tailwindConfig" | "postCSSPlugins" | "directives">) {
|
||||
const post = postcss([
|
||||
tailwind({
|
||||
...tailwindConfig,
|
||||
@ -272,7 +307,11 @@ function createPostCSS({ tailwindConfig, postCSSPlugins = [] }: TailwindPluginOp
|
||||
}),
|
||||
...postCSSPlugins,
|
||||
]);
|
||||
return (css: string) => post.process(css, { from: undefined });
|
||||
|
||||
const appliedDirectives = directives === "all" ? tailwindDirectives : directives;
|
||||
const directiveTexts = appliedDirectives?.map(d => `@tailwind ${d};\n`).join("") ?? "";
|
||||
|
||||
return (css: string) => post.process(directiveTexts + css, { from: undefined });
|
||||
}
|
||||
|
||||
const esbuildPlugin = (
|
||||
@ -292,7 +331,19 @@ const esbuildPlugin = (
|
||||
}
|
||||
});
|
||||
|
||||
build.onResolve({ filter: RegExp(`^${name}/base$`) }, () => ({
|
||||
path: "directive:babel",
|
||||
namespace: ESBUILD_NAMESPACE,
|
||||
}));
|
||||
|
||||
build.onLoad({ filter: /.*/, namespace: ESBUILD_NAMESPACE }, async ({ path }) => {
|
||||
if (path === "directive:babel") {
|
||||
return {
|
||||
contents: (await compile(`@tailwind base;`)).css!,
|
||||
loader: "css",
|
||||
};
|
||||
}
|
||||
|
||||
if (!styleMap.has(path)) return;
|
||||
const result = await compile(styleMap.get(path)!);
|
||||
|
||||
|
Reference in New Issue
Block a user