Skip to content

Commit 5803f40

Browse files
feat: support tailwind v4 (#413)
* fix: no-custom-classname false negatives with nativewind (#1) * fix: load correct tailwind config in monorepo (#2) * refactor!: move to tailwind api utils * fix sync * compatibility for theme * try better cache? * update * perf * better notice * update version * fix check test env * update version * relax peerDependencies * no throw error * turn off the buggy rules * update version * chore(deps): update tailwind-api-utils (#5) * remove package meta change --------- Co-authored-by: Paul Parker <[email protected]>
1 parent be925ff commit 5803f40

21 files changed

+542
-349
lines changed

lib/config/rules.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ module.exports = {
77
'tailwindcss/classnames-order': 'warn',
88
'tailwindcss/enforces-negative-arbitrary-values': 'warn',
99
'tailwindcss/enforces-shorthand': 'warn',
10-
'tailwindcss/migration-from-tailwind-2': 'warn',
10+
'tailwindcss/migration-from-tailwind-2': 'off',
1111
'tailwindcss/no-arbitrary-value': 'off',
1212
'tailwindcss/no-custom-classname': 'warn',
13-
'tailwindcss/no-contradicting-classname': 'error',
13+
'tailwindcss/no-contradicting-classname': 'off',
1414
'tailwindcss/no-unnecessary-arbitrary-value': 'warn',
1515
};

lib/rules/classnames-order.js

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,11 @@
55
'use strict';
66

77
const docsUrl = require('../util/docsUrl');
8-
const customConfig = require('../util/customConfig');
8+
const { getSortedClassNames } = require('../util/tailwindAPI');
99
const astUtil = require('../util/ast');
1010
const removeDuplicatesFromClassnamesAndWhitespaces = require('../util/removeDuplicatesFromClassnamesAndWhitespaces');
1111
const getOption = require('../util/settings');
1212
const parserUtil = require('../util/parser');
13-
const order = require('../util/prettier/order');
14-
const createContextFallback = require('tailwindcss/lib/lib/setupContextUtils').createContext;
1513

1614
//------------------------------------------------------------------------------
1715
// Rule Definition
@@ -21,8 +19,6 @@ const createContextFallback = require('tailwindcss/lib/lib/setupContextUtils').c
2119
// messageId will still be usable in tests.
2220
const INVALID_CLASSNAMES_ORDER_MSG = 'Invalid Tailwind CSS classnames order';
2321

24-
const contextFallbackCache = new WeakMap();
25-
2622
module.exports = {
2723
meta: {
2824
docs: {
@@ -75,14 +71,6 @@ module.exports = {
7571
const classRegex = getOption(context, 'classRegex');
7672
const removeDuplicates = getOption(context, 'removeDuplicates');
7773

78-
const mergedConfig = customConfig.resolve(twConfig);
79-
const contextFallback = // Set the created contextFallback in the cache if it does not exist yet.
80-
(
81-
contextFallbackCache.has(mergedConfig)
82-
? contextFallbackCache
83-
: contextFallbackCache.set(mergedConfig, createContextFallback(mergedConfig))
84-
).get(mergedConfig);
85-
8674
//----------------------------------------------------------------------
8775
// Helpers
8876
//----------------------------------------------------------------------
@@ -175,7 +163,7 @@ module.exports = {
175163
return;
176164
}
177165

178-
let orderedClassNames = order(classNames, contextFallback).split(' ');
166+
let orderedClassNames = getSortedClassNames(twConfig, classNames);
179167

180168
if (removeDuplicates) {
181169
removeDuplicatesFromClassnamesAndWhitespaces(orderedClassNames, whitespaces, headSpace, tailSpace);

lib/rules/enforces-negative-arbitrary-values.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
'use strict';
66

77
const docsUrl = require('../util/docsUrl');
8-
const customConfig = require('../util/customConfig');
8+
const { getTailwindConfig } = require('../util/tailwindAPI');
99
const astUtil = require('../util/ast');
1010
const groupUtil = require('../util/groupMethods');
1111
const getOption = require('../util/settings');
@@ -66,7 +66,7 @@ module.exports = {
6666
const twConfig = getOption(context, 'config');
6767
const classRegex = getOption(context, 'classRegex');
6868

69-
const mergedConfig = customConfig.resolve(twConfig);
69+
const mergedConfig = getTailwindConfig(twConfig);
7070

7171
//----------------------------------------------------------------------
7272
// Helpers

lib/rules/enforces-shorthand.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
const docsUrl = require('../util/docsUrl');
88
const defaultGroups = require('../config/groups').groups;
9-
const customConfig = require('../util/customConfig');
9+
const { getTailwindConfig } = require('../util/tailwindAPI');
1010
const astUtil = require('../util/ast');
1111
const groupUtil = require('../util/groupMethods');
1212
const getOption = require('../util/settings');
@@ -67,7 +67,7 @@ module.exports = {
6767
const twConfig = getOption(context, 'config');
6868
const classRegex = getOption(context, 'classRegex');
6969

70-
const mergedConfig = customConfig.resolve(twConfig);
70+
const mergedConfig = getTailwindConfig(twConfig);
7171

7272
//----------------------------------------------------------------------
7373
// Helpers
@@ -270,7 +270,15 @@ module.exports = {
270270
const bodyMatch = inputSet.some(
271271
(inputClassPattern) => `${mergedConfig.prefix}${inputClassPattern}` === remainingClass.body
272272
);
273-
if ([undefined, null].includes(mergedConfig.theme.size)) {
273+
if (
274+
!mergedConfig.theme ||
275+
!mergedConfig.theme.size ||
276+
!mergedConfig.theme.size[remainingClass.value] ||
277+
!mergedConfig.theme.width ||
278+
!mergedConfig.theme.width[remainingClass.value] ||
279+
!mergedConfig.theme.height ||
280+
!mergedConfig.theme.height[remainingClass.value]
281+
) {
274282
return false;
275283
}
276284
// w-screen + h-screen ≠ size-screen (Issue #307)

lib/rules/migration-from-tailwind-2.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
'use strict';
66

77
const docsUrl = require('../util/docsUrl');
8-
const customConfig = require('../util/customConfig');
8+
const { getTailwindConfig } = require('../util/tailwindAPI');
99
const astUtil = require('../util/ast');
1010
const groupUtil = require('../util/groupMethods');
1111
const getOption = require('../util/settings');
@@ -72,7 +72,7 @@ module.exports = {
7272
const twConfig = getOption(context, 'config');
7373
const classRegex = getOption(context, 'classRegex');
7474

75-
const mergedConfig = customConfig.resolve(twConfig);
75+
const mergedConfig = getTailwindConfig(twConfig);
7676

7777
//----------------------------------------------------------------------
7878
// Helpers

lib/rules/no-arbitrary-value.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
'use strict';
66

77
const docsUrl = require('../util/docsUrl');
8-
const customConfig = require('../util/customConfig');
8+
const { getTailwindConfig } = require('../util/tailwindAPI');
99
const astUtil = require('../util/ast');
1010
const groupUtil = require('../util/groupMethods');
1111
const getOption = require('../util/settings');
@@ -66,7 +66,7 @@ module.exports = {
6666
const twConfig = getOption(context, 'config');
6767
const classRegex = getOption(context, 'classRegex');
6868

69-
const mergedConfig = customConfig.resolve(twConfig);
69+
const mergedConfig = getTailwindConfig(twConfig);
7070

7171
//----------------------------------------------------------------------
7272
// Helpers

lib/rules/no-contradicting-classname.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
const docsUrl = require('../util/docsUrl');
88
const defaultGroups = require('../config/groups').groups;
9-
const customConfig = require('../util/customConfig');
9+
const { getTailwindConfig } = require('../util/tailwindAPI');
1010
const astUtil = require('../util/ast');
1111
const groupUtil = require('../util/groupMethods');
1212
const getOption = require('../util/settings');
@@ -68,7 +68,7 @@ module.exports = {
6868
const twConfig = getOption(context, 'config');
6969
const classRegex = getOption(context, 'classRegex');
7070

71-
const mergedConfig = customConfig.resolve(twConfig);
71+
const mergedConfig = getTailwindConfig(twConfig);
7272

7373
//----------------------------------------------------------------------
7474
// Helpers

lib/rules/no-custom-classname.js

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66

77
const docsUrl = require('../util/docsUrl');
88
const defaultGroups = require('../config/groups').groups;
9-
const customConfig = require('../util/customConfig');
9+
const { getTailwindConfig, isValidClassName } = require('../util/tailwindAPI');
1010
const astUtil = require('../util/ast');
1111
const groupUtil = require('../util/groupMethods');
1212
const getOption = require('../util/settings');
1313
const parserUtil = require('../util/parser');
1414
const getClassnamesFromCSS = require('../util/cssFiles');
15-
const createContextFallback = require('tailwindcss/lib/lib/setupContextUtils').createContext;
16-
const generated = require('../util/generated');
1715
const escapeRegex = require('../util/regex').escapeRegex;
1816

1917
//------------------------------------------------------------------------------
@@ -97,13 +95,7 @@ module.exports = {
9795
const whitelist = getOption(context, 'whitelist');
9896
const classRegex = getOption(context, 'classRegex');
9997

100-
const mergedConfig = customConfig.resolve(twConfig);
101-
const contextFallback = // Set the created contextFallback in the cache if it does not exist yet.
102-
(
103-
contextFallbackCache.has(mergedConfig)
104-
? contextFallbackCache
105-
: contextFallbackCache.set(mergedConfig, createContextFallback(mergedConfig))
106-
).get(mergedConfig);
98+
const mergedConfig = getTailwindConfig(twConfig);
10799

108100
//----------------------------------------------------------------------
109101
// Helpers
@@ -121,8 +113,7 @@ module.exports = {
121113
*/
122114
const parseForCustomClassNames = (classNames, node) => {
123115
classNames.forEach((className) => {
124-
const gen = generated(className, contextFallback);
125-
if (gen.length) {
116+
if (isValidClassName(twConfig, className)) {
126117
return; // Lazier is faster... processing next className!
127118
}
128119
const idx = groupUtil.getGroupIndex(className, groups, mergedConfig.separator);

lib/rules/no-unnecessary-arbitrary-value.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
'use strict';
66

77
const docsUrl = require('../util/docsUrl');
8-
const customConfig = require('../util/customConfig');
8+
const { getTailwindConfig } = require('../util/tailwindAPI');
99
const astUtil = require('../util/ast');
1010
const groupUtil = require('../util/groupMethods');
1111
const getOption = require('../util/settings');
@@ -72,7 +72,7 @@ module.exports = {
7272
const twConfig = getOption(context, 'config');
7373
const classRegex = getOption(context, 'classRegex');
7474

75-
const mergedConfig = customConfig.resolve(twConfig);
75+
const mergedConfig = getTailwindConfig(twConfig);
7676
const groups = groupUtil.getGroups(defaultGroups, mergedConfig);
7777
const configKeys = groupUtil.getGroupConfigKeys(defaultGroups);
7878
let parentTemplateLiteral = null;
@@ -193,7 +193,7 @@ module.exports = {
193193
const isNegativeClass = parsed.body.indexOf('-') === 0;
194194
const isNegativeValue = arbitraryValue.indexOf('-') === 0;
195195
const configurationKey = configKeys[groupIdx];
196-
const configuration = mergedConfig.theme[configurationKey];
196+
const configuration = mergedConfig.theme?.[configurationKey];
197197
if ([undefined, null].includes(configuration)) {
198198
return false;
199199
}

lib/util/customConfig.js

Lines changed: 13 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,27 @@
11
'use strict';
22

3-
const fs = require('fs');
4-
const path = require('path');
5-
const resolveConfig = require('tailwindcss/resolveConfig');
6-
let twLoadConfig;
3+
const { TailwindUtils } = require('tailwind-api-utils');
74

8-
try {
9-
twLoadConfig = require('tailwindcss/lib/lib/load-config');
10-
} catch (err) {
11-
twLoadConfig = null;
12-
}
13-
14-
const CHECK_REFRESH_RATE = 1_000;
15-
let previousConfig = null;
16-
let lastCheck = null;
17-
let mergedConfig = null;
18-
let lastModifiedDate = null;
19-
20-
/**
21-
* @see https://stackoverflow.com/questions/9210542/node-js-require-cache-possible-to-invalidate
22-
* @param {string} module The path to the module
23-
* @returns the module's export
24-
*/
25-
function requireUncached(module) {
26-
delete require.cache[require.resolve(module)];
27-
if (twLoadConfig === null) {
28-
// Using native loading
29-
return require(module);
30-
} else {
31-
// Using Tailwind CSS's loadConfig utility
32-
return twLoadConfig.loadConfig(module);
33-
}
34-
}
5+
// for nativewind preset
6+
process.env.TAILWIND_MODE = 'build';
357

8+
const CHECK_REFRESH_RATE = 10_000;
9+
let lastCheck = new Map();
3610
/**
37-
* Load the config from a path string or parsed from an object
38-
* @param {string|Object} config
39-
* @returns `null` when unchanged, `{}` when not found
11+
* @type {Map<string, TailwindUtils}>}
4012
*/
41-
function loadConfig(config) {
42-
let loadedConfig = null;
43-
if (typeof config === 'string') {
44-
const resolvedPath = path.isAbsolute(config) ? config : path.join(path.resolve(), config);
45-
try {
46-
const stats = fs.statSync(resolvedPath);
47-
const mtime = `${stats.mtime || ''}`;
48-
if (stats === null) {
49-
// Default to no config
50-
loadedConfig = {};
51-
} else if (lastModifiedDate !== mtime) {
52-
// Load the config based on path
53-
lastModifiedDate = mtime;
54-
loadedConfig = requireUncached(resolvedPath);
55-
} else {
56-
// Unchanged config
57-
loadedConfig = null;
58-
}
59-
} catch (err) {
60-
// Default to no config
61-
loadedConfig = {};
62-
} finally {
63-
return loadedConfig;
64-
}
65-
} else {
66-
if (typeof config === 'object' && config !== null) {
67-
return config;
68-
}
69-
return {};
70-
}
71-
}
72-
73-
function convertConfigToString(config) {
74-
switch (typeof config) {
75-
case 'string':
76-
return config;
77-
case 'object':
78-
return JSON.stringify(config);
79-
default:
80-
return config.toString();
81-
}
82-
}
13+
let mergedConfig = new Map();
8314

8415
function resolve(twConfig) {
85-
const newConfig = convertConfigToString(twConfig) !== convertConfigToString(previousConfig);
16+
const newConfig = mergedConfig.get(twConfig) === undefined;
8617
const now = Date.now();
87-
const expired = now - lastCheck > CHECK_REFRESH_RATE;
18+
const expired = now - lastCheck.get(twConfig) > CHECK_REFRESH_RATE;
8819
if (newConfig || expired) {
89-
previousConfig = twConfig;
90-
lastCheck = now;
91-
const userConfig = loadConfig(twConfig);
92-
// userConfig is null when config file was not modified
93-
if (userConfig !== null) {
94-
mergedConfig = resolveConfig(userConfig);
95-
}
20+
lastCheck.set(twConfig, now);
21+
const tailwindUtils = new TailwindUtils();
22+
mergedConfig.set(twConfig, tailwindUtils);
9623
}
97-
return mergedConfig;
24+
return mergedConfig.get(twConfig);
9825
}
9926

10027
module.exports = {

0 commit comments

Comments
 (0)