Skip to content

Commit 8b75010

Browse files
committed
feat: warn about unsupported tailwind config theme keys
Add detection and user-friendly warnings for unsupported theme configuration keys in tailwind.config files. When users attempt to extend theme with unsupported properties (spacing, borderRadius, etc.), they now receive a clear warning message listing the unsupported keys and directing them to open an issue on GitHub. Currently supported: colors, fontFamily, fontSize The warning system includes: - Detection of unsupported keys in both theme.extend and theme - Deduplication to prevent repeated warnings for same config file - Clear messaging with link to GitHub issues - Production mode bypass for performance
1 parent d26d53d commit 8b75010

File tree

2 files changed

+126
-20
lines changed

2 files changed

+126
-20
lines changed

src/babel/config-loader.test.ts

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import * as fs from "fs";
22
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3-
import { extractCustomTheme, findTailwindConfig, loadTailwindConfig } from "./config-loader";
3+
import {
4+
extractCustomTheme,
5+
findTailwindConfig,
6+
loadTailwindConfig,
7+
warnUnsupportedThemeKeys,
8+
} from "./config-loader";
49

510
// Mock fs
611
vi.mock("fs");
@@ -122,33 +127,78 @@ describe("config-loader", () => {
122127
const result = extractCustomTheme("/project/src/file.ts");
123128
expect(result).toEqual({ colors: {}, fontFamily: {}, fontSize: {} });
124129
});
130+
});
125131

126-
it("should return empty theme when config has no theme", () => {
127-
const configPath = "/project/tailwind.config.js";
132+
describe("warnUnsupportedThemeKeys", () => {
133+
it("should warn about unsupported theme keys", () => {
134+
const configPath = "/project/unsupported/tailwind.config.js";
135+
const mockConfig = {
136+
theme: {
137+
extend: {
138+
colors: { brand: "#123456" },
139+
spacing: { "72": "18rem" }, // Unsupported
140+
borderRadius: { xl: "1rem" }, // Unsupported
141+
},
142+
screens: { tablet: "640px" }, // Unsupported
143+
},
144+
};
128145

129-
vi.spyOn(fs, "existsSync").mockImplementation((filepath) => filepath === configPath);
130-
vi.spyOn(require, "resolve").mockReturnValue(configPath);
146+
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(vi.fn());
131147

132-
// loadTailwindConfig will be called, but we've already tested it
133-
// For integration, we'd need to mock the entire flow
134-
const result = extractCustomTheme("/project/src/file.ts");
148+
warnUnsupportedThemeKeys(mockConfig, configPath);
135149

136-
// Without actual config loading, this returns empty
137-
expect(result).toEqual({ colors: {}, fontFamily: {}, fontSize: {} });
150+
expect(consoleSpy).toHaveBeenCalledWith(
151+
expect.stringContaining("Unsupported theme configuration detected"),
152+
);
153+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("theme.extend.spacing"));
154+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("theme.extend.borderRadius"));
155+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("theme.screens"));
156+
expect(consoleSpy).toHaveBeenCalledWith(
157+
expect.stringContaining("https://github.com/mgcrea/react-native-tailwind/issues/new"),
158+
);
159+
160+
consoleSpy.mockRestore();
161+
});
162+
163+
it("should not warn for supported theme keys only", () => {
164+
const configPath = "/project/supported/tailwind.config.js";
165+
const mockConfig = {
166+
theme: {
167+
extend: {
168+
colors: { brand: "#123456" },
169+
fontFamily: { custom: "CustomFont" },
170+
fontSize: { huge: "48px" },
171+
},
172+
},
173+
};
174+
175+
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(vi.fn());
176+
177+
warnUnsupportedThemeKeys(mockConfig, configPath);
178+
179+
expect(consoleSpy).not.toHaveBeenCalled();
180+
181+
consoleSpy.mockRestore();
138182
});
139183

140-
it("should extract colors and fontFamily from theme.extend", () => {
141-
// This would require complex mocking of the entire require flow
142-
// Testing the logic: theme.extend is preferred
143-
const colors = { brand: { light: "#fff", dark: "#000" } };
144-
const fontFamily = { sans: ['"SF Pro"'], custom: ['"Custom Font"'] };
145-
const theme = {
146-
extend: { colors, fontFamily },
184+
it("should only warn once per config path", () => {
185+
const configPath = "/project/once/tailwind.config.js";
186+
const mockConfig = {
187+
theme: {
188+
extend: {
189+
spacing: { "72": "18rem" },
190+
},
191+
},
147192
};
148193

149-
// If we had the config, we'd flatten the colors and convert fontFamily
150-
expect(theme.extend.colors).toEqual(colors);
151-
expect(theme.extend.fontFamily).toEqual(fontFamily);
194+
const consoleSpy = vi.spyOn(console, "warn").mockImplementation(vi.fn());
195+
196+
warnUnsupportedThemeKeys(mockConfig, configPath);
197+
warnUnsupportedThemeKeys(mockConfig, configPath);
198+
199+
expect(consoleSpy).toHaveBeenCalledTimes(1);
200+
201+
consoleSpy.mockRestore();
152202
});
153203
});
154204
});

src/babel/config-loader.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,66 @@ export type TailwindConfig = {
1515
colors?: Record<string, string | Record<string, string>>;
1616
fontFamily?: Record<string, string | string[]>;
1717
fontSize?: Record<string, string | number>;
18+
[key: string]: unknown;
1819
};
1920
colors?: Record<string, string | Record<string, string>>;
2021
fontFamily?: Record<string, string | string[]>;
2122
fontSize?: Record<string, string | number>;
23+
[key: string]: unknown;
2224
};
2325
};
2426

27+
/**
28+
* Theme keys currently supported by react-native-tailwind
29+
*/
30+
const SUPPORTED_THEME_KEYS = new Set(["colors", "fontFamily", "fontSize", "extend"]);
31+
32+
/**
33+
* Cache for warned config paths to avoid duplicate warnings
34+
*/
35+
const warnedConfigPaths = new Set<string>();
36+
37+
/**
38+
* Check for unsupported theme extensions and warn the user
39+
* @internal Exported for testing
40+
*/
41+
export function warnUnsupportedThemeKeys(config: TailwindConfig, configPath: string): void {
42+
if (process.env.NODE_ENV === "production" || warnedConfigPaths.has(configPath)) {
43+
return;
44+
}
45+
46+
const unsupportedKeys: string[] = [];
47+
48+
// Check theme.extend keys
49+
if (config.theme?.extend && typeof config.theme.extend === "object") {
50+
for (const key of Object.keys(config.theme.extend)) {
51+
if (!SUPPORTED_THEME_KEYS.has(key)) {
52+
unsupportedKeys.push(`theme.extend.${key}`);
53+
}
54+
}
55+
}
56+
57+
// Check direct theme keys (excluding 'extend')
58+
if (config.theme && typeof config.theme === "object") {
59+
for (const key of Object.keys(config.theme)) {
60+
if (key !== "extend" && !SUPPORTED_THEME_KEYS.has(key)) {
61+
unsupportedKeys.push(`theme.${key}`);
62+
}
63+
}
64+
}
65+
66+
if (unsupportedKeys.length > 0) {
67+
warnedConfigPaths.add(configPath);
68+
console.warn(
69+
`[react-native-tailwind] Unsupported theme configuration detected:\n` +
70+
` ${unsupportedKeys.join(", ")}\n\n` +
71+
` Currently supported: colors, fontFamily, fontSize\n\n` +
72+
` These extensions will be ignored. If you need support for these features,\n` +
73+
` please open an issue: https://github.com/mgcrea/react-native-tailwind/issues/new`,
74+
);
75+
}
76+
}
77+
2578
// Cache configs per path to avoid repeated file I/O
2679
const configCache = new Map<string, TailwindConfig | null>();
2780

@@ -111,6 +164,9 @@ export function extractCustomTheme(filename: string): CustomTheme {
111164
return { colors: {}, fontFamily: {}, fontSize: {} };
112165
}
113166

167+
// Warn about unsupported theme keys
168+
warnUnsupportedThemeKeys(config, configPath);
169+
114170
// Extract colors
115171
/* v8 ignore next 5 */
116172
if (config.theme.colors && !config.theme.extend?.colors && process.env.NODE_ENV !== "production") {

0 commit comments

Comments
 (0)