tailwindcss 0.0.0-insiders.df011dc → 0.0.0-insiders.e2d5f21
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 +106 -1
- package/README.md +1 -1
- package/colors.js +1 -304
- package/defaultConfig.js +1 -4
- package/defaultTheme.js +1 -4
- package/lib/cli-peer-dependencies.js +4 -9
- package/lib/cli.js +646 -705
- package/lib/constants.js +15 -15
- package/lib/corePluginList.js +174 -4
- package/lib/corePlugins.js +3637 -2934
- package/lib/css/preflight.css +14 -1
- package/lib/featureFlags.js +44 -44
- package/lib/index.js +25 -24
- package/lib/lib/collapseAdjacentRules.js +33 -35
- package/lib/lib/collapseDuplicateDeclarations.js +29 -0
- package/lib/lib/detectNesting.js +34 -0
- package/lib/lib/evaluateTailwindFunctions.js +134 -155
- package/lib/lib/expandApplyAtRules.js +183 -200
- package/lib/lib/expandTailwindAtRules.js +219 -232
- package/lib/lib/generateRules.js +478 -311
- package/lib/lib/getModuleDependencies.js +36 -41
- package/lib/lib/normalizeTailwindDirectives.js +66 -58
- package/lib/lib/resolveDefaultsAtRules.js +110 -90
- package/lib/lib/setupContextUtils.js +740 -580
- package/lib/lib/setupTrackingContext.js +142 -162
- package/lib/lib/setupWatchingContext.js +235 -278
- package/lib/lib/sharedState.js +7 -17
- package/lib/lib/substituteScreenAtRules.js +22 -26
- package/lib/processTailwindFeatures.js +38 -44
- package/lib/public/colors.js +329 -0
- package/lib/public/create-plugin.js +13 -0
- package/lib/public/default-config.js +14 -0
- package/lib/public/default-theme.js +14 -0
- package/lib/public/resolve-config.js +19 -0
- package/lib/util/bigSign.js +3 -5
- package/lib/util/buildMediaQuery.js +15 -31
- package/lib/util/cloneDeep.js +14 -13
- package/lib/util/cloneNodes.js +9 -13
- package/lib/util/color.js +69 -59
- package/lib/util/configurePlugins.js +13 -14
- package/lib/util/createPlugin.js +21 -24
- package/lib/util/createUtilityPlugin.js +47 -45
- package/lib/util/dataTypes.js +231 -0
- package/lib/util/defaults.js +10 -14
- package/lib/util/escapeClassName.js +12 -14
- package/lib/util/escapeCommas.js +3 -5
- package/lib/util/flattenColorPalette.js +14 -10
- package/lib/util/formatVariantSelector.js +186 -0
- package/lib/util/getAllConfigs.js +23 -16
- package/lib/util/hashConfig.js +10 -10
- package/lib/util/isKeyframeRule.js +3 -5
- package/lib/util/isPlainObject.js +7 -10
- package/lib/util/isValidArbitraryValue.js +64 -0
- package/lib/util/log.js +39 -30
- package/lib/util/nameClass.js +23 -23
- package/lib/util/negateValue.js +14 -15
- package/lib/util/normalizeConfig.js +208 -0
- package/lib/util/normalizeScreens.js +58 -0
- package/lib/util/parseAnimationValue.js +82 -53
- package/lib/util/parseBoxShadowValue.js +77 -0
- package/lib/util/parseDependency.js +49 -60
- package/lib/util/parseObjectStyles.js +23 -20
- package/lib/util/pluginUtils.js +153 -285
- package/lib/util/prefixSelector.js +14 -16
- package/lib/util/resolveConfig.js +194 -249
- package/lib/util/resolveConfigPath.js +38 -45
- package/lib/util/responsive.js +12 -11
- package/lib/util/tap.js +4 -6
- package/lib/util/toColorValue.js +4 -5
- package/lib/util/toPath.js +4 -6
- package/lib/util/transformThemeValue.js +57 -26
- package/lib/util/withAlphaVariable.js +56 -54
- package/package.json +27 -37
- package/peers/index.js +73099 -84294
- package/plugin.js +1 -3
- package/resolveConfig.js +1 -7
- package/scripts/create-plugin-list.js +2 -2
- package/src/cli.js +27 -18
- package/src/corePluginList.js +1 -1
- package/src/corePlugins.js +2124 -1968
- package/src/css/preflight.css +14 -1
- package/src/featureFlags.js +12 -6
- package/src/lib/collapseDuplicateDeclarations.js +28 -0
- package/src/lib/detectNesting.js +39 -0
- package/src/lib/evaluateTailwindFunctions.js +5 -2
- package/src/lib/expandTailwindAtRules.js +24 -32
- package/src/lib/generateRules.js +230 -20
- package/src/lib/normalizeTailwindDirectives.js +3 -2
- package/src/lib/resolveDefaultsAtRules.js +46 -30
- package/src/lib/setupContextUtils.js +275 -113
- package/src/lib/setupTrackingContext.js +9 -8
- package/src/lib/setupWatchingContext.js +12 -7
- package/src/lib/sharedState.js +1 -4
- package/src/lib/substituteScreenAtRules.js +6 -3
- package/src/processTailwindFeatures.js +8 -1
- package/src/public/colors.js +300 -0
- package/src/public/create-plugin.js +2 -0
- package/src/public/default-config.js +4 -0
- package/src/public/default-theme.js +4 -0
- package/src/public/resolve-config.js +7 -0
- package/src/util/buildMediaQuery.js +14 -18
- package/src/util/createUtilityPlugin.js +2 -11
- package/src/util/dataTypes.js +246 -0
- package/src/util/formatVariantSelector.js +196 -0
- package/src/util/isValidArbitraryValue.js +61 -0
- package/src/util/log.js +18 -21
- package/src/util/nameClass.js +10 -6
- package/src/util/negateValue.js +4 -2
- package/src/util/normalizeConfig.js +249 -0
- package/src/util/normalizeScreens.js +42 -0
- package/src/util/parseAnimationValue.js +7 -1
- package/src/util/parseBoxShadowValue.js +71 -0
- package/src/util/parseDependency.js +4 -0
- package/src/util/pluginUtils.js +101 -204
- package/src/util/prefixSelector.js +1 -4
- package/src/util/resolveConfig.js +12 -65
- package/src/util/transformThemeValue.js +22 -7
- package/src/util/withAlphaVariable.js +13 -8
- package/stubs/defaultConfig.stub.js +163 -97
- package/stubs/simpleConfig.stub.js +0 -1
package/src/css/preflight.css
CHANGED
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
border-color: currentColor; /* 2 */
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
::before,
|
|
16
|
+
::after {
|
|
17
|
+
--tw-content: '';
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
/*
|
|
16
21
|
1. Use a consistent sensible line-height in all browsers.
|
|
17
22
|
2. Prevent adjustments of font size after orientation changes in iOS.
|
|
@@ -283,7 +288,8 @@ legend {
|
|
|
283
288
|
}
|
|
284
289
|
|
|
285
290
|
ol,
|
|
286
|
-
ul
|
|
291
|
+
ul,
|
|
292
|
+
menu {
|
|
287
293
|
list-style: none;
|
|
288
294
|
margin: 0;
|
|
289
295
|
padding: 0;
|
|
@@ -317,6 +323,13 @@ button,
|
|
|
317
323
|
cursor: pointer;
|
|
318
324
|
}
|
|
319
325
|
|
|
326
|
+
/*
|
|
327
|
+
Make sure disabled buttons don't get the pointer cursor.
|
|
328
|
+
*/
|
|
329
|
+
:disabled {
|
|
330
|
+
cursor: default;
|
|
331
|
+
}
|
|
332
|
+
|
|
320
333
|
/*
|
|
321
334
|
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
322
335
|
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
package/src/featureFlags.js
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
import log from './util/log'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
let defaults = {
|
|
5
|
+
optimizeUniversalDefaults: true,
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let featureFlags = {
|
|
5
9
|
future: [],
|
|
6
10
|
experimental: ['optimizeUniversalDefaults'],
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
export function flagEnabled(config, flag) {
|
|
10
14
|
if (featureFlags.future.includes(flag)) {
|
|
11
|
-
return config.future === 'all' || (config?.future?.[flag] ?? false)
|
|
15
|
+
return config.future === 'all' || (config?.future?.[flag] ?? defaults[flag] ?? false)
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
if (featureFlags.experimental.includes(flag)) {
|
|
15
|
-
return
|
|
19
|
+
return (
|
|
20
|
+
config.experimental === 'all' || (config?.experimental?.[flag] ?? defaults[flag] ?? false)
|
|
21
|
+
)
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
return false
|
|
@@ -34,13 +40,13 @@ export function issueFlagNotices(config) {
|
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
if (experimentalFlagsEnabled(config).length > 0) {
|
|
37
|
-
|
|
43
|
+
let changes = experimentalFlagsEnabled(config)
|
|
38
44
|
.map((s) => chalk.yellow(s))
|
|
39
45
|
.join(', ')
|
|
40
46
|
|
|
41
|
-
log.warn([
|
|
47
|
+
log.warn('experimental-flags-enabled', [
|
|
42
48
|
`You have enabled experimental features: ${changes}`,
|
|
43
|
-
'Experimental features are not covered by semver, may introduce breaking changes, and can change at any time.',
|
|
49
|
+
'Experimental features in Tailwind CSS are not covered by semver, may introduce breaking changes, and can change at any time.',
|
|
44
50
|
])
|
|
45
51
|
}
|
|
46
52
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export default function collapseDuplicateDeclarations() {
|
|
2
|
+
return (root) => {
|
|
3
|
+
root.walkRules((node) => {
|
|
4
|
+
let seen = new Map()
|
|
5
|
+
let droppable = new Set([])
|
|
6
|
+
|
|
7
|
+
node.walkDecls((decl) => {
|
|
8
|
+
// This could happen if we have nested selectors. In that case the
|
|
9
|
+
// parent will loop over all its declarations but also the declarations
|
|
10
|
+
// of nested rules. With this we ensure that we are shallowly checking
|
|
11
|
+
// declarations.
|
|
12
|
+
if (decl.parent !== node) {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (seen.has(decl.prop)) {
|
|
17
|
+
droppable.add(seen.get(decl.prop))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
seen.set(decl.prop, decl)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
for (let decl of droppable) {
|
|
24
|
+
decl.remove()
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export default function (_context) {
|
|
2
|
+
return (root, result) => {
|
|
3
|
+
let found = false
|
|
4
|
+
|
|
5
|
+
root.walkAtRules('tailwind', (node) => {
|
|
6
|
+
if (found) return false
|
|
7
|
+
|
|
8
|
+
if (node.parent && node.parent.type !== 'root') {
|
|
9
|
+
found = true
|
|
10
|
+
node.warn(
|
|
11
|
+
result,
|
|
12
|
+
[
|
|
13
|
+
'Nested @tailwind rules were detected, but are not supported.',
|
|
14
|
+
"Consider using a prefix to scope Tailwind's classes: https://tailwindcss.com/docs/configuration#prefix",
|
|
15
|
+
'Alternatively, use the important selector strategy: https://tailwindcss.com/docs/configuration#selector-strategy',
|
|
16
|
+
].join('\n')
|
|
17
|
+
)
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
root.walkRules((rule) => {
|
|
23
|
+
if (found) return false
|
|
24
|
+
|
|
25
|
+
rule.walkRules((nestedRule) => {
|
|
26
|
+
found = true
|
|
27
|
+
nestedRule.warn(
|
|
28
|
+
result,
|
|
29
|
+
[
|
|
30
|
+
'Nested CSS was detected, but CSS nesting has not been configured correctly.',
|
|
31
|
+
'Please enable a CSS nesting plugin *before* Tailwind in your configuration.',
|
|
32
|
+
'See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting',
|
|
33
|
+
].join('\n')
|
|
34
|
+
)
|
|
35
|
+
return false
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -2,6 +2,7 @@ import dlv from 'dlv'
|
|
|
2
2
|
import didYouMean from 'didyoumean'
|
|
3
3
|
import transformThemeValue from '../util/transformThemeValue'
|
|
4
4
|
import parseValue from 'postcss-value-parser'
|
|
5
|
+
import { normalizeScreens } from '../util/normalizeScreens'
|
|
5
6
|
import buildMediaQuery from '../util/buildMediaQuery'
|
|
6
7
|
import { toPath } from '../util/toPath'
|
|
7
8
|
|
|
@@ -173,12 +174,14 @@ export default function ({ tailwindConfig: config }) {
|
|
|
173
174
|
},
|
|
174
175
|
screen: (node, screen) => {
|
|
175
176
|
screen = screen.replace(/^['"]+/g, '').replace(/['"]+$/g, '')
|
|
177
|
+
let screens = normalizeScreens(config.theme.screens)
|
|
178
|
+
let screenDefinition = screens.find(({ name }) => name === screen)
|
|
176
179
|
|
|
177
|
-
if (
|
|
180
|
+
if (!screenDefinition) {
|
|
178
181
|
throw node.error(`The '${screen}' screen does not exist in your theme.`)
|
|
179
182
|
}
|
|
180
183
|
|
|
181
|
-
return buildMediaQuery(
|
|
184
|
+
return buildMediaQuery(screenDefinition)
|
|
182
185
|
},
|
|
183
186
|
}
|
|
184
187
|
return (root) => {
|
|
@@ -1,16 +1,24 @@
|
|
|
1
|
+
import LRU from 'quick-lru'
|
|
1
2
|
import * as sharedState from './sharedState'
|
|
2
3
|
import { generateRules } from './generateRules'
|
|
3
4
|
import bigSign from '../util/bigSign'
|
|
4
5
|
import cloneNodes from '../util/cloneNodes'
|
|
5
6
|
|
|
6
7
|
let env = sharedState.env
|
|
7
|
-
let contentMatchCache = sharedState.contentMatchCache
|
|
8
8
|
|
|
9
9
|
const PATTERNS = [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
/([^<>"'`\s]*\[\w*'[^"`\s]*'?\])/.source, // font-['some_font',sans-serif]
|
|
11
|
+
/([^<>"'`\s]*\[\w*"[^"`\s]*"?\])/.source, // font-["some_font",sans-serif]
|
|
12
|
+
/([^<>"'`\s]*\[\w*\('[^"'`\s]*'\)\])/.source, // bg-[url('...')]
|
|
13
|
+
/([^<>"'`\s]*\[\w*\("[^"'`\s]*"\)\])/.source, // bg-[url("...")]
|
|
14
|
+
/([^<>"'`\s]*\[\w*\('[^"`\s]*'\)\])/.source, // bg-[url('...'),url('...')]
|
|
15
|
+
/([^<>"'`\s]*\[\w*\("[^'`\s]*"\)\])/.source, // bg-[url("..."),url("...")]
|
|
16
|
+
/([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']`
|
|
17
|
+
/([^<>"'`\s]*\["[^"'`\s]*"\])/.source, // `content-["hello"]` but not `content-["hello"]"]`
|
|
18
|
+
/([^<>"'`\s]*\[[^<>"'`\s]*:'[^"'`\s]*'\])/.source, // `[content:'hello']` but not `[content:"hello"]`
|
|
19
|
+
/([^<>"'`\s]*\[[^<>"'`\s]*:"[^"'`\s]*"\])/.source, // `[content:"hello"]` but not `[content:'hello']`
|
|
20
|
+
/([^<>"'`\s]*\[[^"'`\s]+\][^<>"'`\s]*)/.source, // `fill-[#bada55]`, `fill-[#bada55]/50`
|
|
21
|
+
/([^<>"'`\s]*[^"'`\s:])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
|
|
14
22
|
].join('|')
|
|
15
23
|
const BROAD_MATCH_GLOBAL_REGEXP = new RegExp(PATTERNS, 'g')
|
|
16
24
|
const INNER_MATCH_GLOBAL_REGEXP = /[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g
|
|
@@ -31,21 +39,6 @@ const builtInTransformers = {
|
|
|
31
39
|
|
|
32
40
|
function getExtractor(tailwindConfig, fileExtension) {
|
|
33
41
|
let extractors = tailwindConfig.content.extract
|
|
34
|
-
let contentOptions = tailwindConfig.content.options
|
|
35
|
-
|
|
36
|
-
if (typeof extractors === 'function') {
|
|
37
|
-
extractors = {
|
|
38
|
-
DEFAULT: extractors,
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (contentOptions.defaultExtractor) {
|
|
42
|
-
extractors.DEFAULT = contentOptions.defaultExtractor
|
|
43
|
-
}
|
|
44
|
-
for (let { extensions, extractor } of contentOptions.extractors || []) {
|
|
45
|
-
for (let extension of extensions) {
|
|
46
|
-
extractors[extension] = extractor
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
42
|
|
|
50
43
|
return (
|
|
51
44
|
extractors[fileExtension] ||
|
|
@@ -58,12 +51,6 @@ function getExtractor(tailwindConfig, fileExtension) {
|
|
|
58
51
|
function getTransformer(tailwindConfig, fileExtension) {
|
|
59
52
|
let transformers = tailwindConfig.content.transform
|
|
60
53
|
|
|
61
|
-
if (typeof transformers === 'function') {
|
|
62
|
-
transformers = {
|
|
63
|
-
DEFAULT: transformers,
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
54
|
return (
|
|
68
55
|
transformers[fileExtension] ||
|
|
69
56
|
transformers.DEFAULT ||
|
|
@@ -72,10 +59,16 @@ function getTransformer(tailwindConfig, fileExtension) {
|
|
|
72
59
|
)
|
|
73
60
|
}
|
|
74
61
|
|
|
62
|
+
let extractorCache = new WeakMap()
|
|
63
|
+
|
|
75
64
|
// Scans template contents for possible classes. This is a hot path on initial build but
|
|
76
65
|
// not too important for subsequent builds. The faster the better though — if we can speed
|
|
77
66
|
// up these regexes by 50% that could cut initial build time by like 20%.
|
|
78
|
-
function getClassCandidates(content, extractor,
|
|
67
|
+
function getClassCandidates(content, extractor, candidates, seen) {
|
|
68
|
+
if (!extractorCache.has(extractor)) {
|
|
69
|
+
extractorCache.set(extractor, new LRU({ maxSize: 25000 }))
|
|
70
|
+
}
|
|
71
|
+
|
|
79
72
|
for (let line of content.split('\n')) {
|
|
80
73
|
line = line.trim()
|
|
81
74
|
|
|
@@ -84,8 +77,8 @@ function getClassCandidates(content, extractor, contentMatchCache, candidates, s
|
|
|
84
77
|
}
|
|
85
78
|
seen.add(line)
|
|
86
79
|
|
|
87
|
-
if (
|
|
88
|
-
for (let match of
|
|
80
|
+
if (extractorCache.get(extractor).has(line)) {
|
|
81
|
+
for (let match of extractorCache.get(extractor).get(line)) {
|
|
89
82
|
candidates.add(match)
|
|
90
83
|
}
|
|
91
84
|
} else {
|
|
@@ -96,7 +89,7 @@ function getClassCandidates(content, extractor, contentMatchCache, candidates, s
|
|
|
96
89
|
candidates.add(match)
|
|
97
90
|
}
|
|
98
91
|
|
|
99
|
-
|
|
92
|
+
extractorCache.get(extractor).set(line, lineMatchesSet)
|
|
100
93
|
}
|
|
101
94
|
}
|
|
102
95
|
}
|
|
@@ -185,7 +178,7 @@ export default function expandTailwindAtRules(context) {
|
|
|
185
178
|
for (let { content, extension } of context.changedContent) {
|
|
186
179
|
let transformer = getTransformer(context.tailwindConfig, extension)
|
|
187
180
|
let extractor = getExtractor(context.tailwindConfig, extension)
|
|
188
|
-
getClassCandidates(transformer(content), extractor,
|
|
181
|
+
getClassCandidates(transformer(content), extractor, candidates, seen)
|
|
189
182
|
}
|
|
190
183
|
|
|
191
184
|
// ---
|
|
@@ -246,7 +239,6 @@ export default function expandTailwindAtRules(context) {
|
|
|
246
239
|
if (env.DEBUG) {
|
|
247
240
|
console.log('Potential classes: ', candidates.size)
|
|
248
241
|
console.log('Active contexts: ', sharedState.contextSourcesMap.size)
|
|
249
|
-
console.log('Content match entries', contentMatchCache.size)
|
|
250
242
|
}
|
|
251
243
|
|
|
252
244
|
// Clear the cache for the changed files
|
package/src/lib/generateRules.js
CHANGED
|
@@ -4,6 +4,11 @@ import parseObjectStyles from '../util/parseObjectStyles'
|
|
|
4
4
|
import isPlainObject from '../util/isPlainObject'
|
|
5
5
|
import prefixSelector from '../util/prefixSelector'
|
|
6
6
|
import { updateAllClasses } from '../util/pluginUtils'
|
|
7
|
+
import log from '../util/log'
|
|
8
|
+
import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
|
|
9
|
+
import { asClass } from '../util/nameClass'
|
|
10
|
+
import { normalize } from '../util/dataTypes'
|
|
11
|
+
import isValidArbitraryValue from '../util/isValidArbitraryValue'
|
|
7
12
|
|
|
8
13
|
let classNameParser = selectorParser((selectors) => {
|
|
9
14
|
return selectors.first.filter(({ type }) => type === 'class').pop().value
|
|
@@ -107,16 +112,21 @@ function applyVariant(variant, matches, context) {
|
|
|
107
112
|
let result = []
|
|
108
113
|
|
|
109
114
|
for (let [meta, rule] of matches) {
|
|
110
|
-
if (meta.options.respectVariants === false) {
|
|
111
|
-
result.push([meta, rule])
|
|
112
|
-
continue
|
|
113
|
-
}
|
|
114
|
-
|
|
115
115
|
let container = postcss.root({ nodes: [rule.clone()] })
|
|
116
116
|
|
|
117
117
|
for (let [variantSort, variantFunction] of variantFunctionTuples) {
|
|
118
118
|
let clone = container.clone()
|
|
119
|
+
let collectedFormats = []
|
|
120
|
+
|
|
121
|
+
let originals = new Map()
|
|
122
|
+
|
|
123
|
+
function prepareBackup() {
|
|
124
|
+
if (originals.size > 0) return // Already prepared, chicken out
|
|
125
|
+
clone.walkRules((rule) => originals.set(rule, rule.selector))
|
|
126
|
+
}
|
|
127
|
+
|
|
119
128
|
function modifySelectors(modifierFunction) {
|
|
129
|
+
prepareBackup()
|
|
120
130
|
clone.each((rule) => {
|
|
121
131
|
if (rule.type !== 'rule') {
|
|
122
132
|
return
|
|
@@ -131,20 +141,84 @@ function applyVariant(variant, matches, context) {
|
|
|
131
141
|
})
|
|
132
142
|
})
|
|
133
143
|
})
|
|
144
|
+
|
|
134
145
|
return clone
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
let ruleWithVariant = variantFunction({
|
|
138
|
-
|
|
149
|
+
// Public API
|
|
150
|
+
get container() {
|
|
151
|
+
prepareBackup()
|
|
152
|
+
return clone
|
|
153
|
+
},
|
|
139
154
|
separator: context.tailwindConfig.separator,
|
|
140
155
|
modifySelectors,
|
|
156
|
+
|
|
157
|
+
// Private API for now
|
|
158
|
+
wrap(wrapper) {
|
|
159
|
+
let nodes = clone.nodes
|
|
160
|
+
clone.removeAll()
|
|
161
|
+
wrapper.append(nodes)
|
|
162
|
+
clone.append(wrapper)
|
|
163
|
+
},
|
|
164
|
+
format(selectorFormat) {
|
|
165
|
+
collectedFormats.push(selectorFormat)
|
|
166
|
+
},
|
|
141
167
|
})
|
|
142
168
|
|
|
169
|
+
if (typeof ruleWithVariant === 'string') {
|
|
170
|
+
collectedFormats.push(ruleWithVariant)
|
|
171
|
+
}
|
|
172
|
+
|
|
143
173
|
if (ruleWithVariant === null) {
|
|
144
174
|
continue
|
|
145
175
|
}
|
|
146
176
|
|
|
147
|
-
|
|
177
|
+
// We filled the `originals`, therefore we assume that somebody touched
|
|
178
|
+
// `container` or `modifySelectors`. Let's see if they did, so that we
|
|
179
|
+
// can restore the selectors, and collect the format strings.
|
|
180
|
+
if (originals.size > 0) {
|
|
181
|
+
clone.walkRules((rule) => {
|
|
182
|
+
if (!originals.has(rule)) return
|
|
183
|
+
let before = originals.get(rule)
|
|
184
|
+
if (before === rule.selector) return // No mutation happened
|
|
185
|
+
|
|
186
|
+
let modified = rule.selector
|
|
187
|
+
|
|
188
|
+
// Rebuild the base selector, this is what plugin authors would do
|
|
189
|
+
// as well. E.g.: `${variant}${separator}${className}`.
|
|
190
|
+
// However, plugin authors probably also prepend or append certain
|
|
191
|
+
// classes, pseudos, ids, ...
|
|
192
|
+
let rebuiltBase = selectorParser((selectors) => {
|
|
193
|
+
selectors.walkClasses((classNode) => {
|
|
194
|
+
classNode.value = `${variant}${context.tailwindConfig.separator}${classNode.value}`
|
|
195
|
+
})
|
|
196
|
+
}).processSync(before)
|
|
197
|
+
|
|
198
|
+
// Now that we know the original selector, the new selector, and
|
|
199
|
+
// the rebuild part in between, we can replace the part that plugin
|
|
200
|
+
// authors need to rebuild with `&`, and eventually store it in the
|
|
201
|
+
// collectedFormats. Similar to what `format('...')` would do.
|
|
202
|
+
//
|
|
203
|
+
// E.g.:
|
|
204
|
+
// variant: foo
|
|
205
|
+
// selector: .markdown > p
|
|
206
|
+
// modified (by plugin): .foo .foo\\:markdown > p
|
|
207
|
+
// rebuiltBase (internal): .foo\\:markdown > p
|
|
208
|
+
// format: .foo &
|
|
209
|
+
collectedFormats.push(modified.replace(rebuiltBase, '&'))
|
|
210
|
+
rule.selector = before
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let withOffset = [
|
|
215
|
+
{
|
|
216
|
+
...meta,
|
|
217
|
+
sort: variantSort | meta.sort,
|
|
218
|
+
collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
|
|
219
|
+
},
|
|
220
|
+
clone.nodes[0],
|
|
221
|
+
]
|
|
148
222
|
result.push(withOffset)
|
|
149
223
|
}
|
|
150
224
|
}
|
|
@@ -174,21 +248,57 @@ function parseRules(rule, cache, options = {}) {
|
|
|
174
248
|
return [cache.get(rule), options]
|
|
175
249
|
}
|
|
176
250
|
|
|
251
|
+
function extractArbitraryProperty(classCandidate, context) {
|
|
252
|
+
let [, property, value] = classCandidate.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/) ?? []
|
|
253
|
+
|
|
254
|
+
if (value === undefined) {
|
|
255
|
+
return null
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let normalized = normalize(value)
|
|
259
|
+
|
|
260
|
+
if (!isValidArbitraryValue(normalized)) {
|
|
261
|
+
return null
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return [
|
|
265
|
+
[
|
|
266
|
+
{ sort: context.arbitraryPropertiesSort, layer: 'utilities' },
|
|
267
|
+
() => ({
|
|
268
|
+
[asClass(classCandidate)]: {
|
|
269
|
+
[property]: normalized,
|
|
270
|
+
},
|
|
271
|
+
}),
|
|
272
|
+
],
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
|
|
177
276
|
function* resolveMatchedPlugins(classCandidate, context) {
|
|
178
277
|
if (context.candidateRuleMap.has(classCandidate)) {
|
|
179
278
|
yield [context.candidateRuleMap.get(classCandidate), 'DEFAULT']
|
|
180
279
|
}
|
|
181
280
|
|
|
281
|
+
yield* (function* (arbitraryPropertyRule) {
|
|
282
|
+
if (arbitraryPropertyRule !== null) {
|
|
283
|
+
yield [arbitraryPropertyRule, 'DEFAULT']
|
|
284
|
+
}
|
|
285
|
+
})(extractArbitraryProperty(classCandidate, context))
|
|
286
|
+
|
|
182
287
|
let candidatePrefix = classCandidate
|
|
183
288
|
let negative = false
|
|
184
289
|
|
|
185
|
-
const twConfigPrefix = context.tailwindConfig.prefix
|
|
290
|
+
const twConfigPrefix = context.tailwindConfig.prefix
|
|
291
|
+
|
|
186
292
|
const twConfigPrefixLen = twConfigPrefix.length
|
|
187
293
|
if (candidatePrefix[twConfigPrefixLen] === '-') {
|
|
188
294
|
negative = true
|
|
189
295
|
candidatePrefix = twConfigPrefix + candidatePrefix.slice(twConfigPrefixLen + 1)
|
|
190
296
|
}
|
|
191
297
|
|
|
298
|
+
if (negative && context.candidateRuleMap.has(candidatePrefix)) {
|
|
299
|
+
yield [context.candidateRuleMap.get(candidatePrefix), '-DEFAULT']
|
|
300
|
+
}
|
|
301
|
+
|
|
192
302
|
for (let [prefix, modifier] of candidatePermutations(candidatePrefix)) {
|
|
193
303
|
if (context.candidateRuleMap.has(prefix)) {
|
|
194
304
|
yield [context.candidateRuleMap.get(prefix), negative ? `-${modifier}` : modifier]
|
|
@@ -225,28 +335,94 @@ function* resolveMatches(candidate, context) {
|
|
|
225
335
|
|
|
226
336
|
for (let matchedPlugins of resolveMatchedPlugins(classCandidate, context)) {
|
|
227
337
|
let matches = []
|
|
338
|
+
let typesByMatches = new Map()
|
|
339
|
+
|
|
228
340
|
let [plugins, modifier] = matchedPlugins
|
|
341
|
+
let isOnlyPlugin = plugins.length === 1
|
|
229
342
|
|
|
230
343
|
for (let [sort, plugin] of plugins) {
|
|
344
|
+
let matchesPerPlugin = []
|
|
345
|
+
|
|
231
346
|
if (typeof plugin === 'function') {
|
|
232
|
-
for (let ruleSet of [].concat(plugin(modifier))) {
|
|
347
|
+
for (let ruleSet of [].concat(plugin(modifier, { isOnlyPlugin }))) {
|
|
233
348
|
let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
|
|
234
349
|
for (let rule of rules) {
|
|
235
|
-
|
|
350
|
+
matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
|
|
236
351
|
}
|
|
237
352
|
}
|
|
238
353
|
}
|
|
239
354
|
// Only process static plugins on exact matches
|
|
240
|
-
else if (modifier === 'DEFAULT') {
|
|
355
|
+
else if (modifier === 'DEFAULT' || modifier === '-DEFAULT') {
|
|
241
356
|
let ruleSet = plugin
|
|
242
357
|
let [rules, options] = parseRules(ruleSet, context.postCssNodeCache)
|
|
243
358
|
for (let rule of rules) {
|
|
244
|
-
|
|
359
|
+
matchesPerPlugin.push([{ ...sort, options: { ...sort.options, ...options } }, rule])
|
|
245
360
|
}
|
|
246
361
|
}
|
|
362
|
+
|
|
363
|
+
if (matchesPerPlugin.length > 0) {
|
|
364
|
+
typesByMatches.set(matchesPerPlugin, sort.options?.type)
|
|
365
|
+
matches.push(matchesPerPlugin)
|
|
366
|
+
}
|
|
247
367
|
}
|
|
248
368
|
|
|
249
|
-
|
|
369
|
+
// Only keep the result of the very first plugin if we are dealing with
|
|
370
|
+
// arbitrary values, to protect against ambiguity.
|
|
371
|
+
if (isArbitraryValue(modifier) && matches.length > 1) {
|
|
372
|
+
let typesPerPlugin = matches.map((match) => new Set([...(typesByMatches.get(match) ?? [])]))
|
|
373
|
+
|
|
374
|
+
// Remove duplicates, so that we can detect proper unique types for each plugin.
|
|
375
|
+
for (let pluginTypes of typesPerPlugin) {
|
|
376
|
+
for (let type of pluginTypes) {
|
|
377
|
+
let removeFromOwnGroup = false
|
|
378
|
+
|
|
379
|
+
for (let otherGroup of typesPerPlugin) {
|
|
380
|
+
if (pluginTypes === otherGroup) continue
|
|
381
|
+
|
|
382
|
+
if (otherGroup.has(type)) {
|
|
383
|
+
otherGroup.delete(type)
|
|
384
|
+
removeFromOwnGroup = true
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (removeFromOwnGroup) pluginTypes.delete(type)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let messages = []
|
|
393
|
+
|
|
394
|
+
for (let [idx, group] of typesPerPlugin.entries()) {
|
|
395
|
+
for (let type of group) {
|
|
396
|
+
let rules = matches[idx]
|
|
397
|
+
.map(([, rule]) => rule)
|
|
398
|
+
.flat()
|
|
399
|
+
.map((rule) =>
|
|
400
|
+
rule
|
|
401
|
+
.toString()
|
|
402
|
+
.split('\n')
|
|
403
|
+
.slice(1, -1) // Remove selector and closing '}'
|
|
404
|
+
.map((line) => line.trim())
|
|
405
|
+
.map((x) => ` ${x}`) // Re-indent
|
|
406
|
+
.join('\n')
|
|
407
|
+
)
|
|
408
|
+
.join('\n\n')
|
|
409
|
+
|
|
410
|
+
messages.push(` Use \`${candidate.replace('[', `[${type}:`)}\` for \`${rules.trim()}\``)
|
|
411
|
+
break
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
log.warn([
|
|
416
|
+
`The class \`${candidate}\` is ambiguous and matches multiple utilities.`,
|
|
417
|
+
...messages,
|
|
418
|
+
`If this is content and not a class, replace it with \`${candidate
|
|
419
|
+
.replace('[', '[')
|
|
420
|
+
.replace(']', ']')}\` to silence this warning.`,
|
|
421
|
+
])
|
|
422
|
+
continue
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
matches = applyPrefix(matches.flat(), context)
|
|
250
426
|
|
|
251
427
|
if (important) {
|
|
252
428
|
matches = applyImportant(matches, context)
|
|
@@ -257,6 +433,22 @@ function* resolveMatches(candidate, context) {
|
|
|
257
433
|
}
|
|
258
434
|
|
|
259
435
|
for (let match of matches) {
|
|
436
|
+
// Apply final format selector
|
|
437
|
+
if (match[0].collectedFormats) {
|
|
438
|
+
let finalFormat = formatVariantSelector('&', ...match[0].collectedFormats)
|
|
439
|
+
let container = postcss.root({ nodes: [match[1].clone()] })
|
|
440
|
+
container.walkRules((rule) => {
|
|
441
|
+
if (inKeyframes(rule)) return
|
|
442
|
+
|
|
443
|
+
rule.selector = finalizeSelector(finalFormat, {
|
|
444
|
+
selector: rule.selector,
|
|
445
|
+
candidate,
|
|
446
|
+
context,
|
|
447
|
+
})
|
|
448
|
+
})
|
|
449
|
+
match[1] = container.nodes[0]
|
|
450
|
+
}
|
|
451
|
+
|
|
260
452
|
yield match
|
|
261
453
|
}
|
|
262
454
|
}
|
|
@@ -290,30 +482,48 @@ function generateRules(candidates, context) {
|
|
|
290
482
|
allRules.push(matches)
|
|
291
483
|
}
|
|
292
484
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
485
|
+
// Strategy based on `tailwindConfig.important`
|
|
486
|
+
let strategy = ((important) => {
|
|
487
|
+
if (important === true) {
|
|
488
|
+
return (rule) => {
|
|
296
489
|
rule.walkDecls((d) => {
|
|
297
490
|
if (d.parent.type === 'rule' && !inKeyframes(d.parent)) {
|
|
298
491
|
d.important = true
|
|
299
492
|
}
|
|
300
493
|
})
|
|
301
|
-
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (typeof important === 'string') {
|
|
498
|
+
return (rule) => {
|
|
499
|
+
rule.selectors = rule.selectors.map((selector) => {
|
|
500
|
+
return `${important} ${selector}`
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
})(context.tailwindConfig.important)
|
|
505
|
+
|
|
506
|
+
return allRules.flat(1).map(([{ sort, layer, options }, rule]) => {
|
|
507
|
+
if (options.respectImportant) {
|
|
508
|
+
if (strategy) {
|
|
302
509
|
let container = postcss.root({ nodes: [rule.clone()] })
|
|
303
510
|
container.walkRules((r) => {
|
|
304
511
|
if (inKeyframes(r)) {
|
|
305
512
|
return
|
|
306
513
|
}
|
|
307
514
|
|
|
308
|
-
r
|
|
309
|
-
return `${context.tailwindConfig.important} ${selector}`
|
|
310
|
-
})
|
|
515
|
+
strategy(r)
|
|
311
516
|
})
|
|
312
517
|
rule = container.nodes[0]
|
|
313
518
|
}
|
|
314
519
|
}
|
|
520
|
+
|
|
315
521
|
return [sort | context.layerOrder[layer], rule]
|
|
316
522
|
})
|
|
317
523
|
}
|
|
318
524
|
|
|
525
|
+
function isArbitraryValue(input) {
|
|
526
|
+
return input.startsWith('[') && input.endsWith(']')
|
|
527
|
+
}
|
|
528
|
+
|
|
319
529
|
export { resolveMatches, generateRules }
|
|
@@ -41,8 +41,9 @@ export default function normalizeTailwindDirectives(root) {
|
|
|
41
41
|
|
|
42
42
|
if (['layer', 'responsive', 'variants'].includes(atRule.name)) {
|
|
43
43
|
if (['responsive', 'variants'].includes(atRule.name)) {
|
|
44
|
-
log.warn([
|
|
45
|
-
`
|
|
44
|
+
log.warn(`${atRule.name}-at-rule-deprecated`, [
|
|
45
|
+
`The \`@${atRule.name}\` directive has been deprecated in Tailwind CSS v3.0.`,
|
|
46
|
+
`Use \`@layer utilities\` or \`@layer components\` instead.`,
|
|
46
47
|
])
|
|
47
48
|
}
|
|
48
49
|
layerDirectives.add(atRule)
|