Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,10 @@
"esbuild",
"unrs-resolver"
]
},
"dependencies": {
"class-variance-authority": "^0.7.1",
"tailwind-merge": "^3.4.0",
"tailwind-variants": "^3.2.2"
}
}
49 changes: 49 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/babel/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import { jsxAttributeVisitor } from "./plugin/visitors/className.js";
import { importDeclarationVisitor } from "./plugin/visitors/imports.js";
import { programEnter, programExit } from "./plugin/visitors/program.js";
import { callExpressionVisitor, taggedTemplateVisitor } from "./plugin/visitors/tw.js";
import {
variantCallVisitor,
variantDefinitionVisitor,
variantImportVisitor,
} from "./plugin/visitors/variants.js";

// Re-export PluginOptions for external use
export type { PluginOptions };
Expand Down Expand Up @@ -56,13 +61,23 @@ export default function reactNativeTailwindBabelPlugin(

ImportDeclaration(path, state) {
importDeclarationVisitor(path, state, t);
// Also track variant imports (tv/cva)
variantImportVisitor(path, state, t);
},

VariableDeclarator(path, state) {
// Process variant function definitions (const button = tv({...}))
variantDefinitionVisitor(path, state, t);
},

TaggedTemplateExpression(path, state) {
taggedTemplateVisitor(path, state, t);
},

CallExpression(path, state) {
// First check if this is a variant function call
variantCallVisitor(path, state, t);
// Then check for tw/twStyle calls
callExpressionVisitor(path, state, t);
},

Expand Down
47 changes: 47 additions & 0 deletions src/babel/plugin/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,44 @@ import type { StyleObject } from "../../types/core.js";
import type { CustomTheme } from "../config-loader.js";
import { extractCustomTheme } from "../config-loader.js";
import { DEFAULT_CLASS_ATTRIBUTES, buildAttributeMatchers } from "../utils/attributeMatchers.js";
import type { VariantFunctionEntry } from "../utils/variantProcessing.js";

/**
* Types of class utilities we track and transform
*
* - "tv" / "cva": Variant function creators (create functions called later)
* - "twMerge": Class merger with conflict resolution
* - "twJoin" / "cx": Class joiners without conflict resolution
*/
export type ClassUtilityType = "tv" | "cva" | "twMerge" | "twJoin" | "cx";

/**
* Tracked class utility import info
*/
export type TrackedClassUtility = {
type: ClassUtilityType;
originalName: string; // The actual import name (e.g., 'tv', 'twMerge')
};

/**
* Configuration for class utility imports to track
* Maps package name -> { exportName -> utilityType }
*
* Adding a new library or export is just one line here!
*/
export const CLASS_UTILITY_CONFIG: Record<string, Record<string, ClassUtilityType>> = {
"tailwind-variants": {
tv: "tv",
},
"class-variance-authority": {
cva: "cva",
cx: "cx", // Re-export of clsx for class concatenation
},
"tailwind-merge": {
twMerge: "twMerge", // Merge with conflict resolution
twJoin: "twJoin", // Join without conflict resolution (faster)
},
};

/**
* Plugin options
Expand Down Expand Up @@ -121,6 +159,11 @@ export type PluginState = PluginPass & {
functionComponentsNeedingColorScheme: Set<NodePath<BabelTypes.Function>>;
// Track function components that need windowDimensions hook injection
functionComponentsNeedingWindowDimensions: Set<NodePath<BabelTypes.Function>>;
// Track class utility imports (tv, cva, twMerge, etc.)
// Maps local name -> { type, originalName }
classUtilityImports: Map<string, TrackedClassUtility>;
variantFunctions: Map<string, VariantFunctionEntry>;
hasClassUtilityTransformations: boolean;
};

// Default identifier for the generated StyleSheet constant
Expand Down Expand Up @@ -181,5 +224,9 @@ export function createInitialState(
reactNativeImportPath: undefined,
functionComponentsNeedingColorScheme: new Set(),
functionComponentsNeedingWindowDimensions: new Set(),
// Class utility support (tv, cva, twMerge)
classUtilityImports: new Map(),
variantFunctions: new Map(),
hasClassUtilityTransformations: false,
};
}
10 changes: 9 additions & 1 deletion src/babel/plugin/visitors/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from "../../utils/styleInjection.js";
import { removeTwImports } from "../../utils/twProcessing.js";
import type { PluginState } from "../state.js";
import { removeVariantDefinitions, removeVariantImports } from "./variants.js";

/**
* Program enter visitor - initialize state for each file
Expand All @@ -41,9 +42,16 @@ export function programExit(
removeTwImports(path, t);
}

// If no classNames were found and no hooks/imports needed, skip processing
// Remove class utility imports and definitions if they were processed
if (state.hasClassUtilityTransformations) {
removeVariantImports(path, state, t);
removeVariantDefinitions(path, state, t);
}

// If no classNames/utilities were found and no hooks/imports needed, skip processing
if (
!state.hasClassNames &&
!state.hasClassUtilityTransformations &&
!state.needsWindowDimensionsImport &&
!state.needsColorSchemeImport &&
!state.needsI18nManagerImport
Expand Down
Loading