@mgcrea/react-native-tailwind 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,6 +22,7 @@ Compile-time Tailwind CSS for React Native with zero runtime overhead. Transform
22
22
  - 🔧 **No dependencies** — Direct-to-React-Native style generation without tailwindcss package
23
23
  - 🎨 **Custom colors** — Extend the default palette via `tailwind.config.*`
24
24
  - 📐 **Arbitrary values** — Use custom sizes and borders: `w-[123px]`, `rounded-[20px]`
25
+ - 🔀 **Dynamic className** — Conditional styles with hybrid compile-time optimization
25
26
  - 📜 **Special style props** — Support for `contentContainerClassName`, `columnWrapperClassName`, and more
26
27
 
27
28
  ## Installation
@@ -94,11 +95,11 @@ The Babel plugin transforms your code at compile time:
94
95
  ```tsx
95
96
  import { StyleSheet } from "react-native";
96
97
 
97
- <View style={styles._bg_blue_500_m_4_p_2_rounded_lg} />;
98
- <ScrollView contentContainerStyle={styles._gap_4_items_center} />;
99
- <FlatList columnWrapperStyle={styles._gap_4} ListHeaderComponentStyle={styles._bg_gray_100_p_4} />;
98
+ <View style={_twStyles._bg_blue_500_m_4_p_2_rounded_lg} />;
99
+ <ScrollView contentContainerStyle={_twStyles._gap_4_items_center} />;
100
+ <FlatList columnWrapperStyle={_twStyles._gap_4} ListHeaderComponentStyle={_twStyles._bg_gray_100_p_4} />;
100
101
 
101
- const styles = StyleSheet.create({
102
+ const _twStyles = StyleSheet.create({
102
103
  _bg_blue_500_m_4_p_2_rounded_lg: {
103
104
  margin: 16,
104
105
  padding: 8,
@@ -249,6 +250,106 @@ export function Card({ title, description, onPress }) {
249
250
  }
250
251
  ```
251
252
 
253
+ ### Dynamic className (Hybrid Optimization)
254
+
255
+ You can use dynamic expressions in `className` for conditional styling. The Babel plugin will parse all static strings at compile-time and preserve the conditional logic:
256
+
257
+ **Conditional Expression:**
258
+
259
+ ```tsx
260
+ import { useState } from "react";
261
+ import { View, Text, Pressable } from "react-native";
262
+
263
+ export function ToggleButton() {
264
+ const [isActive, setIsActive] = useState(false);
265
+
266
+ return (
267
+ <Pressable
268
+ onPress={() => setIsActive(!isActive)}
269
+ className={isActive ? "bg-green-500 p-4" : "bg-red-500 p-4"}
270
+ >
271
+ <Text className="text-white">{isActive ? "Active" : "Inactive"}</Text>
272
+ </Pressable>
273
+ );
274
+ }
275
+ ```
276
+
277
+ **Transforms to:**
278
+
279
+ ```tsx
280
+ <Pressable
281
+ onPress={() => setIsActive(!isActive)}
282
+ style={isActive ? _twStyles._bg_green_500_p_4 : _twStyles._bg_red_500_p_4}
283
+ >
284
+ <Text style={_twStyles._text_white}>{isActive ? "Active" : "Inactive"}</Text>
285
+ </Pressable>
286
+ ```
287
+
288
+ **Template Literal (Static + Dynamic):**
289
+
290
+ ```tsx
291
+ <Pressable className={`border-2 rounded-lg ${isActive ? "bg-blue-500" : "bg-gray-300"} p-4`}>
292
+ <Text className="text-white">Click Me</Text>
293
+ </Pressable>
294
+ ```
295
+
296
+ **Transforms to:**
297
+
298
+ ```tsx
299
+ <Pressable
300
+ style={[
301
+ _twStyles._border_2,
302
+ _twStyles._rounded_lg,
303
+ isActive ? _twStyles._bg_blue_500 : _twStyles._bg_gray_300,
304
+ _twStyles._p_4,
305
+ ]}
306
+ >
307
+ <Text style={_twStyles._text_white}>Click Me</Text>
308
+ </Pressable>
309
+ ```
310
+
311
+ **Logical Expression:**
312
+
313
+ ```tsx
314
+ <View className={`p-4 bg-gray-100 ${isActive && "border-4 border-purple-500"}`}>
315
+ <Text>Content</Text>
316
+ </View>
317
+ ```
318
+
319
+ **Transforms to:**
320
+
321
+ ```tsx
322
+ <View style={[_twStyles._p_4, _twStyles._bg_gray_100, isActive && _twStyles._border_4_border_purple_500]}>
323
+ <Text>Content</Text>
324
+ </View>
325
+ ```
326
+
327
+ **Multiple Conditionals:**
328
+
329
+ ```tsx
330
+ <View className={`${size === "lg" ? "p-8" : "p-4"} ${isActive ? "bg-blue-500" : "bg-gray-400"}`}>
331
+ <Text>Dynamic Size & Color</Text>
332
+ </View>
333
+ ```
334
+
335
+ **Key Benefits:**
336
+
337
+ - ✅ All string literals are parsed at compile-time
338
+ - ✅ Only conditional logic remains at runtime (no parser overhead)
339
+ - ✅ Full type-safety and validation for all class names
340
+ - ✅ Optimal performance with pre-compiled styles
341
+
342
+ **What Won't Work:**
343
+
344
+ ```tsx
345
+ // ❌ Runtime variables in class names
346
+ const spacing = 4;
347
+ <View className={`p-${spacing}`} /> // Can't parse "p-${spacing}" at compile time
348
+
349
+ // ✅ Use inline style for truly dynamic values:
350
+ <View className="border-2" style={{ padding: spacing * 4 }} />
351
+ ```
352
+
252
353
  ### Combining with Inline Styles
253
354
 
254
355
  You can use inline `style` prop alongside `className`:
@@ -262,7 +363,7 @@ You can use inline `style` prop alongside `className`:
262
363
  The Babel plugin will merge them:
263
364
 
264
365
  ```tsx
265
- <View style={[styles._className_styles, { paddingTop: safeAreaInsets.top }]}>
366
+ <View style={[_twStyles._className_styles, { paddingTop: safeAreaInsets.top }]}>
266
367
  <Text>Content</Text>
267
368
  </View>
268
369
  ```
@@ -456,7 +557,7 @@ Access the parser and constants programmatically:
456
557
  import { parseClassName, COLORS, SPACING_SCALE } from "@mgcrea/react-native-tailwind";
457
558
 
458
559
  // Parse className strings
459
- const styles = parseClassName("m-4 p-2 bg-blue-500");
560
+ const _twStyles = parseClassName("m-4 p-2 bg-blue-500");
460
561
  // Returns: { margin: 16, padding: 8, backgroundColor: '#3B82F6' }
461
562
 
462
563
  // Access default scales
@@ -950,6 +950,7 @@ function extractCustomColors(filename) {
950
950
  }
951
951
 
952
952
  // src/babel/index.ts
953
+ var STYLES_IDENTIFIER = "_twStyles";
953
954
  var SUPPORTED_CLASS_ATTRIBUTES = [
954
955
  "className",
955
956
  "contentContainerClassName",
@@ -972,6 +973,102 @@ function getTargetStyleProp(attributeName) {
972
973
  }
973
974
  return "style";
974
975
  }
976
+ function processDynamicExpression(expression, state, t) {
977
+ if (t.isTemplateLiteral(expression)) {
978
+ return processTemplateLiteral(expression, state, t);
979
+ }
980
+ if (t.isConditionalExpression(expression)) {
981
+ return processConditionalExpression(expression, state, t);
982
+ }
983
+ if (t.isLogicalExpression(expression)) {
984
+ return processLogicalExpression(expression, state, t);
985
+ }
986
+ return null;
987
+ }
988
+ function processTemplateLiteral(node, state, t) {
989
+ const parts = [];
990
+ const staticParts = [];
991
+ for (let i = 0; i < node.quasis.length; i++) {
992
+ const quasi = node.quasis[i];
993
+ const staticText = quasi.value.cooked?.trim();
994
+ if (staticText) {
995
+ const classes = staticText.split(/\s+/).filter(Boolean);
996
+ for (const cls of classes) {
997
+ const styleObject = parseClassName2(cls, state.customColors);
998
+ const styleKey = generateStyleKey2(cls);
999
+ state.styleRegistry.set(styleKey, styleObject);
1000
+ staticParts.push(cls);
1001
+ parts.push(t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey)));
1002
+ }
1003
+ }
1004
+ if (i < node.expressions.length) {
1005
+ const expr = node.expressions[i];
1006
+ const result = processDynamicExpression(expr, state, t);
1007
+ if (result) {
1008
+ parts.push(result.expression);
1009
+ } else {
1010
+ parts.push(expr);
1011
+ }
1012
+ }
1013
+ }
1014
+ if (parts.length === 0) {
1015
+ return null;
1016
+ }
1017
+ const expression = parts.length === 1 ? parts[0] : t.arrayExpression(parts);
1018
+ return {
1019
+ expression,
1020
+ staticParts: staticParts.length > 0 ? staticParts : void 0
1021
+ };
1022
+ }
1023
+ function processConditionalExpression(node, state, t) {
1024
+ const consequent = processStringOrExpression(node.consequent, state, t);
1025
+ const alternate = processStringOrExpression(node.alternate, state, t);
1026
+ if (!consequent && !alternate) {
1027
+ return null;
1028
+ }
1029
+ const expression = t.conditionalExpression(
1030
+ node.test,
1031
+ consequent ?? t.nullLiteral(),
1032
+ alternate ?? t.nullLiteral()
1033
+ );
1034
+ return { expression };
1035
+ }
1036
+ function processLogicalExpression(node, state, t) {
1037
+ if (node.operator !== "&&") {
1038
+ return null;
1039
+ }
1040
+ const right = processStringOrExpression(node.right, state, t);
1041
+ if (!right) {
1042
+ return null;
1043
+ }
1044
+ const expression = t.logicalExpression("&&", node.left, right);
1045
+ return { expression };
1046
+ }
1047
+ function processStringOrExpression(node, state, t) {
1048
+ if (t.isStringLiteral(node)) {
1049
+ const className = node.value.trim();
1050
+ if (!className) {
1051
+ return null;
1052
+ }
1053
+ const styleObject = parseClassName2(className, state.customColors);
1054
+ const styleKey = generateStyleKey2(className);
1055
+ state.styleRegistry.set(styleKey, styleObject);
1056
+ return t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey));
1057
+ }
1058
+ if (t.isConditionalExpression(node)) {
1059
+ const result = processConditionalExpression(node, state, t);
1060
+ return result?.expression ?? null;
1061
+ }
1062
+ if (t.isLogicalExpression(node)) {
1063
+ const result = processLogicalExpression(node, state, t);
1064
+ return result?.expression ?? null;
1065
+ }
1066
+ if (t.isTemplateLiteral(node)) {
1067
+ const result = processTemplateLiteral(node, state, t);
1068
+ return result?.expression ?? null;
1069
+ }
1070
+ return null;
1071
+ }
975
1072
  function reactNativeTailwindBabelPlugin({
976
1073
  types: t
977
1074
  }) {
@@ -1021,34 +1118,61 @@ function reactNativeTailwindBabelPlugin({
1021
1118
  return;
1022
1119
  }
1023
1120
  const value = node.value;
1024
- if (!t.isStringLiteral(value)) {
1025
- if (process.env.NODE_ENV !== "production") {
1026
- const filename = state.file.opts.filename ?? "unknown";
1027
- const targetStyleProp2 = getTargetStyleProp(attributeName);
1028
- console.warn(
1029
- `[react-native-tailwind] Dynamic ${attributeName} values are not supported at ${filename}. Use the ${targetStyleProp2} prop for dynamic values.`
1030
- );
1121
+ const targetStyleProp = getTargetStyleProp(attributeName);
1122
+ if (t.isStringLiteral(value)) {
1123
+ const className = value.value.trim();
1124
+ if (!className) {
1125
+ path2.remove();
1126
+ return;
1127
+ }
1128
+ state.hasClassNames = true;
1129
+ const styleObject = parseClassName2(className, state.customColors);
1130
+ const styleKey = generateStyleKey2(className);
1131
+ state.styleRegistry.set(styleKey, styleObject);
1132
+ const parent = path2.parent;
1133
+ const styleAttribute = parent.attributes.find(
1134
+ (attr) => t.isJSXAttribute(attr) && attr.name.name === targetStyleProp
1135
+ );
1136
+ if (styleAttribute) {
1137
+ mergeStyleAttribute(path2, styleAttribute, styleKey, t);
1138
+ } else {
1139
+ replaceWithStyleAttribute(path2, styleKey, targetStyleProp, t);
1031
1140
  }
1032
1141
  return;
1033
1142
  }
1034
- const className = value.value.trim();
1035
- if (!className) {
1036
- path2.remove();
1037
- return;
1143
+ if (t.isJSXExpressionContainer(value)) {
1144
+ const expression = value.expression;
1145
+ if (t.isJSXEmptyExpression(expression)) {
1146
+ return;
1147
+ }
1148
+ try {
1149
+ const result = processDynamicExpression(expression, state, t);
1150
+ if (result) {
1151
+ state.hasClassNames = true;
1152
+ const parent = path2.parent;
1153
+ const styleAttribute = parent.attributes.find(
1154
+ (attr) => t.isJSXAttribute(attr) && attr.name.name === targetStyleProp
1155
+ );
1156
+ if (styleAttribute) {
1157
+ mergeDynamicStyleAttribute(path2, styleAttribute, result, t);
1158
+ } else {
1159
+ replaceDynamicWithStyleAttribute(path2, result, targetStyleProp, t);
1160
+ }
1161
+ return;
1162
+ }
1163
+ } catch (error) {
1164
+ if (process.env.NODE_ENV !== "production") {
1165
+ console.warn(
1166
+ `[react-native-tailwind] Failed to process dynamic ${attributeName} at ${state.file.opts.filename ?? "unknown"}: ${error instanceof Error ? error.message : String(error)}`
1167
+ );
1168
+ }
1169
+ }
1038
1170
  }
1039
- state.hasClassNames = true;
1040
- const styleObject = parseClassName2(className, state.customColors);
1041
- const styleKey = generateStyleKey2(className);
1042
- state.styleRegistry.set(styleKey, styleObject);
1043
- const targetStyleProp = getTargetStyleProp(attributeName);
1044
- const parent = path2.parent;
1045
- const styleAttribute = parent.attributes.find(
1046
- (attr) => t.isJSXAttribute(attr) && attr.name.name === targetStyleProp
1047
- );
1048
- if (styleAttribute) {
1049
- mergeStyleAttribute(path2, styleAttribute, styleKey, t);
1050
- } else {
1051
- replaceWithStyleAttribute(path2, styleKey, targetStyleProp, t);
1171
+ if (process.env.NODE_ENV !== "production") {
1172
+ const filename = state.file.opts.filename ?? "unknown";
1173
+ console.warn(
1174
+ `[react-native-tailwind] Dynamic ${attributeName} values are not fully supported at ${filename}. Use the ${targetStyleProp} prop for dynamic values.`
1175
+ );
1052
1176
  }
1053
1177
  }
1054
1178
  }
@@ -1064,19 +1188,37 @@ function addStyleSheetImport(path2, t) {
1064
1188
  function replaceWithStyleAttribute(classNamePath, styleKey, targetStyleProp, t) {
1065
1189
  const styleAttribute = t.jsxAttribute(
1066
1190
  t.jsxIdentifier(targetStyleProp),
1067
- t.jsxExpressionContainer(t.memberExpression(t.identifier("styles"), t.identifier(styleKey)))
1191
+ t.jsxExpressionContainer(t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey)))
1068
1192
  );
1069
1193
  classNamePath.replaceWith(styleAttribute);
1070
1194
  }
1071
1195
  function mergeStyleAttribute(classNamePath, styleAttribute, styleKey, t) {
1072
1196
  const existingStyle = styleAttribute.value.expression;
1073
1197
  const styleArray = t.arrayExpression([
1074
- t.memberExpression(t.identifier("styles"), t.identifier(styleKey)),
1198
+ t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey)),
1075
1199
  existingStyle
1076
1200
  ]);
1077
1201
  styleAttribute.value = t.jsxExpressionContainer(styleArray);
1078
1202
  classNamePath.remove();
1079
1203
  }
1204
+ function replaceDynamicWithStyleAttribute(classNamePath, result, targetStyleProp, t) {
1205
+ const styleAttribute = t.jsxAttribute(
1206
+ t.jsxIdentifier(targetStyleProp),
1207
+ t.jsxExpressionContainer(result.expression)
1208
+ );
1209
+ classNamePath.replaceWith(styleAttribute);
1210
+ }
1211
+ function mergeDynamicStyleAttribute(classNamePath, styleAttribute, result, t) {
1212
+ const existingStyle = styleAttribute.value.expression;
1213
+ let styleArray;
1214
+ if (t.isArrayExpression(existingStyle)) {
1215
+ styleArray = t.arrayExpression([result.expression, ...existingStyle.elements]);
1216
+ } else {
1217
+ styleArray = t.arrayExpression([result.expression, existingStyle]);
1218
+ }
1219
+ styleAttribute.value = t.jsxExpressionContainer(styleArray);
1220
+ classNamePath.remove();
1221
+ }
1080
1222
  function injectStyles(path2, styleRegistry, t) {
1081
1223
  const styleProperties = [];
1082
1224
  for (const [key, styleObject] of styleRegistry) {
@@ -1095,7 +1237,7 @@ function injectStyles(path2, styleRegistry, t) {
1095
1237
  }
1096
1238
  const styleSheet = t.variableDeclaration("const", [
1097
1239
  t.variableDeclarator(
1098
- t.identifier("styles"),
1240
+ t.identifier(STYLES_IDENTIFIER),
1099
1241
  t.callExpression(t.memberExpression(t.identifier("StyleSheet"), t.identifier("create")), [
1100
1242
  t.objectExpression(styleProperties)
1101
1243
  ])
@@ -22,6 +22,9 @@ type PluginState = PluginPass & {
22
22
  customColors: Record<string, string>;
23
23
  };
24
24
 
25
+ // Use a unique identifier to avoid conflicts with user's own styles
26
+ const STYLES_IDENTIFIER = "_twStyles";
27
+
25
28
  /**
26
29
  * Supported className-like attributes
27
30
  */
@@ -52,6 +55,193 @@ function getTargetStyleProp(attributeName: string): string {
52
55
  return "style";
53
56
  }
54
57
 
58
+ /**
59
+ * Result of processing a dynamic expression
60
+ */
61
+ type DynamicExpressionResult = {
62
+ // The transformed expression to use in the style prop
63
+ expression: any;
64
+ // Static parts that can be parsed at compile time (if any)
65
+ staticParts?: string[];
66
+ };
67
+
68
+ /**
69
+ * Process a dynamic className expression
70
+ * Extracts static strings and transforms the expression to use pre-compiled styles
71
+ */
72
+ function processDynamicExpression(
73
+ expression: any,
74
+ state: PluginState,
75
+ t: typeof BabelTypes,
76
+ ): DynamicExpressionResult | null {
77
+ // Handle template literals: `m-4 ${condition ? "p-4" : "p-2"}`
78
+ if (t.isTemplateLiteral(expression)) {
79
+ return processTemplateLiteral(expression, state, t);
80
+ }
81
+
82
+ // Handle conditional expressions: condition ? "m-4" : "p-2"
83
+ if (t.isConditionalExpression(expression)) {
84
+ return processConditionalExpression(expression, state, t);
85
+ }
86
+
87
+ // Handle logical expressions: condition && "m-4"
88
+ if (t.isLogicalExpression(expression)) {
89
+ return processLogicalExpression(expression, state, t);
90
+ }
91
+
92
+ // Unsupported expression type
93
+ return null;
94
+ }
95
+
96
+ /**
97
+ * Process template literal: `static ${dynamic} more-static`
98
+ */
99
+ function processTemplateLiteral(
100
+ node: any,
101
+ state: PluginState,
102
+ t: typeof BabelTypes,
103
+ ): DynamicExpressionResult | null {
104
+ const parts: any[] = [];
105
+ const staticParts: string[] = [];
106
+
107
+ // Process quasis (static parts) and expressions (dynamic parts)
108
+ for (let i = 0; i < node.quasis.length; i++) {
109
+ const quasi = node.quasis[i];
110
+ const staticText = quasi.value.cooked?.trim();
111
+
112
+ // Add static part if not empty
113
+ if (staticText) {
114
+ // Parse static classes and add to registry
115
+ const classes = staticText.split(/\s+/).filter(Boolean);
116
+ for (const cls of classes) {
117
+ const styleObject = parseClassName(cls, state.customColors);
118
+ const styleKey = generateStyleKey(cls);
119
+ state.styleRegistry.set(styleKey, styleObject);
120
+ staticParts.push(cls);
121
+
122
+ // Add to parts array
123
+ parts.push(t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey)));
124
+ }
125
+ }
126
+
127
+ // Add dynamic expression if exists
128
+ if (i < node.expressions.length) {
129
+ const expr = node.expressions[i];
130
+
131
+ // Recursively process nested dynamic expressions
132
+ const result = processDynamicExpression(expr, state, t);
133
+ if (result) {
134
+ parts.push(result.expression);
135
+ } else {
136
+ // For unsupported expressions, keep them as-is
137
+ // This won't work at runtime but maintains the structure
138
+ parts.push(expr);
139
+ }
140
+ }
141
+ }
142
+
143
+ if (parts.length === 0) {
144
+ return null;
145
+ }
146
+
147
+ // If single part, return it directly; otherwise return array
148
+ const expression = parts.length === 1 ? parts[0] : t.arrayExpression(parts);
149
+
150
+ return {
151
+ expression,
152
+ staticParts: staticParts.length > 0 ? staticParts : undefined,
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Process conditional expression: condition ? "class-a" : "class-b"
158
+ */
159
+ function processConditionalExpression(
160
+ node: any,
161
+ state: PluginState,
162
+ t: typeof BabelTypes,
163
+ ): DynamicExpressionResult | null {
164
+ const consequent = processStringOrExpression(node.consequent, state, t);
165
+ const alternate = processStringOrExpression(node.alternate, state, t);
166
+
167
+ if (!consequent && !alternate) {
168
+ return null;
169
+ }
170
+
171
+ // Build conditional: condition ? consequentStyle : alternateStyle
172
+ const expression = t.conditionalExpression(
173
+ node.test,
174
+ consequent ?? t.nullLiteral(),
175
+ alternate ?? t.nullLiteral(),
176
+ );
177
+
178
+ return { expression };
179
+ }
180
+
181
+ /**
182
+ * Process logical expression: condition && "class-a"
183
+ */
184
+ function processLogicalExpression(
185
+ node: any,
186
+ state: PluginState,
187
+ t: typeof BabelTypes,
188
+ ): DynamicExpressionResult | null {
189
+ // Only handle AND (&&) expressions
190
+ if (node.operator !== "&&") {
191
+ return null;
192
+ }
193
+
194
+ const right = processStringOrExpression(node.right, state, t);
195
+
196
+ if (!right) {
197
+ return null;
198
+ }
199
+
200
+ // Build logical: condition && style
201
+ const expression = t.logicalExpression("&&", node.left, right);
202
+
203
+ return { expression };
204
+ }
205
+
206
+ /**
207
+ * Process a node that might be a string literal or another expression
208
+ */
209
+ function processStringOrExpression(node: any, state: PluginState, t: typeof BabelTypes): any {
210
+ // Handle string literals
211
+ if (t.isStringLiteral(node)) {
212
+ const className = node.value.trim();
213
+ if (!className) {
214
+ return null;
215
+ }
216
+
217
+ // Parse and register styles
218
+ const styleObject = parseClassName(className, state.customColors);
219
+ const styleKey = generateStyleKey(className);
220
+ state.styleRegistry.set(styleKey, styleObject);
221
+
222
+ return t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey));
223
+ }
224
+
225
+ // Handle nested expressions recursively
226
+ if (t.isConditionalExpression(node)) {
227
+ const result = processConditionalExpression(node, state, t);
228
+ return result?.expression ?? null;
229
+ }
230
+
231
+ if (t.isLogicalExpression(node)) {
232
+ const result = processLogicalExpression(node, state, t);
233
+ return result?.expression ?? null;
234
+ }
235
+
236
+ if (t.isTemplateLiteral(node)) {
237
+ const result = processTemplateLiteral(node, state, t);
238
+ return result?.expression ?? null;
239
+ }
240
+
241
+ // Unsupported - return null
242
+ return null;
243
+ }
244
+
55
245
  export default function reactNativeTailwindBabelPlugin({
56
246
  types: t,
57
247
  }: {
@@ -121,54 +311,94 @@ export default function reactNativeTailwindBabelPlugin({
121
311
 
122
312
  const value = node.value;
123
313
 
124
- // Only handle static string literals
125
- if (!t.isStringLiteral(value)) {
126
- // Warn about dynamic className in development
127
- if (process.env.NODE_ENV !== "production") {
128
- const filename = state.file.opts.filename ?? "unknown";
129
- const targetStyleProp = getTargetStyleProp(attributeName);
130
- console.warn(
131
- `[react-native-tailwind] Dynamic ${attributeName} values are not supported at ${filename}. ` +
132
- `Use the ${targetStyleProp} prop for dynamic values.`,
133
- );
314
+ // Determine target style prop based on attribute name
315
+ const targetStyleProp = getTargetStyleProp(attributeName);
316
+
317
+ // Handle static string literals (original behavior)
318
+ if (t.isStringLiteral(value)) {
319
+ const className = value.value.trim();
320
+
321
+ // Skip empty classNames
322
+ if (!className) {
323
+ path.remove();
324
+ return;
134
325
  }
135
- return;
136
- }
137
326
 
138
- const className = value.value.trim();
327
+ state.hasClassNames = true;
139
328
 
140
- // Skip empty classNames
141
- if (!className) {
142
- path.remove();
143
- return;
144
- }
329
+ // Parse className to React Native styles
330
+ const styleObject = parseClassName(className, state.customColors);
145
331
 
146
- state.hasClassNames = true;
332
+ // Generate unique style key
333
+ const styleKey = generateStyleKey(className);
147
334
 
148
- // Parse className to React Native styles
149
- const styleObject = parseClassName(className, state.customColors);
335
+ // Store in registry
336
+ state.styleRegistry.set(styleKey, styleObject);
150
337
 
151
- // Generate unique style key
152
- const styleKey = generateStyleKey(className);
338
+ // Check if there's already a style prop on this element
339
+ const parent = path.parent as any;
340
+ const styleAttribute = parent.attributes.find(
341
+ (attr: any) => t.isJSXAttribute(attr) && attr.name.name === targetStyleProp,
342
+ );
153
343
 
154
- // Store in registry
155
- state.styleRegistry.set(styleKey, styleObject);
344
+ if (styleAttribute) {
345
+ // Merge with existing style prop
346
+ mergeStyleAttribute(path, styleAttribute, styleKey, t);
347
+ } else {
348
+ // Replace className with style prop
349
+ replaceWithStyleAttribute(path, styleKey, targetStyleProp, t);
350
+ }
351
+ return;
352
+ }
156
353
 
157
- // Determine target style prop based on attribute name
158
- const targetStyleProp = getTargetStyleProp(attributeName);
354
+ // Handle dynamic expressions (JSXExpressionContainer)
355
+ if (t.isJSXExpressionContainer(value)) {
356
+ const expression = value.expression;
357
+
358
+ // Skip JSXEmptyExpression
359
+ if (t.isJSXEmptyExpression(expression)) {
360
+ return;
361
+ }
362
+
363
+ try {
364
+ // Process dynamic expression
365
+ const result = processDynamicExpression(expression, state, t);
366
+
367
+ if (result) {
368
+ state.hasClassNames = true;
369
+
370
+ // Check if there's already a style prop on this element
371
+ const parent = path.parent as any;
372
+ const styleAttribute = parent.attributes.find(
373
+ (attr: any) => t.isJSXAttribute(attr) && attr.name.name === targetStyleProp,
374
+ );
375
+
376
+ if (styleAttribute) {
377
+ // Merge with existing style prop
378
+ mergeDynamicStyleAttribute(path, styleAttribute, result, t);
379
+ } else {
380
+ // Replace className with style prop
381
+ replaceDynamicWithStyleAttribute(path, result, targetStyleProp, t);
382
+ }
383
+ return;
384
+ }
385
+ } catch (error) {
386
+ // Fall through to warning
387
+ if (process.env.NODE_ENV !== "production") {
388
+ console.warn(
389
+ `[react-native-tailwind] Failed to process dynamic ${attributeName} at ${state.file.opts.filename ?? "unknown"}: ${error instanceof Error ? error.message : String(error)}`,
390
+ );
391
+ }
392
+ }
393
+ }
159
394
 
160
- // Check if there's already a style prop on this element
161
- const parent = path.parent as any;
162
- const styleAttribute = parent.attributes.find(
163
- (attr: any) => t.isJSXAttribute(attr) && attr.name.name === targetStyleProp,
164
- );
165
-
166
- if (styleAttribute) {
167
- // Merge with existing style prop
168
- mergeStyleAttribute(path, styleAttribute, styleKey, t);
169
- } else {
170
- // Replace className with style prop
171
- replaceWithStyleAttribute(path, styleKey, targetStyleProp, t);
395
+ // Unsupported dynamic className - warn in development
396
+ if (process.env.NODE_ENV !== "production") {
397
+ const filename = state.file.opts.filename ?? "unknown";
398
+ console.warn(
399
+ `[react-native-tailwind] Dynamic ${attributeName} values are not fully supported at ${filename}. ` +
400
+ `Use the ${targetStyleProp} prop for dynamic values.`,
401
+ );
172
402
  }
173
403
  },
174
404
  },
@@ -199,7 +429,7 @@ function replaceWithStyleAttribute(
199
429
  ) {
200
430
  const styleAttribute = t.jsxAttribute(
201
431
  t.jsxIdentifier(targetStyleProp),
202
- t.jsxExpressionContainer(t.memberExpression(t.identifier("styles"), t.identifier(styleKey))),
432
+ t.jsxExpressionContainer(t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey))),
203
433
  );
204
434
 
205
435
  classNamePath.replaceWith(styleAttribute);
@@ -219,7 +449,7 @@ function mergeStyleAttribute(
219
449
  // Create array with className styles first, then existing styles
220
450
  // This allows existing styles to override className styles
221
451
  const styleArray = t.arrayExpression([
222
- t.memberExpression(t.identifier("styles"), t.identifier(styleKey)),
452
+ t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey)),
223
453
  existingStyle,
224
454
  ]);
225
455
 
@@ -229,6 +459,51 @@ function mergeStyleAttribute(
229
459
  classNamePath.remove();
230
460
  }
231
461
 
462
+ /**
463
+ * Replace className with dynamic style attribute
464
+ */
465
+ function replaceDynamicWithStyleAttribute(
466
+ classNamePath: NodePath,
467
+ result: DynamicExpressionResult,
468
+ targetStyleProp: string,
469
+ t: typeof BabelTypes,
470
+ ) {
471
+ const styleAttribute = t.jsxAttribute(
472
+ t.jsxIdentifier(targetStyleProp),
473
+ t.jsxExpressionContainer(result.expression),
474
+ );
475
+
476
+ classNamePath.replaceWith(styleAttribute);
477
+ }
478
+
479
+ /**
480
+ * Merge dynamic className styles with existing style prop
481
+ */
482
+ function mergeDynamicStyleAttribute(
483
+ classNamePath: NodePath,
484
+ styleAttribute: any,
485
+ result: DynamicExpressionResult,
486
+ t: typeof BabelTypes,
487
+ ) {
488
+ const existingStyle = styleAttribute.value.expression;
489
+
490
+ // Merge dynamic expression with existing styles
491
+ // If existing is already an array, append to it; otherwise create new array
492
+ let styleArray;
493
+ if (t.isArrayExpression(existingStyle)) {
494
+ // Prepend dynamic styles to existing array
495
+ styleArray = t.arrayExpression([result.expression, ...existingStyle.elements]);
496
+ } else {
497
+ // Create new array with dynamic styles first, then existing
498
+ styleArray = t.arrayExpression([result.expression, existingStyle]);
499
+ }
500
+
501
+ styleAttribute.value = t.jsxExpressionContainer(styleArray);
502
+
503
+ // Remove the className attribute
504
+ classNamePath.remove();
505
+ }
506
+
232
507
  /**
233
508
  * Inject StyleSheet.create with all collected styles
234
509
  */
@@ -259,10 +534,10 @@ function injectStyles(
259
534
  styleProperties.push(t.objectProperty(t.identifier(key), t.objectExpression(properties)));
260
535
  }
261
536
 
262
- // Create: const styles = StyleSheet.create({ ... })
537
+ // Create: const _tailwindStyles = StyleSheet.create({ ... })
263
538
  const styleSheet = t.variableDeclaration("const", [
264
539
  t.variableDeclarator(
265
- t.identifier("styles"),
540
+ t.identifier(STYLES_IDENTIFIER),
266
541
  t.callExpression(t.memberExpression(t.identifier("StyleSheet"), t.identifier("create")), [
267
542
  t.objectExpression(styleProperties),
268
543
  ]),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgcrea/react-native-tailwind",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Compile-time Tailwind CSS for React Native with zero runtime overhead",
5
5
  "author": "Olivier Louvignes <olivier@mgcrea.io> (https://github.com/mgcrea)",
6
6
  "homepage": "https://github.com/mgcrea/react-native-tailwind#readme",
@@ -22,6 +22,9 @@ type PluginState = PluginPass & {
22
22
  customColors: Record<string, string>;
23
23
  };
24
24
 
25
+ // Use a unique identifier to avoid conflicts with user's own styles
26
+ const STYLES_IDENTIFIER = "_twStyles";
27
+
25
28
  /**
26
29
  * Supported className-like attributes
27
30
  */
@@ -52,6 +55,193 @@ function getTargetStyleProp(attributeName: string): string {
52
55
  return "style";
53
56
  }
54
57
 
58
+ /**
59
+ * Result of processing a dynamic expression
60
+ */
61
+ type DynamicExpressionResult = {
62
+ // The transformed expression to use in the style prop
63
+ expression: any;
64
+ // Static parts that can be parsed at compile time (if any)
65
+ staticParts?: string[];
66
+ };
67
+
68
+ /**
69
+ * Process a dynamic className expression
70
+ * Extracts static strings and transforms the expression to use pre-compiled styles
71
+ */
72
+ function processDynamicExpression(
73
+ expression: any,
74
+ state: PluginState,
75
+ t: typeof BabelTypes,
76
+ ): DynamicExpressionResult | null {
77
+ // Handle template literals: `m-4 ${condition ? "p-4" : "p-2"}`
78
+ if (t.isTemplateLiteral(expression)) {
79
+ return processTemplateLiteral(expression, state, t);
80
+ }
81
+
82
+ // Handle conditional expressions: condition ? "m-4" : "p-2"
83
+ if (t.isConditionalExpression(expression)) {
84
+ return processConditionalExpression(expression, state, t);
85
+ }
86
+
87
+ // Handle logical expressions: condition && "m-4"
88
+ if (t.isLogicalExpression(expression)) {
89
+ return processLogicalExpression(expression, state, t);
90
+ }
91
+
92
+ // Unsupported expression type
93
+ return null;
94
+ }
95
+
96
+ /**
97
+ * Process template literal: `static ${dynamic} more-static`
98
+ */
99
+ function processTemplateLiteral(
100
+ node: any,
101
+ state: PluginState,
102
+ t: typeof BabelTypes,
103
+ ): DynamicExpressionResult | null {
104
+ const parts: any[] = [];
105
+ const staticParts: string[] = [];
106
+
107
+ // Process quasis (static parts) and expressions (dynamic parts)
108
+ for (let i = 0; i < node.quasis.length; i++) {
109
+ const quasi = node.quasis[i];
110
+ const staticText = quasi.value.cooked?.trim();
111
+
112
+ // Add static part if not empty
113
+ if (staticText) {
114
+ // Parse static classes and add to registry
115
+ const classes = staticText.split(/\s+/).filter(Boolean);
116
+ for (const cls of classes) {
117
+ const styleObject = parseClassName(cls, state.customColors);
118
+ const styleKey = generateStyleKey(cls);
119
+ state.styleRegistry.set(styleKey, styleObject);
120
+ staticParts.push(cls);
121
+
122
+ // Add to parts array
123
+ parts.push(t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey)));
124
+ }
125
+ }
126
+
127
+ // Add dynamic expression if exists
128
+ if (i < node.expressions.length) {
129
+ const expr = node.expressions[i];
130
+
131
+ // Recursively process nested dynamic expressions
132
+ const result = processDynamicExpression(expr, state, t);
133
+ if (result) {
134
+ parts.push(result.expression);
135
+ } else {
136
+ // For unsupported expressions, keep them as-is
137
+ // This won't work at runtime but maintains the structure
138
+ parts.push(expr);
139
+ }
140
+ }
141
+ }
142
+
143
+ if (parts.length === 0) {
144
+ return null;
145
+ }
146
+
147
+ // If single part, return it directly; otherwise return array
148
+ const expression = parts.length === 1 ? parts[0] : t.arrayExpression(parts);
149
+
150
+ return {
151
+ expression,
152
+ staticParts: staticParts.length > 0 ? staticParts : undefined,
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Process conditional expression: condition ? "class-a" : "class-b"
158
+ */
159
+ function processConditionalExpression(
160
+ node: any,
161
+ state: PluginState,
162
+ t: typeof BabelTypes,
163
+ ): DynamicExpressionResult | null {
164
+ const consequent = processStringOrExpression(node.consequent, state, t);
165
+ const alternate = processStringOrExpression(node.alternate, state, t);
166
+
167
+ if (!consequent && !alternate) {
168
+ return null;
169
+ }
170
+
171
+ // Build conditional: condition ? consequentStyle : alternateStyle
172
+ const expression = t.conditionalExpression(
173
+ node.test,
174
+ consequent ?? t.nullLiteral(),
175
+ alternate ?? t.nullLiteral(),
176
+ );
177
+
178
+ return { expression };
179
+ }
180
+
181
+ /**
182
+ * Process logical expression: condition && "class-a"
183
+ */
184
+ function processLogicalExpression(
185
+ node: any,
186
+ state: PluginState,
187
+ t: typeof BabelTypes,
188
+ ): DynamicExpressionResult | null {
189
+ // Only handle AND (&&) expressions
190
+ if (node.operator !== "&&") {
191
+ return null;
192
+ }
193
+
194
+ const right = processStringOrExpression(node.right, state, t);
195
+
196
+ if (!right) {
197
+ return null;
198
+ }
199
+
200
+ // Build logical: condition && style
201
+ const expression = t.logicalExpression("&&", node.left, right);
202
+
203
+ return { expression };
204
+ }
205
+
206
+ /**
207
+ * Process a node that might be a string literal or another expression
208
+ */
209
+ function processStringOrExpression(node: any, state: PluginState, t: typeof BabelTypes): any {
210
+ // Handle string literals
211
+ if (t.isStringLiteral(node)) {
212
+ const className = node.value.trim();
213
+ if (!className) {
214
+ return null;
215
+ }
216
+
217
+ // Parse and register styles
218
+ const styleObject = parseClassName(className, state.customColors);
219
+ const styleKey = generateStyleKey(className);
220
+ state.styleRegistry.set(styleKey, styleObject);
221
+
222
+ return t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey));
223
+ }
224
+
225
+ // Handle nested expressions recursively
226
+ if (t.isConditionalExpression(node)) {
227
+ const result = processConditionalExpression(node, state, t);
228
+ return result?.expression ?? null;
229
+ }
230
+
231
+ if (t.isLogicalExpression(node)) {
232
+ const result = processLogicalExpression(node, state, t);
233
+ return result?.expression ?? null;
234
+ }
235
+
236
+ if (t.isTemplateLiteral(node)) {
237
+ const result = processTemplateLiteral(node, state, t);
238
+ return result?.expression ?? null;
239
+ }
240
+
241
+ // Unsupported - return null
242
+ return null;
243
+ }
244
+
55
245
  export default function reactNativeTailwindBabelPlugin({
56
246
  types: t,
57
247
  }: {
@@ -121,54 +311,94 @@ export default function reactNativeTailwindBabelPlugin({
121
311
 
122
312
  const value = node.value;
123
313
 
124
- // Only handle static string literals
125
- if (!t.isStringLiteral(value)) {
126
- // Warn about dynamic className in development
127
- if (process.env.NODE_ENV !== "production") {
128
- const filename = state.file.opts.filename ?? "unknown";
129
- const targetStyleProp = getTargetStyleProp(attributeName);
130
- console.warn(
131
- `[react-native-tailwind] Dynamic ${attributeName} values are not supported at ${filename}. ` +
132
- `Use the ${targetStyleProp} prop for dynamic values.`,
133
- );
314
+ // Determine target style prop based on attribute name
315
+ const targetStyleProp = getTargetStyleProp(attributeName);
316
+
317
+ // Handle static string literals (original behavior)
318
+ if (t.isStringLiteral(value)) {
319
+ const className = value.value.trim();
320
+
321
+ // Skip empty classNames
322
+ if (!className) {
323
+ path.remove();
324
+ return;
134
325
  }
135
- return;
136
- }
137
326
 
138
- const className = value.value.trim();
327
+ state.hasClassNames = true;
139
328
 
140
- // Skip empty classNames
141
- if (!className) {
142
- path.remove();
143
- return;
144
- }
329
+ // Parse className to React Native styles
330
+ const styleObject = parseClassName(className, state.customColors);
145
331
 
146
- state.hasClassNames = true;
332
+ // Generate unique style key
333
+ const styleKey = generateStyleKey(className);
147
334
 
148
- // Parse className to React Native styles
149
- const styleObject = parseClassName(className, state.customColors);
335
+ // Store in registry
336
+ state.styleRegistry.set(styleKey, styleObject);
150
337
 
151
- // Generate unique style key
152
- const styleKey = generateStyleKey(className);
338
+ // Check if there's already a style prop on this element
339
+ const parent = path.parent as any;
340
+ const styleAttribute = parent.attributes.find(
341
+ (attr: any) => t.isJSXAttribute(attr) && attr.name.name === targetStyleProp,
342
+ );
153
343
 
154
- // Store in registry
155
- state.styleRegistry.set(styleKey, styleObject);
344
+ if (styleAttribute) {
345
+ // Merge with existing style prop
346
+ mergeStyleAttribute(path, styleAttribute, styleKey, t);
347
+ } else {
348
+ // Replace className with style prop
349
+ replaceWithStyleAttribute(path, styleKey, targetStyleProp, t);
350
+ }
351
+ return;
352
+ }
156
353
 
157
- // Determine target style prop based on attribute name
158
- const targetStyleProp = getTargetStyleProp(attributeName);
354
+ // Handle dynamic expressions (JSXExpressionContainer)
355
+ if (t.isJSXExpressionContainer(value)) {
356
+ const expression = value.expression;
357
+
358
+ // Skip JSXEmptyExpression
359
+ if (t.isJSXEmptyExpression(expression)) {
360
+ return;
361
+ }
362
+
363
+ try {
364
+ // Process dynamic expression
365
+ const result = processDynamicExpression(expression, state, t);
366
+
367
+ if (result) {
368
+ state.hasClassNames = true;
369
+
370
+ // Check if there's already a style prop on this element
371
+ const parent = path.parent as any;
372
+ const styleAttribute = parent.attributes.find(
373
+ (attr: any) => t.isJSXAttribute(attr) && attr.name.name === targetStyleProp,
374
+ );
375
+
376
+ if (styleAttribute) {
377
+ // Merge with existing style prop
378
+ mergeDynamicStyleAttribute(path, styleAttribute, result, t);
379
+ } else {
380
+ // Replace className with style prop
381
+ replaceDynamicWithStyleAttribute(path, result, targetStyleProp, t);
382
+ }
383
+ return;
384
+ }
385
+ } catch (error) {
386
+ // Fall through to warning
387
+ if (process.env.NODE_ENV !== "production") {
388
+ console.warn(
389
+ `[react-native-tailwind] Failed to process dynamic ${attributeName} at ${state.file.opts.filename ?? "unknown"}: ${error instanceof Error ? error.message : String(error)}`,
390
+ );
391
+ }
392
+ }
393
+ }
159
394
 
160
- // Check if there's already a style prop on this element
161
- const parent = path.parent as any;
162
- const styleAttribute = parent.attributes.find(
163
- (attr: any) => t.isJSXAttribute(attr) && attr.name.name === targetStyleProp,
164
- );
165
-
166
- if (styleAttribute) {
167
- // Merge with existing style prop
168
- mergeStyleAttribute(path, styleAttribute, styleKey, t);
169
- } else {
170
- // Replace className with style prop
171
- replaceWithStyleAttribute(path, styleKey, targetStyleProp, t);
395
+ // Unsupported dynamic className - warn in development
396
+ if (process.env.NODE_ENV !== "production") {
397
+ const filename = state.file.opts.filename ?? "unknown";
398
+ console.warn(
399
+ `[react-native-tailwind] Dynamic ${attributeName} values are not fully supported at ${filename}. ` +
400
+ `Use the ${targetStyleProp} prop for dynamic values.`,
401
+ );
172
402
  }
173
403
  },
174
404
  },
@@ -199,7 +429,7 @@ function replaceWithStyleAttribute(
199
429
  ) {
200
430
  const styleAttribute = t.jsxAttribute(
201
431
  t.jsxIdentifier(targetStyleProp),
202
- t.jsxExpressionContainer(t.memberExpression(t.identifier("styles"), t.identifier(styleKey))),
432
+ t.jsxExpressionContainer(t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey))),
203
433
  );
204
434
 
205
435
  classNamePath.replaceWith(styleAttribute);
@@ -219,7 +449,7 @@ function mergeStyleAttribute(
219
449
  // Create array with className styles first, then existing styles
220
450
  // This allows existing styles to override className styles
221
451
  const styleArray = t.arrayExpression([
222
- t.memberExpression(t.identifier("styles"), t.identifier(styleKey)),
452
+ t.memberExpression(t.identifier(STYLES_IDENTIFIER), t.identifier(styleKey)),
223
453
  existingStyle,
224
454
  ]);
225
455
 
@@ -229,6 +459,51 @@ function mergeStyleAttribute(
229
459
  classNamePath.remove();
230
460
  }
231
461
 
462
+ /**
463
+ * Replace className with dynamic style attribute
464
+ */
465
+ function replaceDynamicWithStyleAttribute(
466
+ classNamePath: NodePath,
467
+ result: DynamicExpressionResult,
468
+ targetStyleProp: string,
469
+ t: typeof BabelTypes,
470
+ ) {
471
+ const styleAttribute = t.jsxAttribute(
472
+ t.jsxIdentifier(targetStyleProp),
473
+ t.jsxExpressionContainer(result.expression),
474
+ );
475
+
476
+ classNamePath.replaceWith(styleAttribute);
477
+ }
478
+
479
+ /**
480
+ * Merge dynamic className styles with existing style prop
481
+ */
482
+ function mergeDynamicStyleAttribute(
483
+ classNamePath: NodePath,
484
+ styleAttribute: any,
485
+ result: DynamicExpressionResult,
486
+ t: typeof BabelTypes,
487
+ ) {
488
+ const existingStyle = styleAttribute.value.expression;
489
+
490
+ // Merge dynamic expression with existing styles
491
+ // If existing is already an array, append to it; otherwise create new array
492
+ let styleArray;
493
+ if (t.isArrayExpression(existingStyle)) {
494
+ // Prepend dynamic styles to existing array
495
+ styleArray = t.arrayExpression([result.expression, ...existingStyle.elements]);
496
+ } else {
497
+ // Create new array with dynamic styles first, then existing
498
+ styleArray = t.arrayExpression([result.expression, existingStyle]);
499
+ }
500
+
501
+ styleAttribute.value = t.jsxExpressionContainer(styleArray);
502
+
503
+ // Remove the className attribute
504
+ classNamePath.remove();
505
+ }
506
+
232
507
  /**
233
508
  * Inject StyleSheet.create with all collected styles
234
509
  */
@@ -259,10 +534,10 @@ function injectStyles(
259
534
  styleProperties.push(t.objectProperty(t.identifier(key), t.objectExpression(properties)));
260
535
  }
261
536
 
262
- // Create: const styles = StyleSheet.create({ ... })
537
+ // Create: const _tailwindStyles = StyleSheet.create({ ... })
263
538
  const styleSheet = t.variableDeclaration("const", [
264
539
  t.variableDeclarator(
265
- t.identifier("styles"),
540
+ t.identifier(STYLES_IDENTIFIER),
266
541
  t.callExpression(t.memberExpression(t.identifier("StyleSheet"), t.identifier("create")), [
267
542
  t.objectExpression(styleProperties),
268
543
  ]),