@mgcrea/react-native-tailwind 0.11.0 → 0.11.1
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 +129 -0
- package/dist/babel/index.cjs +41 -27
- package/dist/babel/plugin.d.ts +37 -0
- package/dist/babel/plugin.test.ts +275 -1
- package/dist/babel/plugin.ts +73 -15
- package/dist/babel/utils/styleInjection.d.ts +5 -3
- package/dist/babel/utils/styleInjection.ts +38 -23
- package/package.json +1 -1
- package/src/babel/plugin.test.ts +275 -1
- package/src/babel/plugin.ts +73 -15
- package/src/babel/utils/styleInjection.ts +38 -23
package/src/babel/plugin.ts
CHANGED
|
@@ -89,6 +89,42 @@ export type PluginOptions = {
|
|
|
89
89
|
darkSuffix?: string;
|
|
90
90
|
lightSuffix?: string;
|
|
91
91
|
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Configuration for color scheme hook import (dark:/light: modifiers)
|
|
95
|
+
*
|
|
96
|
+
* Allows using custom color scheme hooks from theme providers instead of
|
|
97
|
+
* React Native's built-in useColorScheme.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* // Use custom hook from theme provider
|
|
101
|
+
* {
|
|
102
|
+
* importFrom: '@/hooks/useColorScheme',
|
|
103
|
+
* importName: 'useColorScheme'
|
|
104
|
+
* }
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* // Use React Navigation theme
|
|
108
|
+
* {
|
|
109
|
+
* importFrom: '@react-navigation/native',
|
|
110
|
+
* importName: 'useTheme' // You'd wrap this to return ColorSchemeName
|
|
111
|
+
* }
|
|
112
|
+
*
|
|
113
|
+
* @default { importFrom: 'react-native', importName: 'useColorScheme' }
|
|
114
|
+
*/
|
|
115
|
+
colorScheme?: {
|
|
116
|
+
/**
|
|
117
|
+
* Module to import the color scheme hook from
|
|
118
|
+
* @default 'react-native'
|
|
119
|
+
*/
|
|
120
|
+
importFrom?: string;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Name of the hook to import
|
|
124
|
+
* @default 'useColorScheme'
|
|
125
|
+
*/
|
|
126
|
+
importName?: string;
|
|
127
|
+
};
|
|
92
128
|
};
|
|
93
129
|
|
|
94
130
|
type PluginState = PluginPass & {
|
|
@@ -100,6 +136,9 @@ type PluginState = PluginPass & {
|
|
|
100
136
|
hasColorSchemeImport: boolean;
|
|
101
137
|
needsColorSchemeImport: boolean;
|
|
102
138
|
colorSchemeVariableName: string;
|
|
139
|
+
colorSchemeImportSource: string; // Where to import the hook from (e.g., 'react-native')
|
|
140
|
+
colorSchemeHookName: string; // Name of the hook to import (e.g., 'useColorScheme')
|
|
141
|
+
colorSchemeLocalIdentifier?: string; // Local identifier if hook is already imported with an alias
|
|
103
142
|
customTheme: CustomTheme;
|
|
104
143
|
schemeModifierConfig: SchemeModifierConfig;
|
|
105
144
|
supportedAttributes: Set<string>;
|
|
@@ -210,6 +249,10 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
210
249
|
lightSuffix: options?.schemeModifier?.lightSuffix ?? "-light",
|
|
211
250
|
};
|
|
212
251
|
|
|
252
|
+
// Color scheme hook configuration from plugin options
|
|
253
|
+
const colorSchemeImportSource = options?.colorScheme?.importFrom ?? "react-native";
|
|
254
|
+
const colorSchemeHookName = options?.colorScheme?.importName ?? "useColorScheme";
|
|
255
|
+
|
|
213
256
|
return {
|
|
214
257
|
name: "react-native-tailwind",
|
|
215
258
|
|
|
@@ -225,6 +268,8 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
225
268
|
state.hasColorSchemeImport = false;
|
|
226
269
|
state.needsColorSchemeImport = false;
|
|
227
270
|
state.colorSchemeVariableName = "_twColorScheme";
|
|
271
|
+
state.colorSchemeImportSource = colorSchemeImportSource;
|
|
272
|
+
state.colorSchemeHookName = colorSchemeHookName;
|
|
228
273
|
state.supportedAttributes = exactMatches;
|
|
229
274
|
state.attributePatterns = patterns;
|
|
230
275
|
state.stylesIdentifier = stylesIdentifier;
|
|
@@ -260,15 +305,21 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
260
305
|
addPlatformImport(path, t);
|
|
261
306
|
}
|
|
262
307
|
|
|
263
|
-
// Add
|
|
308
|
+
// Add color scheme hook import if color scheme modifiers were used and not already present
|
|
264
309
|
if (state.needsColorSchemeImport && !state.hasColorSchemeImport) {
|
|
265
|
-
addColorSchemeImport(path, t);
|
|
310
|
+
addColorSchemeImport(path, state.colorSchemeImportSource, state.colorSchemeHookName, t);
|
|
266
311
|
}
|
|
267
312
|
|
|
268
|
-
// Inject
|
|
313
|
+
// Inject color scheme hook in function components that need it
|
|
269
314
|
if (state.needsColorSchemeImport) {
|
|
270
315
|
for (const functionPath of state.functionComponentsNeedingColorScheme) {
|
|
271
|
-
injectColorSchemeHook(
|
|
316
|
+
injectColorSchemeHook(
|
|
317
|
+
functionPath,
|
|
318
|
+
state.colorSchemeVariableName,
|
|
319
|
+
state.colorSchemeHookName,
|
|
320
|
+
state.colorSchemeLocalIdentifier,
|
|
321
|
+
t,
|
|
322
|
+
);
|
|
272
323
|
}
|
|
273
324
|
}
|
|
274
325
|
|
|
@@ -300,13 +351,6 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
300
351
|
return false;
|
|
301
352
|
});
|
|
302
353
|
|
|
303
|
-
const hasUseColorScheme = specifiers.some((spec) => {
|
|
304
|
-
if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
|
|
305
|
-
return spec.imported.name === "useColorScheme";
|
|
306
|
-
}
|
|
307
|
-
return false;
|
|
308
|
-
});
|
|
309
|
-
|
|
310
354
|
// Only track if imports exist - don't mutate yet
|
|
311
355
|
// Actual import injection happens in Program.exit only if needed
|
|
312
356
|
if (hasStyleSheet) {
|
|
@@ -317,14 +361,28 @@ export default function reactNativeTailwindBabelPlugin(
|
|
|
317
361
|
state.hasPlatformImport = true;
|
|
318
362
|
}
|
|
319
363
|
|
|
320
|
-
if (hasUseColorScheme) {
|
|
321
|
-
state.hasColorSchemeImport = true;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
364
|
// Store reference to the react-native import for later modification if needed
|
|
325
365
|
state.reactNativeImportPath = path;
|
|
326
366
|
}
|
|
327
367
|
|
|
368
|
+
// Track color scheme hook import from the configured source
|
|
369
|
+
// (default: react-native, but can be custom like @/hooks/useColorScheme)
|
|
370
|
+
if (node.source.value === state.colorSchemeImportSource) {
|
|
371
|
+
const specifiers = node.specifiers;
|
|
372
|
+
|
|
373
|
+
for (const spec of specifiers) {
|
|
374
|
+
if (t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)) {
|
|
375
|
+
if (spec.imported.name === state.colorSchemeHookName) {
|
|
376
|
+
state.hasColorSchemeImport = true;
|
|
377
|
+
// Track the local identifier (handles aliased imports)
|
|
378
|
+
// e.g., import { useTheme as navTheme } → local name is 'navTheme'
|
|
379
|
+
state.colorSchemeLocalIdentifier = spec.local.name;
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
328
386
|
// Track tw/twStyle imports from main package (for compile-time transformation)
|
|
329
387
|
if (node.source.value === "@mgcrea/react-native-tailwind") {
|
|
330
388
|
const specifiers = node.specifiers;
|
|
@@ -67,54 +67,64 @@ export function addPlatformImport(path: NodePath<BabelTypes.Program>, t: typeof
|
|
|
67
67
|
/**
|
|
68
68
|
* Add useColorScheme import to the file or merge with existing react-native import
|
|
69
69
|
*/
|
|
70
|
-
export function addColorSchemeImport(
|
|
71
|
-
|
|
70
|
+
export function addColorSchemeImport(
|
|
71
|
+
path: NodePath<BabelTypes.Program>,
|
|
72
|
+
importSource: string,
|
|
73
|
+
hookName: string,
|
|
74
|
+
t: typeof BabelTypes,
|
|
75
|
+
): void {
|
|
76
|
+
// Check if there's already an import from the specified source
|
|
72
77
|
const body = path.node.body;
|
|
73
|
-
let
|
|
78
|
+
let existingValueImport: BabelTypes.ImportDeclaration | null = null;
|
|
74
79
|
|
|
75
80
|
for (const statement of body) {
|
|
76
|
-
if (t.isImportDeclaration(statement) && statement.source.value ===
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
if (t.isImportDeclaration(statement) && statement.source.value === importSource) {
|
|
82
|
+
// Only consider value imports (not type-only imports which get erased)
|
|
83
|
+
if (statement.importKind !== "type") {
|
|
84
|
+
existingValueImport = statement;
|
|
85
|
+
break; // Found a value import, we can stop
|
|
86
|
+
}
|
|
79
87
|
}
|
|
80
88
|
}
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
// If we found a value import (not type-only), merge with it
|
|
91
|
+
if (existingValueImport) {
|
|
92
|
+
// Check if the hook is already imported
|
|
93
|
+
const hasHook = existingValueImport.specifiers.some(
|
|
85
94
|
(spec) =>
|
|
86
|
-
t.isImportSpecifier(spec) &&
|
|
87
|
-
spec.imported.type === "Identifier" &&
|
|
88
|
-
spec.imported.name === "useColorScheme",
|
|
95
|
+
t.isImportSpecifier(spec) && spec.imported.type === "Identifier" && spec.imported.name === hookName,
|
|
89
96
|
);
|
|
90
97
|
|
|
91
|
-
if (!
|
|
92
|
-
// Add
|
|
93
|
-
|
|
94
|
-
t.importSpecifier(t.identifier("useColorScheme"), t.identifier("useColorScheme")),
|
|
95
|
-
);
|
|
98
|
+
if (!hasHook) {
|
|
99
|
+
// Add hook to existing value import
|
|
100
|
+
existingValueImport.specifiers.push(t.importSpecifier(t.identifier(hookName), t.identifier(hookName)));
|
|
96
101
|
}
|
|
97
102
|
} else {
|
|
98
|
-
//
|
|
103
|
+
// No value import exists - create a new one
|
|
104
|
+
// (Don't merge with type-only imports as they get erased by Babel/TypeScript)
|
|
99
105
|
const importDeclaration = t.importDeclaration(
|
|
100
|
-
[t.importSpecifier(t.identifier(
|
|
101
|
-
t.stringLiteral(
|
|
106
|
+
[t.importSpecifier(t.identifier(hookName), t.identifier(hookName))],
|
|
107
|
+
t.stringLiteral(importSource),
|
|
102
108
|
);
|
|
103
109
|
path.unshiftContainer("body", importDeclaration);
|
|
104
110
|
}
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
/**
|
|
108
|
-
* Inject
|
|
114
|
+
* Inject color scheme hook call at the top of a function component
|
|
109
115
|
*
|
|
110
116
|
* @param functionPath - Path to the function component
|
|
111
117
|
* @param colorSchemeVariableName - Name for the color scheme variable
|
|
118
|
+
* @param hookName - Name of the hook to call (e.g., 'useColorScheme')
|
|
119
|
+
* @param localIdentifier - Local identifier if hook is already imported with an alias
|
|
112
120
|
* @param t - Babel types
|
|
113
121
|
* @returns true if hook was injected, false if already exists
|
|
114
122
|
*/
|
|
115
123
|
export function injectColorSchemeHook(
|
|
116
124
|
functionPath: NodePath<BabelTypes.Function>,
|
|
117
125
|
colorSchemeVariableName: string,
|
|
126
|
+
hookName: string,
|
|
127
|
+
localIdentifier: string | undefined,
|
|
118
128
|
t: typeof BabelTypes,
|
|
119
129
|
): boolean {
|
|
120
130
|
let body = functionPath.node.body;
|
|
@@ -151,11 +161,16 @@ export function injectColorSchemeHook(
|
|
|
151
161
|
return false; // Already injected
|
|
152
162
|
}
|
|
153
163
|
|
|
154
|
-
//
|
|
164
|
+
// Use the local identifier if hook was already imported with an alias,
|
|
165
|
+
// otherwise use the configured hook name
|
|
166
|
+
// e.g., import { useTheme as navTheme } → call navTheme()
|
|
167
|
+
const identifierToCall = localIdentifier ?? hookName;
|
|
168
|
+
|
|
169
|
+
// Create: const _twColorScheme = useColorScheme(); (or aliased name if already imported)
|
|
155
170
|
const hookCall = t.variableDeclaration("const", [
|
|
156
171
|
t.variableDeclarator(
|
|
157
172
|
t.identifier(colorSchemeVariableName),
|
|
158
|
-
t.callExpression(t.identifier(
|
|
173
|
+
t.callExpression(t.identifier(identifierToCall), []),
|
|
159
174
|
),
|
|
160
175
|
]);
|
|
161
176
|
|