@mgcrea/react-native-tailwind 0.12.1 → 0.14.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.
Files changed (104) hide show
  1. package/README.md +45 -2031
  2. package/dist/babel/index.cjs +1726 -1094
  3. package/dist/babel/plugin/componentScope.d.ts +26 -0
  4. package/dist/babel/plugin/componentScope.ts +87 -0
  5. package/dist/babel/plugin/state.d.ts +123 -0
  6. package/dist/babel/plugin/state.ts +185 -0
  7. package/dist/babel/plugin/visitors/className.d.ts +11 -0
  8. package/{src/babel/plugin.test.ts → dist/babel/plugin/visitors/className.test.ts} +285 -572
  9. package/dist/babel/plugin/visitors/className.ts +652 -0
  10. package/dist/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  11. package/dist/babel/plugin/visitors/imports.d.ts +11 -0
  12. package/dist/babel/plugin/visitors/imports.test.ts +88 -0
  13. package/dist/babel/plugin/visitors/imports.ts +116 -0
  14. package/dist/babel/plugin/visitors/program.d.ts +15 -0
  15. package/dist/babel/plugin/visitors/program.test.ts +325 -0
  16. package/dist/babel/plugin/visitors/program.ts +116 -0
  17. package/dist/babel/plugin/visitors/tw.d.ts +16 -0
  18. package/dist/babel/plugin/visitors/tw.test.ts +771 -0
  19. package/dist/babel/plugin/visitors/tw.ts +148 -0
  20. package/dist/babel/plugin.d.ts +3 -96
  21. package/dist/babel/plugin.test.ts +470 -0
  22. package/dist/babel/plugin.ts +28 -963
  23. package/dist/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  24. package/dist/babel/utils/componentSupport.test.ts +20 -7
  25. package/dist/babel/utils/componentSupport.ts +2 -0
  26. package/dist/babel/utils/directionalModifierProcessing.d.ts +34 -0
  27. package/dist/babel/utils/directionalModifierProcessing.ts +99 -0
  28. package/dist/babel/utils/modifierProcessing.ts +21 -0
  29. package/dist/babel/utils/platformModifierProcessing.ts +11 -0
  30. package/dist/babel/utils/styleInjection.d.ts +31 -0
  31. package/dist/babel/utils/styleInjection.ts +253 -7
  32. package/dist/babel/utils/twProcessing.d.ts +2 -0
  33. package/dist/babel/utils/twProcessing.ts +103 -3
  34. package/dist/babel/utils/windowDimensionsProcessing.d.ts +56 -0
  35. package/dist/babel/utils/windowDimensionsProcessing.ts +121 -0
  36. package/dist/components/TouchableOpacity.d.ts +35 -0
  37. package/dist/components/TouchableOpacity.js +1 -0
  38. package/dist/components/index.d.ts +3 -0
  39. package/dist/components/index.js +1 -0
  40. package/dist/config/markers.d.ts +5 -0
  41. package/dist/config/markers.js +1 -0
  42. package/dist/index.d.ts +2 -5
  43. package/dist/index.js +1 -1
  44. package/dist/parser/borders.d.ts +3 -1
  45. package/dist/parser/borders.js +1 -1
  46. package/dist/parser/borders.test.js +1 -1
  47. package/dist/parser/colors.js +1 -1
  48. package/dist/parser/colors.test.js +1 -1
  49. package/dist/parser/index.d.ts +2 -2
  50. package/dist/parser/index.js +1 -1
  51. package/dist/parser/layout.js +1 -1
  52. package/dist/parser/layout.test.js +1 -1
  53. package/dist/parser/modifiers.d.ts +32 -2
  54. package/dist/parser/modifiers.js +1 -1
  55. package/dist/parser/modifiers.test.js +1 -1
  56. package/dist/parser/sizing.js +1 -1
  57. package/dist/parser/spacing.d.ts +1 -1
  58. package/dist/parser/spacing.js +1 -1
  59. package/dist/parser/spacing.test.js +1 -1
  60. package/dist/parser/typography.test.js +1 -1
  61. package/dist/runtime.cjs +1 -1
  62. package/dist/runtime.cjs.map +4 -4
  63. package/dist/runtime.js +1 -1
  64. package/dist/runtime.js.map +4 -4
  65. package/package.json +6 -6
  66. package/src/babel/plugin/componentScope.ts +87 -0
  67. package/src/babel/plugin/state.ts +185 -0
  68. package/src/babel/plugin/visitors/className.test.ts +1625 -0
  69. package/src/babel/plugin/visitors/className.ts +652 -0
  70. package/src/babel/plugin/visitors/className.windowDimensions.test.ts +406 -0
  71. package/src/babel/plugin/visitors/imports.test.ts +88 -0
  72. package/src/babel/plugin/visitors/imports.ts +116 -0
  73. package/src/babel/plugin/visitors/program.test.ts +325 -0
  74. package/src/babel/plugin/visitors/program.ts +116 -0
  75. package/src/babel/plugin/visitors/tw.test.ts +771 -0
  76. package/src/babel/plugin/visitors/tw.ts +148 -0
  77. package/src/babel/plugin.ts +28 -963
  78. package/src/babel/utils/colorSchemeModifierProcessing.ts +11 -0
  79. package/src/babel/utils/componentSupport.test.ts +20 -7
  80. package/src/babel/utils/componentSupport.ts +2 -0
  81. package/src/babel/utils/directionalModifierProcessing.ts +99 -0
  82. package/src/babel/utils/modifierProcessing.ts +21 -0
  83. package/src/babel/utils/platformModifierProcessing.ts +11 -0
  84. package/src/babel/utils/styleInjection.ts +253 -7
  85. package/src/babel/utils/twProcessing.ts +103 -3
  86. package/src/babel/utils/windowDimensionsProcessing.ts +121 -0
  87. package/src/components/TouchableOpacity.tsx +71 -0
  88. package/src/components/index.ts +3 -0
  89. package/src/config/markers.ts +5 -0
  90. package/src/index.ts +4 -5
  91. package/src/parser/borders.test.ts +162 -0
  92. package/src/parser/borders.ts +67 -9
  93. package/src/parser/colors.test.ts +249 -0
  94. package/src/parser/colors.ts +38 -0
  95. package/src/parser/index.ts +4 -2
  96. package/src/parser/layout.test.ts +74 -0
  97. package/src/parser/layout.ts +94 -0
  98. package/src/parser/modifiers.test.ts +206 -0
  99. package/src/parser/modifiers.ts +62 -3
  100. package/src/parser/sizing.ts +11 -0
  101. package/src/parser/spacing.test.ts +66 -0
  102. package/src/parser/spacing.ts +15 -5
  103. package/src/parser/typography.test.ts +8 -0
  104. package/src/parser/typography.ts +4 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgcrea/react-native-tailwind",
3
- "version": "0.12.1",
3
+ "version": "0.14.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",
@@ -68,23 +68,23 @@
68
68
  "@testing-library/react-native": "^13.3.3",
69
69
  "@types/babel__core": "^7.20.5",
70
70
  "@types/babel__traverse": "^7.28.0",
71
- "@types/react": "^19.2.6",
72
- "@vitest/coverage-v8": "^4.0.13",
71
+ "@types/react": "^19.2.7",
72
+ "@vitest/coverage-v8": "^4.0.14",
73
73
  "babel-plugin-module-resolver": "^5.0.2",
74
74
  "esbuild": "^0.27.0",
75
75
  "eslint": "^9.39.1",
76
76
  "jest": "^30.2.0",
77
- "prettier": "^3.6.2",
77
+ "prettier": "^3.7.1",
78
78
  "prettier-plugin-organize-imports": "^4.3.0",
79
79
  "react": "^19.2.0",
80
80
  "react-native": "0.82.1",
81
81
  "typescript": "^5.9.3",
82
- "vitest": "^4.0.13"
82
+ "vitest": "^4.0.14"
83
83
  },
84
84
  "engines": {
85
85
  "node": ">=18"
86
86
  },
87
- "packageManager": "pnpm@10.23.0",
87
+ "packageManager": "pnpm@10.24.0",
88
88
  "publishConfig": {
89
89
  "access": "public"
90
90
  },
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Component scope detection helpers for hook injection
3
+ */
4
+
5
+ import type { NodePath } from "@babel/core";
6
+ import type * as BabelTypes from "@babel/types";
7
+
8
+ /**
9
+ * Check if a function path represents a valid component scope for hook injection
10
+ * Valid scopes:
11
+ * - Top-level FunctionDeclaration
12
+ * - FunctionExpression/ArrowFunctionExpression in top-level VariableDeclarator (with PascalCase name)
13
+ * - NOT class methods, NOT nested functions, NOT inline callbacks
14
+ *
15
+ * @param functionPath - Path to the function to check
16
+ * @param t - Babel types
17
+ * @returns true if function is a valid component scope
18
+ */
19
+ export function isComponentScope(functionPath: NodePath<BabelTypes.Function>, t: typeof BabelTypes): boolean {
20
+ const node = functionPath.node;
21
+ const parent = functionPath.parent;
22
+ const parentPath = functionPath.parentPath;
23
+
24
+ // Reject class methods (class components not supported for hooks)
25
+ if (t.isClassMethod(parent)) {
26
+ return false;
27
+ }
28
+
29
+ // Reject if inside a class body
30
+ if (functionPath.findParent((p) => t.isClassBody(p.node))) {
31
+ return false;
32
+ }
33
+
34
+ // Accept top-level FunctionDeclaration
35
+ if (t.isFunctionDeclaration(node)) {
36
+ // Check if it's at program level or in export
37
+ if (t.isProgram(parent) || t.isExportNamedDeclaration(parent) || t.isExportDefaultDeclaration(parent)) {
38
+ return true;
39
+ }
40
+ }
41
+
42
+ // Accept FunctionExpression/ArrowFunctionExpression in VariableDeclarator
43
+ if (t.isFunctionExpression(node) || t.isArrowFunctionExpression(node)) {
44
+ if (t.isVariableDeclarator(parent)) {
45
+ // Check if it's at program level (via VariableDeclaration)
46
+ const varDeclarationPath = parentPath?.parentPath;
47
+ if (
48
+ varDeclarationPath &&
49
+ t.isVariableDeclaration(varDeclarationPath.node) &&
50
+ (t.isProgram(varDeclarationPath.parent) || t.isExportNamedDeclaration(varDeclarationPath.parent))
51
+ ) {
52
+ // Check for PascalCase naming (component convention)
53
+ if (t.isIdentifier(parent.id)) {
54
+ const name = parent.id.name;
55
+ return /^[A-Z]/.test(name); // Starts with uppercase
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ return false;
62
+ }
63
+
64
+ /**
65
+ * Find the nearest valid component scope for hook injection
66
+ * Climbs the AST from the current path to find a component-level function
67
+ *
68
+ * @param path - Starting path (e.g., JSXAttribute)
69
+ * @param t - Babel types
70
+ * @returns NodePath to component function, or null if not found
71
+ */
72
+ export function findComponentScope(
73
+ path: NodePath,
74
+ t: typeof BabelTypes,
75
+ ): NodePath<BabelTypes.Function> | null {
76
+ let current = path.getFunctionParent();
77
+
78
+ while (current) {
79
+ if (t.isFunction(current.node) && isComponentScope(current, t)) {
80
+ return current;
81
+ }
82
+ // Climb to next parent function
83
+ current = current.getFunctionParent();
84
+ }
85
+
86
+ return null;
87
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Plugin state and options types
3
+ */
4
+
5
+ import type { NodePath, PluginPass } from "@babel/core";
6
+ import type * as BabelTypes from "@babel/types";
7
+ import type { SchemeModifierConfig } from "../../types/config.js";
8
+ import type { StyleObject } from "../../types/core.js";
9
+ import type { CustomTheme } from "../config-loader.js";
10
+ import { extractCustomTheme } from "../config-loader.js";
11
+ import { DEFAULT_CLASS_ATTRIBUTES, buildAttributeMatchers } from "../utils/attributeMatchers.js";
12
+
13
+ /**
14
+ * Plugin options
15
+ */
16
+ export type PluginOptions = {
17
+ /**
18
+ * List of JSX attribute names to transform (in addition to or instead of 'className')
19
+ * Supports exact matches and glob patterns:
20
+ * - Exact: 'className', 'containerClassName'
21
+ * - Glob: '*ClassName' (matches any attribute ending in 'ClassName')
22
+ *
23
+ * @default ['className', 'contentContainerClassName', 'columnWrapperClassName', 'ListHeaderComponentClassName', 'ListFooterComponentClassName']
24
+ */
25
+ attributes?: string[];
26
+
27
+ /**
28
+ * Custom identifier name for the generated StyleSheet constant
29
+ *
30
+ * @default '_twStyles'
31
+ */
32
+ stylesIdentifier?: string;
33
+
34
+ /**
35
+ * Configuration for the scheme: modifier that expands to both dark: and light: modifiers
36
+ *
37
+ * @example
38
+ * {
39
+ * darkSuffix: '-dark', // scheme:bg-primary -> dark:bg-primary-dark
40
+ * lightSuffix: '-light' // scheme:bg-primary -> light:bg-primary-light
41
+ * }
42
+ *
43
+ * @default { darkSuffix: '-dark', lightSuffix: '-light' }
44
+ */
45
+ schemeModifier?: {
46
+ darkSuffix?: string;
47
+ lightSuffix?: string;
48
+ };
49
+
50
+ /**
51
+ * Configuration for color scheme hook import (dark:/light: modifiers)
52
+ *
53
+ * Allows using custom color scheme hooks from theme providers instead of
54
+ * React Native's built-in useColorScheme.
55
+ *
56
+ * @example
57
+ * // Use custom hook from theme provider
58
+ * {
59
+ * importFrom: '@/hooks/useColorScheme',
60
+ * importName: 'useColorScheme'
61
+ * }
62
+ *
63
+ * @example
64
+ * // Use React Navigation theme
65
+ * {
66
+ * importFrom: '@react-navigation/native',
67
+ * importName: 'useTheme' // You'd wrap this to return ColorSchemeName
68
+ * }
69
+ *
70
+ * @default { importFrom: 'react-native', importName: 'useColorScheme' }
71
+ */
72
+ colorScheme?: {
73
+ /**
74
+ * Module to import the color scheme hook from
75
+ * @default 'react-native'
76
+ */
77
+ importFrom?: string;
78
+
79
+ /**
80
+ * Name of the hook to import
81
+ * @default 'useColorScheme'
82
+ */
83
+ importName?: string;
84
+ };
85
+ };
86
+
87
+ /**
88
+ * Plugin state - passed through all visitors
89
+ */
90
+ export type PluginState = PluginPass & {
91
+ styleRegistry: Map<string, StyleObject>;
92
+ hasClassNames: boolean;
93
+ hasStyleSheetImport: boolean;
94
+ hasPlatformImport: boolean;
95
+ needsPlatformImport: boolean;
96
+ hasColorSchemeImport: boolean;
97
+ needsColorSchemeImport: boolean;
98
+ colorSchemeVariableName: string;
99
+ colorSchemeImportSource: string; // Where to import the hook from (e.g., 'react-native')
100
+ colorSchemeHookName: string; // Name of the hook to import (e.g., 'useColorScheme')
101
+ colorSchemeLocalIdentifier?: string; // Local identifier if hook is already imported with an alias
102
+ hasWindowDimensionsImport: boolean;
103
+ needsWindowDimensionsImport: boolean;
104
+ windowDimensionsVariableName: string;
105
+ windowDimensionsLocalIdentifier?: string; // Local identifier if hook is already imported with an alias
106
+ hasI18nManagerImport: boolean;
107
+ needsI18nManagerImport: boolean;
108
+ i18nManagerVariableName: string; // Variable name for the RTL state (e.g., '_twIsRTL')
109
+ i18nManagerLocalIdentifier?: string; // Local identifier if I18nManager is already imported with an alias
110
+ customTheme: CustomTheme;
111
+ schemeModifierConfig: SchemeModifierConfig;
112
+ supportedAttributes: Set<string>;
113
+ attributePatterns: RegExp[];
114
+ stylesIdentifier: string;
115
+ // Track tw/twStyle imports from main package
116
+ twImportNames: Set<string>; // e.g., ['tw', 'twStyle'] or ['tw as customTw']
117
+ hasTwImport: boolean;
118
+ // Track react-native import path for conditional StyleSheet/Platform injection
119
+ reactNativeImportPath?: NodePath<BabelTypes.ImportDeclaration>;
120
+ // Track function components that need colorScheme hook injection
121
+ functionComponentsNeedingColorScheme: Set<NodePath<BabelTypes.Function>>;
122
+ // Track function components that need windowDimensions hook injection
123
+ functionComponentsNeedingWindowDimensions: Set<NodePath<BabelTypes.Function>>;
124
+ };
125
+
126
+ // Default identifier for the generated StyleSheet constant
127
+ export const DEFAULT_STYLES_IDENTIFIER = "_twStyles";
128
+
129
+ /**
130
+ * Create initial plugin state for a file
131
+ *
132
+ * @param options - Plugin options from babel config
133
+ * @param filename - Current file being processed
134
+ * @param colorSchemeImportSource - Where to import the color scheme hook from
135
+ * @param colorSchemeHookName - Name of the color scheme hook to import
136
+ * @param schemeModifierConfig - Configuration for scheme: modifier expansion
137
+ * @returns Initial plugin state
138
+ */
139
+ export function createInitialState(
140
+ options: PluginOptions | undefined,
141
+ filename: string,
142
+ colorSchemeImportSource: string,
143
+ colorSchemeHookName: string,
144
+ schemeModifierConfig: SchemeModifierConfig,
145
+ ): Partial<PluginState> {
146
+ // Build attribute matchers from options
147
+ const attributes = options?.attributes ?? [...DEFAULT_CLASS_ATTRIBUTES];
148
+ const { exactMatches, patterns } = buildAttributeMatchers(attributes);
149
+ const stylesIdentifier = options?.stylesIdentifier ?? DEFAULT_STYLES_IDENTIFIER;
150
+
151
+ // Load custom theme from tailwind.config.*
152
+ const customTheme = extractCustomTheme(filename);
153
+
154
+ return {
155
+ styleRegistry: new Map(),
156
+ hasClassNames: false,
157
+ hasStyleSheetImport: false,
158
+ hasPlatformImport: false,
159
+ needsPlatformImport: false,
160
+ hasColorSchemeImport: false,
161
+ needsColorSchemeImport: false,
162
+ colorSchemeVariableName: "_twColorScheme",
163
+ colorSchemeImportSource,
164
+ colorSchemeHookName,
165
+ colorSchemeLocalIdentifier: undefined,
166
+ hasWindowDimensionsImport: false,
167
+ needsWindowDimensionsImport: false,
168
+ windowDimensionsVariableName: "_twDimensions",
169
+ windowDimensionsLocalIdentifier: undefined,
170
+ hasI18nManagerImport: false,
171
+ needsI18nManagerImport: false,
172
+ i18nManagerVariableName: "_twIsRTL",
173
+ i18nManagerLocalIdentifier: undefined,
174
+ customTheme,
175
+ schemeModifierConfig,
176
+ supportedAttributes: exactMatches,
177
+ attributePatterns: patterns,
178
+ stylesIdentifier,
179
+ twImportNames: new Set(),
180
+ hasTwImport: false,
181
+ reactNativeImportPath: undefined,
182
+ functionComponentsNeedingColorScheme: new Set(),
183
+ functionComponentsNeedingWindowDimensions: new Set(),
184
+ };
185
+ }