tailwindcss 3.0.23 → 3.0.24
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/CHANGELOG.md +29 -3
- package/lib/cli-peer-dependencies.js +3 -3
- package/lib/cli.js +183 -161
- package/lib/constants.js +8 -8
- package/lib/corePlugins.js +1572 -1523
- package/lib/featureFlags.js +9 -9
- package/lib/index.js +19 -6
- package/lib/lib/cacheInvalidation.js +69 -0
- package/lib/lib/collapseAdjacentRules.js +26 -13
- package/lib/lib/collapseDuplicateDeclarations.js +1 -1
- package/lib/lib/defaultExtractor.js +6 -6
- package/lib/lib/detectNesting.js +9 -9
- package/lib/lib/evaluateTailwindFunctions.js +16 -16
- package/lib/lib/expandApplyAtRules.js +180 -27
- package/lib/lib/expandTailwindAtRules.js +132 -122
- package/lib/lib/generateRules.js +90 -72
- package/lib/lib/getModuleDependencies.js +14 -14
- package/lib/lib/normalizeTailwindDirectives.js +35 -35
- package/lib/lib/partitionApplyAtRules.js +7 -7
- package/lib/lib/resolveDefaultsAtRules.js +81 -77
- package/lib/lib/setupContextUtils.js +78 -87
- package/lib/lib/setupTrackingContext.js +57 -57
- package/lib/lib/sharedState.js +10 -8
- package/lib/lib/substituteScreenAtRules.js +2 -2
- package/lib/postcss-plugins/nesting/README.md +2 -2
- package/lib/postcss-plugins/nesting/index.js +1 -1
- package/lib/postcss-plugins/nesting/plugin.js +41 -9
- package/lib/processTailwindFeatures.js +7 -7
- package/lib/public/colors.js +241 -241
- package/lib/public/resolve-config.js +5 -5
- package/lib/util/buildMediaQuery.js +2 -2
- package/lib/util/cloneDeep.js +1 -1
- package/lib/util/cloneNodes.js +12 -1
- package/lib/util/color.js +21 -20
- package/lib/util/createUtilityPlugin.js +6 -6
- package/lib/util/dataTypes.js +77 -75
- package/lib/util/escapeClassName.js +5 -5
- package/lib/util/escapeCommas.js +1 -1
- package/lib/util/flattenColorPalette.js +2 -2
- package/lib/util/formatVariantSelector.js +19 -19
- package/lib/util/getAllConfigs.js +5 -5
- package/lib/util/hashConfig.js +5 -5
- package/lib/util/isKeyframeRule.js +1 -1
- package/lib/util/isPlainObject.js +1 -1
- package/lib/util/isValidArbitraryValue.js +27 -27
- package/lib/util/log.js +8 -8
- package/lib/util/nameClass.js +7 -7
- package/lib/util/negateValue.js +4 -4
- package/lib/util/normalizeConfig.js +39 -39
- package/lib/util/normalizeScreens.js +4 -4
- package/lib/util/parseAnimationValue.js +56 -56
- package/lib/util/parseBoxShadowValue.js +60 -20
- package/lib/util/parseDependency.js +32 -32
- package/lib/util/parseObjectStyles.js +6 -6
- package/lib/util/pluginUtils.js +9 -9
- package/lib/util/prefixSelector.js +1 -1
- package/lib/util/resolveConfig.js +28 -28
- package/lib/util/resolveConfigPath.js +16 -16
- package/lib/util/responsive.js +6 -6
- package/lib/util/toColorValue.js +1 -1
- package/lib/util/toPath.js +2 -2
- package/lib/util/transformThemeValue.js +27 -27
- package/lib/util/withAlphaVariable.js +19 -19
- package/package.json +24 -23
- package/peers/index.js +4777 -4831
- package/scripts/generate-types.js +52 -0
- package/src/cli.js +41 -11
- package/src/corePlugins.js +67 -5
- package/src/featureFlags.js +2 -2
- package/src/index.js +17 -1
- package/src/lib/cacheInvalidation.js +52 -0
- package/src/lib/collapseAdjacentRules.js +16 -1
- package/src/lib/defaultExtractor.js +4 -4
- package/src/lib/expandApplyAtRules.js +179 -6
- package/src/lib/expandTailwindAtRules.js +25 -5
- package/src/lib/generateRules.js +68 -46
- package/src/lib/resolveDefaultsAtRules.js +6 -2
- package/src/lib/setupContextUtils.js +25 -26
- package/src/lib/setupTrackingContext.js +3 -3
- package/src/lib/sharedState.js +1 -0
- package/src/postcss-plugins/nesting/README.md +2 -2
- package/src/postcss-plugins/nesting/plugin.js +36 -0
- package/src/util/cloneNodes.js +14 -1
- package/src/util/color.js +7 -5
- package/src/util/dataTypes.js +3 -1
- package/src/util/log.js +7 -7
- package/src/util/parseBoxShadowValue.js +50 -2
- package/src/util/resolveConfig.js +32 -0
- package/stubs/defaultConfig.stub.js +5 -0
|
@@ -5,6 +5,8 @@ import { resolveMatches } from './generateRules'
|
|
|
5
5
|
import bigSign from '../util/bigSign'
|
|
6
6
|
import escapeClassName from '../util/escapeClassName'
|
|
7
7
|
|
|
8
|
+
/** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */
|
|
9
|
+
|
|
8
10
|
function extractClasses(node) {
|
|
9
11
|
let classes = new Set()
|
|
10
12
|
let container = postcss.root({ nodes: [node.clone()] })
|
|
@@ -35,6 +37,131 @@ function prefix(context, selector) {
|
|
|
35
37
|
return typeof prefix === 'function' ? prefix(selector) : prefix + selector
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
function* pathToRoot(node) {
|
|
41
|
+
yield node
|
|
42
|
+
while (node.parent) {
|
|
43
|
+
yield node.parent
|
|
44
|
+
node = node.parent
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Only clone the node itself and not its children
|
|
50
|
+
*
|
|
51
|
+
* @param {*} node
|
|
52
|
+
* @param {*} overrides
|
|
53
|
+
* @returns
|
|
54
|
+
*/
|
|
55
|
+
function shallowClone(node, overrides = {}) {
|
|
56
|
+
let children = node.nodes
|
|
57
|
+
node.nodes = []
|
|
58
|
+
|
|
59
|
+
let tmp = node.clone(overrides)
|
|
60
|
+
|
|
61
|
+
node.nodes = children
|
|
62
|
+
|
|
63
|
+
return tmp
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Clone just the nodes all the way to the top that are required to represent
|
|
68
|
+
* this singular rule in the tree.
|
|
69
|
+
*
|
|
70
|
+
* For example, if we have CSS like this:
|
|
71
|
+
* ```css
|
|
72
|
+
* @media (min-width: 768px) {
|
|
73
|
+
* @supports (display: grid) {
|
|
74
|
+
* .foo {
|
|
75
|
+
* display: grid;
|
|
76
|
+
* grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
77
|
+
* }
|
|
78
|
+
* }
|
|
79
|
+
*
|
|
80
|
+
* @supports (backdrop-filter: blur(1px)) {
|
|
81
|
+
* .bar {
|
|
82
|
+
* backdrop-filter: blur(1px);
|
|
83
|
+
* }
|
|
84
|
+
* }
|
|
85
|
+
*
|
|
86
|
+
* .baz {
|
|
87
|
+
* color: orange;
|
|
88
|
+
* }
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* And we're cloning `.bar` it'll return a cloned version of what's required for just that single node:
|
|
93
|
+
*
|
|
94
|
+
* ```css
|
|
95
|
+
* @media (min-width: 768px) {
|
|
96
|
+
* @supports (backdrop-filter: blur(1px)) {
|
|
97
|
+
* .bar {
|
|
98
|
+
* backdrop-filter: blur(1px);
|
|
99
|
+
* }
|
|
100
|
+
* }
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @param {import('postcss').Node} node
|
|
105
|
+
*/
|
|
106
|
+
function nestedClone(node) {
|
|
107
|
+
for (let parent of pathToRoot(node)) {
|
|
108
|
+
if (node === parent) {
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (parent.type === 'root') {
|
|
113
|
+
break
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
node = shallowClone(parent, {
|
|
117
|
+
nodes: [node],
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return node
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {import('postcss').Root} root
|
|
126
|
+
*/
|
|
127
|
+
function buildLocalApplyCache(root, context) {
|
|
128
|
+
/** @type {ApplyCache} */
|
|
129
|
+
let cache = new Map()
|
|
130
|
+
|
|
131
|
+
let highestOffset = context.layerOrder.user >> 4n
|
|
132
|
+
|
|
133
|
+
root.walkRules((rule, idx) => {
|
|
134
|
+
// Ignore rules generated by Tailwind
|
|
135
|
+
for (let node of pathToRoot(rule)) {
|
|
136
|
+
if (node.raws.tailwind?.layer !== undefined) {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Clone what's required to represent this singular rule in the tree
|
|
142
|
+
let container = nestedClone(rule)
|
|
143
|
+
|
|
144
|
+
for (let className of extractClasses(rule)) {
|
|
145
|
+
let list = cache.get(className) || []
|
|
146
|
+
cache.set(className, list)
|
|
147
|
+
|
|
148
|
+
list.push([
|
|
149
|
+
{
|
|
150
|
+
layer: 'user',
|
|
151
|
+
sort: BigInt(idx) + highestOffset,
|
|
152
|
+
important: false,
|
|
153
|
+
},
|
|
154
|
+
container,
|
|
155
|
+
])
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
return cache
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @returns {ApplyCache}
|
|
164
|
+
*/
|
|
38
165
|
function buildApplyCache(applyCandidates, context) {
|
|
39
166
|
for (let candidate of applyCandidates) {
|
|
40
167
|
if (context.notClassCache.has(candidate) || context.applyClassCache.has(candidate)) {
|
|
@@ -62,6 +189,43 @@ function buildApplyCache(applyCandidates, context) {
|
|
|
62
189
|
return context.applyClassCache
|
|
63
190
|
}
|
|
64
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Build a cache only when it's first used
|
|
194
|
+
*
|
|
195
|
+
* @param {() => ApplyCache} buildCacheFn
|
|
196
|
+
* @returns {ApplyCache}
|
|
197
|
+
*/
|
|
198
|
+
function lazyCache(buildCacheFn) {
|
|
199
|
+
let cache = null
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
get: (name) => {
|
|
203
|
+
cache = cache || buildCacheFn()
|
|
204
|
+
|
|
205
|
+
return cache.get(name)
|
|
206
|
+
},
|
|
207
|
+
has: (name) => {
|
|
208
|
+
cache = cache || buildCacheFn()
|
|
209
|
+
|
|
210
|
+
return cache.has(name)
|
|
211
|
+
},
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Take a series of multiple caches and merge
|
|
217
|
+
* them so they act like one large cache
|
|
218
|
+
*
|
|
219
|
+
* @param {ApplyCache[]} caches
|
|
220
|
+
* @returns {ApplyCache}
|
|
221
|
+
*/
|
|
222
|
+
function combineCaches(caches) {
|
|
223
|
+
return {
|
|
224
|
+
get: (name) => caches.flatMap((cache) => cache.get(name) || []),
|
|
225
|
+
has: (name) => caches.some((cache) => cache.has(name)),
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
65
229
|
function extractApplyCandidates(params) {
|
|
66
230
|
let candidates = params.split(/[\s\t\n]+/g)
|
|
67
231
|
|
|
@@ -72,7 +236,7 @@ function extractApplyCandidates(params) {
|
|
|
72
236
|
return [candidates, false]
|
|
73
237
|
}
|
|
74
238
|
|
|
75
|
-
function processApply(root, context) {
|
|
239
|
+
function processApply(root, context, localCache) {
|
|
76
240
|
let applyCandidates = new Set()
|
|
77
241
|
|
|
78
242
|
// Collect all @apply rules and candidates
|
|
@@ -90,7 +254,7 @@ function processApply(root, context) {
|
|
|
90
254
|
// Start the @apply process if we have rules with @apply in them
|
|
91
255
|
if (applies.length > 0) {
|
|
92
256
|
// Fill up some caches!
|
|
93
|
-
let applyClassCache = buildApplyCache(applyCandidates, context)
|
|
257
|
+
let applyClassCache = combineCaches([localCache, buildApplyCache(applyCandidates, context)])
|
|
94
258
|
|
|
95
259
|
/**
|
|
96
260
|
* When we have an apply like this:
|
|
@@ -140,7 +304,7 @@ function processApply(root, context) {
|
|
|
140
304
|
for (let apply of applies) {
|
|
141
305
|
let candidates = perParentApplies.get(apply.parent) || []
|
|
142
306
|
|
|
143
|
-
perParentApplies.set(apply.parent, candidates)
|
|
307
|
+
perParentApplies.set(apply.parent, [candidates, apply.source])
|
|
144
308
|
|
|
145
309
|
let [applyCandidates, important] = extractApplyCandidates(apply.params)
|
|
146
310
|
|
|
@@ -178,7 +342,7 @@ function processApply(root, context) {
|
|
|
178
342
|
}
|
|
179
343
|
}
|
|
180
344
|
|
|
181
|
-
for (const [parent, candidates] of perParentApplies) {
|
|
345
|
+
for (const [parent, [candidates, atApplySource]] of perParentApplies) {
|
|
182
346
|
let siblings = []
|
|
183
347
|
|
|
184
348
|
for (let [applyCandidate, important, rules] of candidates) {
|
|
@@ -220,6 +384,12 @@ function processApply(root, context) {
|
|
|
220
384
|
}
|
|
221
385
|
|
|
222
386
|
let root = postcss.root({ nodes: [node.clone()] })
|
|
387
|
+
|
|
388
|
+
// Make sure every node in the entire tree points back at the @apply rule that generated it
|
|
389
|
+
root.walk((node) => {
|
|
390
|
+
node.source = atApplySource
|
|
391
|
+
})
|
|
392
|
+
|
|
223
393
|
let canRewriteSelector =
|
|
224
394
|
node.type !== 'atrule' || (node.type === 'atrule' && node.name !== 'keyframes')
|
|
225
395
|
|
|
@@ -296,12 +466,15 @@ function processApply(root, context) {
|
|
|
296
466
|
}
|
|
297
467
|
|
|
298
468
|
// Do it again, in case we have other `@apply` rules
|
|
299
|
-
processApply(root, context)
|
|
469
|
+
processApply(root, context, localCache)
|
|
300
470
|
}
|
|
301
471
|
}
|
|
302
472
|
|
|
303
473
|
export default function expandApplyAtRules(context) {
|
|
304
474
|
return (root) => {
|
|
305
|
-
|
|
475
|
+
// Build a cache of the user's CSS so we can use it to resolve classes used by @apply
|
|
476
|
+
let localCache = lazyCache(() => buildLocalApplyCache(root, context))
|
|
477
|
+
|
|
478
|
+
processApply(root, context, localCache)
|
|
306
479
|
}
|
|
307
480
|
}
|
|
@@ -204,17 +204,29 @@ export default function expandTailwindAtRules(context) {
|
|
|
204
204
|
// Replace any Tailwind directives with generated CSS
|
|
205
205
|
|
|
206
206
|
if (layerNodes.base) {
|
|
207
|
-
layerNodes.base.before(
|
|
207
|
+
layerNodes.base.before(
|
|
208
|
+
cloneNodes([...baseNodes, ...defaultNodes], layerNodes.base.source, {
|
|
209
|
+
layer: 'base',
|
|
210
|
+
})
|
|
211
|
+
)
|
|
208
212
|
layerNodes.base.remove()
|
|
209
213
|
}
|
|
210
214
|
|
|
211
215
|
if (layerNodes.components) {
|
|
212
|
-
layerNodes.components.before(
|
|
216
|
+
layerNodes.components.before(
|
|
217
|
+
cloneNodes([...componentNodes], layerNodes.components.source, {
|
|
218
|
+
layer: 'components',
|
|
219
|
+
})
|
|
220
|
+
)
|
|
213
221
|
layerNodes.components.remove()
|
|
214
222
|
}
|
|
215
223
|
|
|
216
224
|
if (layerNodes.utilities) {
|
|
217
|
-
layerNodes.utilities.before(
|
|
225
|
+
layerNodes.utilities.before(
|
|
226
|
+
cloneNodes([...utilityNodes], layerNodes.utilities.source, {
|
|
227
|
+
layer: 'utilities',
|
|
228
|
+
})
|
|
229
|
+
)
|
|
218
230
|
layerNodes.utilities.remove()
|
|
219
231
|
}
|
|
220
232
|
|
|
@@ -234,10 +246,18 @@ export default function expandTailwindAtRules(context) {
|
|
|
234
246
|
})
|
|
235
247
|
|
|
236
248
|
if (layerNodes.variants) {
|
|
237
|
-
layerNodes.variants.before(
|
|
249
|
+
layerNodes.variants.before(
|
|
250
|
+
cloneNodes(variantNodes, layerNodes.variants.source, {
|
|
251
|
+
layer: 'variants',
|
|
252
|
+
})
|
|
253
|
+
)
|
|
238
254
|
layerNodes.variants.remove()
|
|
239
255
|
} else if (variantNodes.length > 0) {
|
|
240
|
-
root.append(
|
|
256
|
+
root.append(
|
|
257
|
+
cloneNodes(variantNodes, root.source, {
|
|
258
|
+
layer: 'variants',
|
|
259
|
+
})
|
|
260
|
+
)
|
|
241
261
|
}
|
|
242
262
|
|
|
243
263
|
// If we've got a utility layer and no utilities are generated there's likely something wrong
|
package/src/lib/generateRules.js
CHANGED
|
@@ -88,7 +88,7 @@ function applyPrefix(matches, context) {
|
|
|
88
88
|
return matches
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
function applyImportant(matches) {
|
|
91
|
+
function applyImportant(matches, classCandidate) {
|
|
92
92
|
if (matches.length === 0) {
|
|
93
93
|
return matches
|
|
94
94
|
}
|
|
@@ -98,7 +98,10 @@ function applyImportant(matches) {
|
|
|
98
98
|
let container = postcss.root({ nodes: [rule.clone()] })
|
|
99
99
|
container.walkRules((r) => {
|
|
100
100
|
r.selector = updateAllClasses(r.selector, (className) => {
|
|
101
|
-
|
|
101
|
+
if (className === classCandidate) {
|
|
102
|
+
return `!${className}`
|
|
103
|
+
}
|
|
104
|
+
return className
|
|
102
105
|
})
|
|
103
106
|
r.walkDecls((d) => (d.important = true))
|
|
104
107
|
})
|
|
@@ -300,6 +303,19 @@ function looksLikeUri(declaration) {
|
|
|
300
303
|
}
|
|
301
304
|
}
|
|
302
305
|
|
|
306
|
+
function isParsableNode(node) {
|
|
307
|
+
let isParsable = true
|
|
308
|
+
|
|
309
|
+
node.walkDecls((decl) => {
|
|
310
|
+
if (!isParsableCssValue(decl.name, decl.value)) {
|
|
311
|
+
isParsable = false
|
|
312
|
+
return false
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
return isParsable
|
|
317
|
+
}
|
|
318
|
+
|
|
303
319
|
function isParsableCssValue(property, value) {
|
|
304
320
|
// We don't want to to treat [https://example.com] as a custom property
|
|
305
321
|
// Even though, according to the CSS grammar, it's a totally valid CSS declaration
|
|
@@ -453,60 +469,66 @@ function* resolveMatches(candidate, context) {
|
|
|
453
469
|
}
|
|
454
470
|
}
|
|
455
471
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
472
|
+
if (isArbitraryValue(modifier)) {
|
|
473
|
+
// When generated arbitrary values are ambiguous, we can't know
|
|
474
|
+
// which to pick so don't generate any utilities for them
|
|
475
|
+
if (matches.length > 1) {
|
|
476
|
+
let typesPerPlugin = matches.map((match) => new Set([...(typesByMatches.get(match) ?? [])]))
|
|
460
477
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
478
|
+
// Remove duplicates, so that we can detect proper unique types for each plugin.
|
|
479
|
+
for (let pluginTypes of typesPerPlugin) {
|
|
480
|
+
for (let type of pluginTypes) {
|
|
481
|
+
let removeFromOwnGroup = false
|
|
465
482
|
|
|
466
|
-
|
|
467
|
-
|
|
483
|
+
for (let otherGroup of typesPerPlugin) {
|
|
484
|
+
if (pluginTypes === otherGroup) continue
|
|
468
485
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
486
|
+
if (otherGroup.has(type)) {
|
|
487
|
+
otherGroup.delete(type)
|
|
488
|
+
removeFromOwnGroup = true
|
|
489
|
+
}
|
|
472
490
|
}
|
|
473
|
-
}
|
|
474
491
|
|
|
475
|
-
|
|
492
|
+
if (removeFromOwnGroup) pluginTypes.delete(type)
|
|
493
|
+
}
|
|
476
494
|
}
|
|
477
|
-
}
|
|
478
495
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
496
|
+
let messages = []
|
|
497
|
+
|
|
498
|
+
for (let [idx, group] of typesPerPlugin.entries()) {
|
|
499
|
+
for (let type of group) {
|
|
500
|
+
let rules = matches[idx]
|
|
501
|
+
.map(([, rule]) => rule)
|
|
502
|
+
.flat()
|
|
503
|
+
.map((rule) =>
|
|
504
|
+
rule
|
|
505
|
+
.toString()
|
|
506
|
+
.split('\n')
|
|
507
|
+
.slice(1, -1) // Remove selector and closing '}'
|
|
508
|
+
.map((line) => line.trim())
|
|
509
|
+
.map((x) => ` ${x}`) // Re-indent
|
|
510
|
+
.join('\n')
|
|
511
|
+
)
|
|
512
|
+
.join('\n\n')
|
|
513
|
+
|
|
514
|
+
messages.push(
|
|
515
|
+
` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``
|
|
494
516
|
)
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
messages.push(` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``)
|
|
498
|
-
break
|
|
517
|
+
break
|
|
518
|
+
}
|
|
499
519
|
}
|
|
520
|
+
|
|
521
|
+
log.warn([
|
|
522
|
+
`The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
|
|
523
|
+
...messages,
|
|
524
|
+
`If this is content and not a class, replace it with \`${candidate
|
|
525
|
+
.replace('[', '[')
|
|
526
|
+
.replace(']', ']')}\` to silence this warning.`,
|
|
527
|
+
])
|
|
528
|
+
continue
|
|
500
529
|
}
|
|
501
530
|
|
|
502
|
-
|
|
503
|
-
`The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
|
|
504
|
-
...messages,
|
|
505
|
-
`If this is content and not a class, replace it with \`${candidate
|
|
506
|
-
.replace('[', '[')
|
|
507
|
-
.replace(']', ']')}\` to silence this warning.`,
|
|
508
|
-
])
|
|
509
|
-
continue
|
|
531
|
+
matches = matches.map((list) => list.filter((match) => isParsableNode(match[1])))
|
|
510
532
|
}
|
|
511
533
|
|
|
512
534
|
matches = matches.flat()
|
|
@@ -514,7 +536,7 @@ function* resolveMatches(candidate, context) {
|
|
|
514
536
|
matches = applyPrefix(matches, context)
|
|
515
537
|
|
|
516
538
|
if (important) {
|
|
517
|
-
matches = applyImportant(matches,
|
|
539
|
+
matches = applyImportant(matches, classCandidate)
|
|
518
540
|
}
|
|
519
541
|
|
|
520
542
|
for (let variant of variants) {
|
|
@@ -120,7 +120,9 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
for (let [, selectors] of selectorGroups) {
|
|
123
|
-
let universalRule = postcss.rule(
|
|
123
|
+
let universalRule = postcss.rule({
|
|
124
|
+
source: universal.source,
|
|
125
|
+
})
|
|
124
126
|
|
|
125
127
|
universalRule.selectors = [...selectors]
|
|
126
128
|
|
|
@@ -128,7 +130,9 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
|
|
|
128
130
|
universal.before(universalRule)
|
|
129
131
|
}
|
|
130
132
|
} else {
|
|
131
|
-
let universalRule = postcss.rule(
|
|
133
|
+
let universalRule = postcss.rule({
|
|
134
|
+
source: universal.source,
|
|
135
|
+
})
|
|
132
136
|
|
|
133
137
|
universalRule.selectors = ['*', '::before', '::after']
|
|
134
138
|
|
|
@@ -20,6 +20,7 @@ import log from '../util/log'
|
|
|
20
20
|
import negateValue from '../util/negateValue'
|
|
21
21
|
import isValidArbitraryValue from '../util/isValidArbitraryValue'
|
|
22
22
|
import { generateRules } from './generateRules'
|
|
23
|
+
import { hasContentChanged } from './cacheInvalidation.js'
|
|
23
24
|
|
|
24
25
|
function prefix(context, selector) {
|
|
25
26
|
let prefix = context.tailwindConfig.prefix
|
|
@@ -84,12 +85,18 @@ function parseStyles(styles) {
|
|
|
84
85
|
})
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
function getClasses(selector) {
|
|
88
|
+
function getClasses(selector, mutate) {
|
|
88
89
|
let parser = selectorParser((selectors) => {
|
|
89
90
|
let allClasses = []
|
|
91
|
+
|
|
92
|
+
if (mutate) {
|
|
93
|
+
mutate(selectors)
|
|
94
|
+
}
|
|
95
|
+
|
|
90
96
|
selectors.walkClasses((classNode) => {
|
|
91
97
|
allClasses.push(classNode.value)
|
|
92
98
|
})
|
|
99
|
+
|
|
93
100
|
return allClasses
|
|
94
101
|
})
|
|
95
102
|
return parser.transformSync(selector)
|
|
@@ -100,8 +107,20 @@ function extractCandidates(node, state = { containsNonOnDemandable: false }, dep
|
|
|
100
107
|
|
|
101
108
|
// Handle normal rules
|
|
102
109
|
if (node.type === 'rule') {
|
|
110
|
+
// Ignore everything inside a :not(...). This allows you to write code like
|
|
111
|
+
// `div:not(.foo)`. If `.foo` is never found in your code, then we used to
|
|
112
|
+
// not generated it. But now we will ignore everything inside a `:not`, so
|
|
113
|
+
// that it still gets generated.
|
|
114
|
+
function ignoreNot(selectors) {
|
|
115
|
+
selectors.walkPseudos((pseudo) => {
|
|
116
|
+
if (pseudo.value === ':not') {
|
|
117
|
+
pseudo.remove()
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
|
|
103
122
|
for (let selector of node.selectors) {
|
|
104
|
-
let classCandidates = getClasses(selector)
|
|
123
|
+
let classCandidates = getClasses(selector, ignoreNot)
|
|
105
124
|
// At least one of the selectors contains non-"on-demandable" candidates.
|
|
106
125
|
if (classCandidates.length === 0) {
|
|
107
126
|
state.containsNonOnDemandable = true
|
|
@@ -116,9 +135,7 @@ function extractCandidates(node, state = { containsNonOnDemandable: false }, dep
|
|
|
116
135
|
// Handle at-rules (which contains nested rules)
|
|
117
136
|
else if (node.type === 'atrule') {
|
|
118
137
|
node.walkRules((rule) => {
|
|
119
|
-
for (let classCandidate of rule.selectors.flatMap((selector) =>
|
|
120
|
-
getClasses(selector, state, depth + 1)
|
|
121
|
-
)) {
|
|
138
|
+
for (let classCandidate of rule.selectors.flatMap((selector) => getClasses(selector))) {
|
|
122
139
|
classes.push(classCandidate)
|
|
123
140
|
}
|
|
124
141
|
})
|
|
@@ -230,17 +247,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
|
|
230
247
|
// Preserved for backwards compatibility but not used in v3.0+
|
|
231
248
|
return []
|
|
232
249
|
},
|
|
233
|
-
addUserCss(userCss) {
|
|
234
|
-
for (let [identifier, rule] of withIdentifiers(userCss)) {
|
|
235
|
-
let offset = offsets.user++
|
|
236
|
-
|
|
237
|
-
if (!context.candidateRuleMap.has(identifier)) {
|
|
238
|
-
context.candidateRuleMap.set(identifier, [])
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
context.candidateRuleMap.get(identifier).push([{ sort: offset, layer: 'user' }, rule])
|
|
242
|
-
}
|
|
243
|
-
},
|
|
244
250
|
addBase(base) {
|
|
245
251
|
for (let [identifier, rule] of withIdentifiers(base)) {
|
|
246
252
|
let prefixedIdentifier = prefixIdentifier(identifier, {})
|
|
@@ -521,15 +527,6 @@ function collectLayerPlugins(root) {
|
|
|
521
527
|
}
|
|
522
528
|
})
|
|
523
529
|
|
|
524
|
-
root.walkRules((rule) => {
|
|
525
|
-
// At this point it is safe to include all the left-over css from the
|
|
526
|
-
// user's css file. This is because the `@tailwind` and `@layer` directives
|
|
527
|
-
// will already be handled and will be removed from the css tree.
|
|
528
|
-
layerPlugins.push(function ({ addUserCss }) {
|
|
529
|
-
addUserCss(rule, { respectPrefix: false })
|
|
530
|
-
})
|
|
531
|
-
})
|
|
532
|
-
|
|
533
530
|
return layerPlugins
|
|
534
531
|
}
|
|
535
532
|
|
|
@@ -842,6 +839,8 @@ export function getContext(
|
|
|
842
839
|
existingContext = context
|
|
843
840
|
}
|
|
844
841
|
|
|
842
|
+
let cssDidChange = hasContentChanged(sourcePath, root)
|
|
843
|
+
|
|
845
844
|
// If there's already a context in the cache and we don't need to
|
|
846
845
|
// reset the context, return the cached context.
|
|
847
846
|
if (existingContext) {
|
|
@@ -849,7 +848,7 @@ export function getContext(
|
|
|
849
848
|
[...contextDependencies],
|
|
850
849
|
getFileModifiedMap(existingContext)
|
|
851
850
|
)
|
|
852
|
-
if (!contextDependenciesChanged) {
|
|
851
|
+
if (!contextDependenciesChanged && !cssDidChange) {
|
|
853
852
|
return [existingContext, false]
|
|
854
853
|
}
|
|
855
854
|
}
|
|
@@ -112,7 +112,7 @@ function resolveChangedFiles(candidateFiles, fileModifiedMap) {
|
|
|
112
112
|
// source path), or set up a new one (including setting up watchers and registering
|
|
113
113
|
// plugins) then return it
|
|
114
114
|
export default function setupTrackingContext(configOrPath) {
|
|
115
|
-
return ({ tailwindDirectives, registerDependency
|
|
115
|
+
return ({ tailwindDirectives, registerDependency }) => {
|
|
116
116
|
return (root, result) => {
|
|
117
117
|
let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] =
|
|
118
118
|
getTailwindConfig(configOrPath)
|
|
@@ -125,7 +125,7 @@ export default function setupTrackingContext(configOrPath) {
|
|
|
125
125
|
// being part of this trigger too, but it's tough because it's impossible
|
|
126
126
|
// for a layer in one file to end up in the actual @tailwind rule in
|
|
127
127
|
// another file since independent sources are effectively isolated.
|
|
128
|
-
if (tailwindDirectives.size > 0
|
|
128
|
+
if (tailwindDirectives.size > 0) {
|
|
129
129
|
// Add current css file as a context dependencies.
|
|
130
130
|
contextDependencies.add(result.opts.from)
|
|
131
131
|
|
|
@@ -153,7 +153,7 @@ export default function setupTrackingContext(configOrPath) {
|
|
|
153
153
|
// We may want to think about `@layer` being part of this trigger too, but it's tough
|
|
154
154
|
// because it's impossible for a layer in one file to end up in the actual @tailwind rule
|
|
155
155
|
// in another file since independent sources are effectively isolated.
|
|
156
|
-
if (tailwindDirectives.size > 0
|
|
156
|
+
if (tailwindDirectives.size > 0) {
|
|
157
157
|
let fileModifiedMap = getFileModifiedMap(context)
|
|
158
158
|
|
|
159
159
|
// Add template paths as postcss dependencies.
|
package/src/lib/sharedState.js
CHANGED
|
@@ -5,6 +5,7 @@ export const env = {
|
|
|
5
5
|
export const contextMap = new Map()
|
|
6
6
|
export const configContextMap = new Map()
|
|
7
7
|
export const contextSourcesMap = new Map()
|
|
8
|
+
export const sourceHashMap = new Map()
|
|
8
9
|
export const NOT_ON_DEMAND = new String('*')
|
|
9
10
|
|
|
10
11
|
export function resolveDebug(debug) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# tailwindcss/nesting
|
|
2
2
|
|
|
3
|
-
This is a PostCSS plugin that wraps [postcss-nested](https://github.com/postcss/postcss-nested) or [postcss-nesting](https://github.com/
|
|
3
|
+
This is a PostCSS plugin that wraps [postcss-nested](https://github.com/postcss/postcss-nested) or [postcss-nesting](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting) and acts as a compatibility layer to make sure your nesting plugin of choice properly understands Tailwind's custom syntax like `@apply` and `@screen`.
|
|
4
4
|
|
|
5
5
|
Add it to your PostCSS configuration, somewhere before Tailwind itself:
|
|
6
6
|
|
|
@@ -18,7 +18,7 @@ module.exports = {
|
|
|
18
18
|
|
|
19
19
|
By default, it uses the [postcss-nested](https://github.com/postcss/postcss-nested) plugin under the hood, which uses a Sass-like syntax and is the plugin that powers nesting support in the [Tailwind CSS plugin API](https://tailwindcss.com/docs/plugins#css-in-js-syntax).
|
|
20
20
|
|
|
21
|
-
If you'd rather use [postcss-nesting](https://github.com/
|
|
21
|
+
If you'd rather use [postcss-nesting](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting) (which is based on the work-in-progress [CSS Nesting](https://drafts.csswg.org/css-nesting-1/) specification), first install the plugin alongside:
|
|
22
22
|
|
|
23
23
|
```shell
|
|
24
24
|
npm install postcss-nesting
|
|
@@ -39,6 +39,42 @@ export function nesting(opts = postcssNested) {
|
|
|
39
39
|
decl.remove()
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Use a private PostCSS API to remove the "clean" flag from the entire AST.
|
|
44
|
+
* This is done because running process() on the AST will set the "clean"
|
|
45
|
+
* flag on all nodes, which we don't want.
|
|
46
|
+
*
|
|
47
|
+
* This causes downstream plugins using the visitor API to be skipped.
|
|
48
|
+
*
|
|
49
|
+
* This is guarded because the PostCSS API is not public
|
|
50
|
+
* and may change in future versions of PostCSS.
|
|
51
|
+
*
|
|
52
|
+
* See https://github.com/postcss/postcss/issues/1712 for more details
|
|
53
|
+
*
|
|
54
|
+
* @param {import('postcss').Node} node
|
|
55
|
+
*/
|
|
56
|
+
function markDirty(node) {
|
|
57
|
+
if (!('markDirty' in node)) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Traverse the tree down to the leaf nodes
|
|
62
|
+
if (node.nodes) {
|
|
63
|
+
node.nodes.forEach((n) => markDirty(n))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// If it's a leaf node mark it as dirty
|
|
67
|
+
// We do this here because marking a node as dirty
|
|
68
|
+
// will walk up the tree and mark all parents as dirty
|
|
69
|
+
// resulting in a lot of unnecessary work if we did this
|
|
70
|
+
// for every single node
|
|
71
|
+
if (!node.nodes) {
|
|
72
|
+
node.markDirty()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
markDirty(root)
|
|
77
|
+
|
|
42
78
|
return root
|
|
43
79
|
}
|
|
44
80
|
}
|