tailwindcss 3.0.9 → 3.0.13

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/lib/cli.js +57 -28
  3. package/lib/corePlugins.js +183 -280
  4. package/lib/css/preflight.css +1 -1
  5. package/lib/featureFlags.js +2 -5
  6. package/lib/lib/expandApplyAtRules.js +0 -40
  7. package/lib/lib/expandTailwindAtRules.js +14 -29
  8. package/lib/lib/generateRules.js +1 -2
  9. package/lib/lib/normalizeTailwindDirectives.js +8 -1
  10. package/lib/lib/partitionApplyAtRules.js +53 -0
  11. package/lib/lib/resolveDefaultsAtRules.js +4 -4
  12. package/lib/lib/setupContextUtils.js +48 -52
  13. package/lib/lib/setupTrackingContext.js +11 -10
  14. package/lib/processTailwindFeatures.js +5 -2
  15. package/lib/util/createPlugin.js +1 -2
  16. package/lib/util/createUtilityPlugin.js +4 -8
  17. package/lib/util/flattenColorPalette.js +1 -3
  18. package/lib/util/normalizeConfig.js +6 -12
  19. package/lib/util/normalizeScreens.js +2 -4
  20. package/lib/util/pluginUtils.js +6 -13
  21. package/lib/util/resolveConfig.js +9 -18
  22. package/lib/util/resolveConfigPath.js +1 -2
  23. package/lib/util/toColorValue.js +1 -2
  24. package/lib/util/transformThemeValue.js +4 -8
  25. package/package.json +8 -10
  26. package/peers/index.js +652 -651
  27. package/src/cli.js +57 -12
  28. package/src/corePlugins.js +121 -155
  29. package/src/css/preflight.css +1 -1
  30. package/src/featureFlags.js +1 -5
  31. package/src/lib/expandApplyAtRules.js +0 -42
  32. package/src/lib/expandTailwindAtRules.js +12 -21
  33. package/src/lib/normalizeTailwindDirectives.js +6 -1
  34. package/src/lib/partitionApplyAtRules.js +52 -0
  35. package/src/lib/resolveDefaultsAtRules.js +5 -5
  36. package/src/lib/setupContextUtils.js +37 -17
  37. package/src/lib/setupTrackingContext.js +11 -10
  38. package/src/processTailwindFeatures.js +6 -2
  39. package/src/util/pluginUtils.js +1 -1
@@ -9,7 +9,7 @@
9
9
  box-sizing: border-box; /* 1 */
10
10
  border-width: 0; /* 2 */
11
11
  border-style: solid; /* 2 */
12
- border-color: currentColor; /* 2 */
12
+ border-color: theme('borderColor.DEFAULT', 'currentColor'); /* 2 */
13
13
  }
14
14
 
15
15
  ::before,
@@ -2,11 +2,7 @@ import chalk from 'chalk'
2
2
  import log from './util/log'
3
3
 
4
4
  let defaults = {
5
- // TODO: Drop this once we can safely rely on optimizeUniversalDefaults being
6
- // the default.
7
- optimizeUniversalDefaults: process.env.NODE_ENV === 'test' ? true : false,
8
-
9
- // optimizeUniversalDefaults: true
5
+ optimizeUniversalDefaults: false,
10
6
  }
11
7
 
12
8
  let featureFlags = {
@@ -72,47 +72,6 @@ function extractApplyCandidates(params) {
72
72
  return [candidates, false]
73
73
  }
74
74
 
75
- function partitionApplyParents(root) {
76
- let applyParents = new Set()
77
-
78
- root.walkAtRules('apply', (rule) => {
79
- applyParents.add(rule.parent)
80
- })
81
-
82
- for (let rule of applyParents) {
83
- let nodeGroups = []
84
- let lastGroup = []
85
-
86
- for (let node of rule.nodes) {
87
- if (node.type === 'atrule' && node.name === 'apply') {
88
- if (lastGroup.length > 0) {
89
- nodeGroups.push(lastGroup)
90
- lastGroup = []
91
- }
92
- nodeGroups.push([node])
93
- } else {
94
- lastGroup.push(node)
95
- }
96
- }
97
-
98
- if (lastGroup.length > 0) {
99
- nodeGroups.push(lastGroup)
100
- }
101
-
102
- if (nodeGroups.length === 1) {
103
- continue
104
- }
105
-
106
- for (let group of [...nodeGroups].reverse()) {
107
- let newParent = rule.clone({ nodes: [] })
108
- newParent.append(group)
109
- rule.after(newParent)
110
- }
111
-
112
- rule.remove()
113
- }
114
- }
115
-
116
75
  function processApply(root, context) {
117
76
  let applyCandidates = new Set()
118
77
 
@@ -343,7 +302,6 @@ function processApply(root, context) {
343
302
 
344
303
  export default function expandApplyAtRules(context) {
345
304
  return (root) => {
346
- partitionApplyParents(root)
347
305
  processApply(root, context)
348
306
  }
349
307
  }
@@ -129,8 +129,6 @@ function buildStylesheet(rules, context) {
129
129
  return returnValue
130
130
  }
131
131
 
132
- export const DEFAULTS_LAYER = Symbol('defaults-layer')
133
-
134
132
  export default function expandTailwindAtRules(context) {
135
133
  return (root) => {
136
134
  let layerNodes = {
@@ -140,13 +138,15 @@ export default function expandTailwindAtRules(context) {
140
138
  variants: null,
141
139
  }
142
140
 
143
- // Make sure this file contains Tailwind directives. If not, we can save
144
- // a lot of work and bail early. Also we don't have to register our touch
145
- // file as a dependency since the output of this CSS does not depend on
146
- // the source of any templates. Think Vue <style> blocks for example.
147
- root.walkAtRules('tailwind', (rule) => {
148
- if (Object.keys(layerNodes).includes(rule.params)) {
149
- layerNodes[rule.params] = rule
141
+ root.walkAtRules((rule) => {
142
+ // Make sure this file contains Tailwind directives. If not, we can save
143
+ // a lot of work and bail early. Also we don't have to register our touch
144
+ // file as a dependency since the output of this CSS does not depend on
145
+ // the source of any templates. Think Vue <style> blocks for example.
146
+ if (rule.name === 'tailwind') {
147
+ if (Object.keys(layerNodes).includes(rule.params)) {
148
+ layerNodes[rule.params] = rule
149
+ }
150
150
  }
151
151
  })
152
152
 
@@ -168,6 +168,8 @@ export default function expandTailwindAtRules(context) {
168
168
  getClassCandidates(transformer(content), extractor, candidates, seen)
169
169
  }
170
170
 
171
+ env.DEBUG && console.timeEnd('Reading changed files')
172
+
171
173
  // ---
172
174
 
173
175
  // Generate the actual CSS
@@ -201,18 +203,7 @@ export default function expandTailwindAtRules(context) {
201
203
  // Replace any Tailwind directives with generated CSS
202
204
 
203
205
  if (layerNodes.base) {
204
- layerNodes.base.before(cloneNodes([...baseNodes], layerNodes.base.source))
205
- }
206
-
207
- // @defaults rules are unconditionally added first to ensure that
208
- // using any utility that relies on defaults will work even when
209
- // compiled in an isolated environment like CSS modules
210
- if (context.tailwindConfig[DEFAULTS_LAYER] !== false) {
211
- if (layerNodes.base) {
212
- layerNodes.base.after(cloneNodes([...defaultNodes], root.source))
213
- } else {
214
- root.prepend(cloneNodes([...defaultNodes], root.source))
215
- }
206
+ layerNodes.base.before(cloneNodes([...baseNodes, ...defaultNodes], layerNodes.base.source))
216
207
  }
217
208
 
218
209
  if (layerNodes.base) {
@@ -3,8 +3,13 @@ import log from '../util/log'
3
3
  export default function normalizeTailwindDirectives(root) {
4
4
  let tailwindDirectives = new Set()
5
5
  let layerDirectives = new Set()
6
+ let applyDirectives = new Set()
6
7
 
7
8
  root.walkAtRules((atRule) => {
9
+ if (atRule.name === 'apply') {
10
+ applyDirectives.add(atRule)
11
+ }
12
+
8
13
  if (atRule.name === 'import') {
9
14
  if (atRule.params === '"tailwindcss/base"' || atRule.params === "'tailwindcss/base'") {
10
15
  atRule.name = 'tailwind'
@@ -74,5 +79,5 @@ export default function normalizeTailwindDirectives(root) {
74
79
  }
75
80
  }
76
81
 
77
- return tailwindDirectives
82
+ return { tailwindDirectives, applyDirectives }
78
83
  }
@@ -0,0 +1,52 @@
1
+ function partitionRules(root) {
2
+ if (!root.walkAtRules) return
3
+
4
+ let applyParents = new Set()
5
+
6
+ root.walkAtRules('apply', (rule) => {
7
+ applyParents.add(rule.parent)
8
+ })
9
+
10
+ if (applyParents.size === 0) {
11
+ return
12
+ }
13
+
14
+ for (let rule of applyParents) {
15
+ let nodeGroups = []
16
+ let lastGroup = []
17
+
18
+ for (let node of rule.nodes) {
19
+ if (node.type === 'atrule' && node.name === 'apply') {
20
+ if (lastGroup.length > 0) {
21
+ nodeGroups.push(lastGroup)
22
+ lastGroup = []
23
+ }
24
+ nodeGroups.push([node])
25
+ } else {
26
+ lastGroup.push(node)
27
+ }
28
+ }
29
+
30
+ if (lastGroup.length > 0) {
31
+ nodeGroups.push(lastGroup)
32
+ }
33
+
34
+ if (nodeGroups.length === 1) {
35
+ continue
36
+ }
37
+
38
+ for (let group of [...nodeGroups].reverse()) {
39
+ let clone = rule.clone({ nodes: [] })
40
+ clone.append(group)
41
+ rule.after(clone)
42
+ }
43
+
44
+ rule.remove()
45
+ }
46
+ }
47
+
48
+ export default function expandApplyAtRules() {
49
+ return (root) => {
50
+ partitionRules(root)
51
+ }
52
+ }
@@ -113,12 +113,12 @@ export default function resolveDefaultsAtRules({ tailwindConfig }) {
113
113
  }
114
114
  }
115
115
 
116
- if (selectorGroups.size === 0) {
117
- universal.remove()
118
- continue
119
- }
120
-
121
116
  if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) {
117
+ if (selectorGroups.size === 0) {
118
+ universal.remove()
119
+ continue
120
+ }
121
+
122
122
  for (let [, selectors] of selectorGroups) {
123
123
  let universalRule = postcss.rule()
124
124
 
@@ -89,39 +89,55 @@ function getClasses(selector) {
89
89
  return parser.transformSync(selector)
90
90
  }
91
91
 
92
- function extractCandidates(node) {
92
+ function extractCandidates(node, state = { containsNonOnDemandable: false }, depth = 0) {
93
93
  let classes = []
94
94
 
95
+ // Handle normal rules
95
96
  if (node.type === 'rule') {
96
97
  for (let selector of node.selectors) {
97
98
  let classCandidates = getClasses(selector)
98
99
  // At least one of the selectors contains non-"on-demandable" candidates.
99
- if (classCandidates.length === 0) return []
100
+ if (classCandidates.length === 0) {
101
+ state.containsNonOnDemandable = true
102
+ }
100
103
 
101
- classes = [...classes, ...classCandidates]
104
+ for (let classCandidate of classCandidates) {
105
+ classes.push(classCandidate)
106
+ }
102
107
  }
103
- return classes
104
108
  }
105
109
 
106
- if (node.type === 'atrule') {
110
+ // Handle at-rules (which contains nested rules)
111
+ else if (node.type === 'atrule') {
107
112
  node.walkRules((rule) => {
108
- classes = [...classes, ...rule.selectors.flatMap((selector) => getClasses(selector))]
113
+ for (let classCandidate of rule.selectors.flatMap((selector) =>
114
+ getClasses(selector, state, depth + 1)
115
+ )) {
116
+ classes.push(classCandidate)
117
+ }
109
118
  })
110
119
  }
111
120
 
121
+ if (depth === 0) {
122
+ return [state.containsNonOnDemandable || classes.length === 0, classes]
123
+ }
124
+
112
125
  return classes
113
126
  }
114
127
 
115
128
  function withIdentifiers(styles) {
116
129
  return parseStyles(styles).flatMap((node) => {
117
130
  let nodeMap = new Map()
118
- let candidates = extractCandidates(node)
131
+ let [containsNonOnDemandableSelectors, candidates] = extractCandidates(node)
119
132
 
120
- // If this isn't "on-demandable", assign it a universal candidate.
121
- if (candidates.length === 0) {
122
- return [['*', node]]
133
+ // If this isn't "on-demandable", assign it a universal candidate to always include it.
134
+ if (containsNonOnDemandableSelectors) {
135
+ candidates.unshift('*')
123
136
  }
124
137
 
138
+ // However, it could be that it also contains "on-demandable" candidates.
139
+ // E.g.: `span, .foo {}`, in that case it should still be possible to use
140
+ // `@apply foo` for example.
125
141
  return candidates.map((c) => {
126
142
  if (!nodeMap.has(node)) {
127
143
  nodeMap.set(node, node)
@@ -244,7 +260,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
244
260
 
245
261
  for (let [identifier, rule] of withIdentifiers(groups)) {
246
262
  let prefixedIdentifier = prefixIdentifier(identifier, {})
247
- let offset = offsets.base++
248
263
 
249
264
  if (!context.candidateRuleMap.has(prefixedIdentifier)) {
250
265
  context.candidateRuleMap.set(prefixedIdentifier, [])
@@ -252,7 +267,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
252
267
 
253
268
  context.candidateRuleMap
254
269
  .get(prefixedIdentifier)
255
- .push([{ sort: offset, layer: 'defaults' }, rule])
270
+ .push([{ sort: offsets.base++, layer: 'defaults' }, rule])
256
271
  }
257
272
  },
258
273
  addComponents(components, options) {
@@ -265,7 +280,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
265
280
 
266
281
  for (let [identifier, rule] of withIdentifiers(components)) {
267
282
  let prefixedIdentifier = prefixIdentifier(identifier, options)
268
- let offset = offsets.components++
269
283
 
270
284
  classList.add(prefixedIdentifier)
271
285
 
@@ -275,7 +289,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
275
289
 
276
290
  context.candidateRuleMap
277
291
  .get(prefixedIdentifier)
278
- .push([{ sort: offset, layer: 'components', options }, rule])
292
+ .push([{ sort: offsets.components++, layer: 'components', options }, rule])
279
293
  }
280
294
  },
281
295
  addUtilities(utilities, options) {
@@ -288,7 +302,6 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
288
302
 
289
303
  for (let [identifier, rule] of withIdentifiers(utilities)) {
290
304
  let prefixedIdentifier = prefixIdentifier(identifier, options)
291
- let offset = offsets.utilities++
292
305
 
293
306
  classList.add(prefixedIdentifier)
294
307
 
@@ -298,7 +311,7 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
298
311
 
299
312
  context.candidateRuleMap
300
313
  .get(prefixedIdentifier)
301
- .push([{ sort: offset, layer: 'utilities', options }, rule])
314
+ .push([{ sort: offsets.utilities++, layer: 'utilities', options }, rule])
302
315
  }
303
316
  },
304
317
  matchUtilities: function (utilities, options) {
@@ -435,7 +448,14 @@ function trackModified(files, fileModifiedMap) {
435
448
  let parsed = url.parse(file)
436
449
  let pathname = parsed.hash ? parsed.href.replace(parsed.hash, '') : parsed.href
437
450
  pathname = parsed.search ? pathname.replace(parsed.search, '') : pathname
438
- let newModified = fs.statSync(decodeURIComponent(pathname)).mtimeMs
451
+ let newModified = fs.statSync(decodeURIComponent(pathname), { throwIfNoEntry: false })?.mtimeMs
452
+ if (!newModified) {
453
+ // It could happen that a file is passed in that doesn't exist. E.g.:
454
+ // postcss-cli will provide you a fake path when reading from stdin. This
455
+ // path then looks like /path-to-your-project/stdin In that case we just
456
+ // want to ignore it and don't track changes at all.
457
+ continue
458
+ }
439
459
 
440
460
  if (!fileModifiedMap.has(file) || newModified > fileModifiedMap.get(file)) {
441
461
  changed = true
@@ -112,19 +112,20 @@ 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, applyDirectives }) => {
116
116
  return (root, result) => {
117
117
  let [tailwindConfig, userConfigPath, tailwindConfigHash, configDependencies] =
118
118
  getTailwindConfig(configOrPath)
119
119
 
120
120
  let contextDependencies = new Set(configDependencies)
121
121
 
122
- // If there are no @tailwind rules, we don't consider this CSS file or it's dependencies
123
- // to be dependencies of the context. Can reuse the context even if they change.
124
- // We may want to think about `@layer` being part of this trigger too, but it's tough
125
- // because it's impossible for a layer in one file to end up in the actual @tailwind rule
126
- // in another file since independent sources are effectively isolated.
127
- if (tailwindDirectives.size > 0) {
122
+ // If there are no @tailwind or @apply rules, we don't consider this CSS
123
+ // file or its dependencies to be dependencies of the context. Can reuse
124
+ // the context even if they change. We may want to think about `@layer`
125
+ // being part of this trigger too, but it's tough because it's impossible
126
+ // for a layer in one file to end up in the actual @tailwind rule in
127
+ // another file since independent sources are effectively isolated.
128
+ if (tailwindDirectives.size > 0 || applyDirectives.size > 0) {
128
129
  // Add current css file as a context dependencies.
129
130
  contextDependencies.add(result.opts.from)
130
131
 
@@ -147,12 +148,12 @@ export default function setupTrackingContext(configOrPath) {
147
148
 
148
149
  let candidateFiles = getCandidateFiles(context, tailwindConfig)
149
150
 
150
- // If there are no @tailwind rules, we don't consider this CSS file or it's dependencies
151
- // to be dependencies of the context. Can reuse the context even if they change.
151
+ // If there are no @tailwind or @apply rules, we don't consider this CSS file or it's
152
+ // dependencies to be dependencies of the context. Can reuse the context even if they change.
152
153
  // We may want to think about `@layer` being part of this trigger too, but it's tough
153
154
  // because it's impossible for a layer in one file to end up in the actual @tailwind rule
154
155
  // in another file since independent sources are effectively isolated.
155
- if (tailwindDirectives.size > 0) {
156
+ if (tailwindDirectives.size > 0 || applyDirectives.size > 0) {
156
157
  let fileModifiedMap = getFileModifiedMap(context)
157
158
 
158
159
  // Add template paths as postcss dependencies.
@@ -6,16 +6,21 @@ import substituteScreenAtRules from './lib/substituteScreenAtRules'
6
6
  import resolveDefaultsAtRules from './lib/resolveDefaultsAtRules'
7
7
  import collapseAdjacentRules from './lib/collapseAdjacentRules'
8
8
  import collapseDuplicateDeclarations from './lib/collapseDuplicateDeclarations'
9
+ import partitionApplyAtRules from './lib/partitionApplyAtRules'
9
10
  import detectNesting from './lib/detectNesting'
10
11
  import { createContext } from './lib/setupContextUtils'
11
12
  import { issueFlagNotices } from './featureFlags'
12
13
 
13
14
  export default function processTailwindFeatures(setupContext) {
14
15
  return function (root, result) {
15
- let tailwindDirectives = normalizeTailwindDirectives(root)
16
+ let { tailwindDirectives, applyDirectives } = normalizeTailwindDirectives(root)
17
+
18
+ detectNesting()(root, result)
19
+ partitionApplyAtRules()(root, result)
16
20
 
17
21
  let context = setupContext({
18
22
  tailwindDirectives,
23
+ applyDirectives,
19
24
  registerDependency(dependency) {
20
25
  result.messages.push({
21
26
  plugin: 'tailwindcss',
@@ -36,7 +41,6 @@ export default function processTailwindFeatures(setupContext) {
36
41
 
37
42
  issueFlagNotices(context.tailwindConfig)
38
43
 
39
- detectNesting(context)(root, result)
40
44
  expandTailwindAtRules(context)(root, result)
41
45
  expandApplyAtRules(context)(root, result)
42
46
  evaluateTailwindFunctions(context)(root, result)
@@ -185,7 +185,7 @@ export function coerceValue(types, modifier, options, tailwindConfig) {
185
185
  // Find first matching type
186
186
  for (let type of [].concat(types)) {
187
187
  let result = typeMap[type](modifier, options, { tailwindConfig })
188
- if (result) return [result, type]
188
+ if (result !== undefined) return [result, type]
189
189
  }
190
190
 
191
191
  return []