tailwindcss 3.0.15 → 3.0.19

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/src/cli.js CHANGED
@@ -437,7 +437,6 @@ async function build() {
437
437
 
438
438
  function resolveConfig() {
439
439
  let config = configPath ? require(configPath) : {}
440
- let resolvedConfig = resolveConfigInternal(config)
441
440
 
442
441
  if (args['--purge']) {
443
442
  log.warn('purge-flag-deprecated', [
@@ -450,10 +449,13 @@ async function build() {
450
449
  }
451
450
 
452
451
  if (args['--content']) {
453
- resolvedConfig.content.files = args['--content'].split(/(?<!{[^}]+),/)
452
+ let files = args['--content'].split(/(?<!{[^}]+),/)
453
+ let resolvedConfig = resolveConfigInternal(config, { content: { files } })
454
+ resolvedConfig.content.files = files
455
+ return resolvedConfig
454
456
  }
455
457
 
456
- return resolvedConfig
458
+ return resolveConfigInternal(config)
457
459
  }
458
460
 
459
461
  function extractFileGlobs(config) {
@@ -144,6 +144,7 @@ export let variantPlugins = {
144
144
  log.warn('darkmode-false', [
145
145
  'The `darkMode` option in your Tailwind CSS configuration is set to `false`, which now behaves the same as `media`.',
146
146
  'Change `darkMode` to `media` or remove it entirely.',
147
+ 'https://tailwindcss.com/docs/upgrade-guide#remove-dark-mode-configuration',
147
148
  ])
148
149
  }
149
150
 
@@ -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: theme('borderColor.DEFAULT', 'currentColor'); /* 2 */
12
+ border-color: theme('borderColor.DEFAULT', currentColor); /* 2 */
13
13
  }
14
14
 
15
15
  ::before,
@@ -247,7 +247,8 @@ export default function expandTailwindAtRules(context) {
247
247
 
248
248
  if (layerNodes.utilities && utilityNodes.size === 0 && !hasUtilityVariants) {
249
249
  log.warn('content-problems', [
250
- 'No utilities were generated there is likely a problem with the `content` key in the tailwind config. For more information see the documentation: https://tailwindcss.com/docs/content-configuration',
250
+ 'No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.',
251
+ 'https://tailwindcss.com/docs/content-configuration',
251
252
  ])
252
253
  }
253
254
 
@@ -25,33 +25,33 @@ function getClassNameFromSelector(selector) {
25
25
  // Example with dynamic classes:
26
26
  // ['grid-cols', '[[linename],1fr,auto]']
27
27
  // ['grid', 'cols-[[linename],1fr,auto]']
28
- function* candidatePermutations(candidate, lastIndex = Infinity) {
29
- if (lastIndex < 0) {
30
- return
31
- }
28
+ function* candidatePermutations(candidate) {
29
+ let lastIndex = Infinity
32
30
 
33
- let dashIdx
31
+ while (lastIndex >= 0) {
32
+ let dashIdx
34
33
 
35
- if (lastIndex === Infinity && candidate.endsWith(']')) {
36
- let bracketIdx = candidate.indexOf('[')
34
+ if (lastIndex === Infinity && candidate.endsWith(']')) {
35
+ let bracketIdx = candidate.indexOf('[')
37
36
 
38
- // If character before `[` isn't a dash or a slash, this isn't a dynamic class
39
- // eg. string[]
40
- dashIdx = ['-', '/'].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1
41
- } else {
42
- dashIdx = candidate.lastIndexOf('-', lastIndex)
43
- }
37
+ // If character before `[` isn't a dash or a slash, this isn't a dynamic class
38
+ // eg. string[]
39
+ dashIdx = ['-', '/'].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1
40
+ } else {
41
+ dashIdx = candidate.lastIndexOf('-', lastIndex)
42
+ }
44
43
 
45
- if (dashIdx < 0) {
46
- return
47
- }
44
+ if (dashIdx < 0) {
45
+ break
46
+ }
48
47
 
49
- let prefix = candidate.slice(0, dashIdx)
50
- let modifier = candidate.slice(dashIdx + 1)
48
+ let prefix = candidate.slice(0, dashIdx)
49
+ let modifier = candidate.slice(dashIdx + 1)
51
50
 
52
- yield [prefix, modifier]
51
+ yield [prefix, modifier]
53
52
 
54
- yield* candidatePermutations(candidate, dashIdx - 1)
53
+ lastIndex = dashIdx - 1
54
+ }
55
55
  }
56
56
 
57
57
  function applyPrefix(matches, context) {
@@ -63,9 +63,23 @@ function applyPrefix(matches, context) {
63
63
  let [meta] = match
64
64
  if (meta.options.respectPrefix) {
65
65
  let container = postcss.root({ nodes: [match[1].clone()] })
66
+ let classCandidate = match[1].raws.tailwind.classCandidate
67
+
66
68
  container.walkRules((r) => {
67
- r.selector = prefixSelector(context.tailwindConfig.prefix, r.selector)
69
+ // If this is a negative utility with a dash *before* the prefix we
70
+ // have to ensure that the generated selector matches the candidate
71
+
72
+ // Not doing this will cause `-tw-top-1` to generate the class `.tw--top-1`
73
+ // The disconnect between candidate <-> class can cause @apply to hard crash.
74
+ let shouldPrependNegative = classCandidate.startsWith('-')
75
+
76
+ r.selector = prefixSelector(
77
+ context.tailwindConfig.prefix,
78
+ r.selector,
79
+ shouldPrependNegative
80
+ )
68
81
  })
82
+
69
83
  match[1] = container.nodes[0]
70
84
  }
71
85
  }
@@ -265,7 +279,34 @@ function isValidPropName(name) {
265
279
  return IS_VALID_PROPERTY_NAME.test(name)
266
280
  }
267
281
 
282
+ /**
283
+ * @param {string} declaration
284
+ * @returns {boolean}
285
+ */
286
+ function looksLikeUri(declaration) {
287
+ // Quick bailout for obvious non-urls
288
+ // This doesn't support schemes that don't use a leading // but that's unlikely to be a problem
289
+ if (!declaration.includes('://')) {
290
+ return false
291
+ }
292
+
293
+ try {
294
+ const url = new URL(declaration)
295
+ return url.scheme !== '' && url.host !== ''
296
+ } catch (err) {
297
+ // Definitely not a valid url
298
+ return false
299
+ }
300
+ }
301
+
268
302
  function isParsableCssValue(property, value) {
303
+ // We don't want to to treat [https://example.com] as a custom property
304
+ // Even though, according to the CSS grammar, it's a totally valid CSS declaration
305
+ // So we short-circuit here by checking if the custom property looks like a url
306
+ if (looksLikeUri(`${property}:${value}`)) {
307
+ return false
308
+ }
309
+
269
310
  try {
270
311
  postcss.parse(`a{${property}:${value}}`).toResult()
271
312
  return true
@@ -344,6 +385,14 @@ function splitWithSeparator(input, separator) {
344
385
  return input.split(new RegExp(`\\${separator}(?![^[]*\\])`, 'g'))
345
386
  }
346
387
 
388
+ function* recordCandidates(matches, classCandidate) {
389
+ for (const match of matches) {
390
+ match[1].raws.tailwind = { classCandidate }
391
+
392
+ yield match
393
+ }
394
+ }
395
+
347
396
  function* resolveMatches(candidate, context) {
348
397
  let separator = context.tailwindConfig.separator
349
398
  let [classCandidate, ...variants] = splitWithSeparator(candidate, separator).reverse()
@@ -455,7 +504,9 @@ function* resolveMatches(candidate, context) {
455
504
  continue
456
505
  }
457
506
 
458
- matches = applyPrefix(matches.flat(), context)
507
+ matches = matches.flat()
508
+ matches = Array.from(recordCandidates(matches, classCandidate))
509
+ matches = applyPrefix(matches, context)
459
510
 
460
511
  if (important) {
461
512
  matches = applyImportant(matches, context)
@@ -49,6 +49,7 @@ export default function normalizeTailwindDirectives(root) {
49
49
  log.warn(`${atRule.name}-at-rule-deprecated`, [
50
50
  `The \`@${atRule.name}\` directive has been deprecated in Tailwind CSS v3.0.`,
51
51
  `Use \`@layer utilities\` or \`@layer components\` instead.`,
52
+ 'https://tailwindcss.com/docs/upgrade-guide#replace-variants-with-layer',
52
53
  ])
53
54
  }
54
55
  layerDirectives.add(atRule)
@@ -656,7 +656,7 @@ function registerPlugins(plugins, context) {
656
656
  log.warn('root-regex', [
657
657
  'Regular expressions in `safelist` work differently in Tailwind CSS v3.0.',
658
658
  'Update your `safelist` configuration to eliminate this warning.',
659
- // TODO: Add https://tw.wtf/regex-safelist
659
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
660
660
  ])
661
661
  continue
662
662
  }
@@ -666,17 +666,30 @@ function registerPlugins(plugins, context) {
666
666
 
667
667
  if (checks.length > 0) {
668
668
  let patternMatchingCount = new Map()
669
+ let prefixLength = context.tailwindConfig.prefix.length
669
670
 
670
671
  for (let util of classList) {
671
672
  let utils = Array.isArray(util)
672
673
  ? (() => {
673
674
  let [utilName, options] = util
674
- let classes = Object.keys(options?.values ?? {}).map((value) =>
675
- formatClass(utilName, value)
676
- )
675
+ let values = Object.keys(options?.values ?? {})
676
+ let classes = values.map((value) => formatClass(utilName, value))
677
677
 
678
678
  if (options?.supportsNegativeValues) {
679
+ // This is the normal negated version
680
+ // e.g. `-inset-1` or `-tw-inset-1`
679
681
  classes = [...classes, ...classes.map((cls) => '-' + cls)]
682
+
683
+ // This is the negated version *after* the prefix
684
+ // e.g. `tw--inset-1`
685
+ // The prefix is already attached to util name
686
+ // So we add the negative after the prefix
687
+ classes = [
688
+ ...classes,
689
+ ...classes.map(
690
+ (cls) => cls.slice(0, prefixLength) + '-' + cls.slice(prefixLength)
691
+ ),
692
+ ]
680
693
  }
681
694
 
682
695
  return classes
@@ -714,6 +727,7 @@ function registerPlugins(plugins, context) {
714
727
  log.warn([
715
728
  `The safelist pattern \`${regex}\` doesn't match any Tailwind CSS classes.`,
716
729
  'Fix this pattern or remove it from your `safelist` configuration.',
730
+ 'https://tailwindcss.com/docs/content-configuration#safelisting-classes',
717
731
  ])
718
732
  }
719
733
  }
@@ -0,0 +1,42 @@
1
+ # tailwindcss/nesting
2
+
3
+ This is a PostCSS plugin that wraps [postcss-nested](https://github.com/postcss/postcss-nested) or [postcss-nesting](https://github.com/jonathantneal/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
+
5
+ Add it to your PostCSS configuration, somewhere before Tailwind itself:
6
+
7
+ ```js
8
+ // postcss.config.js
9
+ module.exports = {
10
+ plugins: [
11
+ require('postcss-import'),
12
+ require('tailwindcss/nesting'),
13
+ require('tailwindcss'),
14
+ require('autoprefixer'),
15
+ ]
16
+ }
17
+ ```
18
+
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
+
21
+ If you'd rather use [postcss-nesting](https://github.com/jonathantneal/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
+
23
+ ```shell
24
+ npm install postcss-nesting
25
+ ```
26
+
27
+ Then pass the plugin itself as an argument to `tailwindcss/nesting` in your PostCSS configuration:
28
+
29
+ ```js
30
+ // postcss.config.js
31
+ module.exports = {
32
+ plugins: [
33
+ require('postcss-import'),
34
+ require('tailwindcss/nesting')(require('postcss-nesting')),
35
+ require('tailwindcss'),
36
+ require('autoprefixer'),
37
+ ]
38
+ }
39
+ ```
40
+
41
+ This can also be helpful if for whatever reason you need to use a very specific version of `postcss-nested` and want to override the version we bundle with `tailwindcss/nesting` itself.
42
+
@@ -0,0 +1,13 @@
1
+ import { nesting } from './plugin'
2
+
3
+ export default Object.assign(
4
+ function (opts) {
5
+ return {
6
+ postcssPlugin: 'tailwindcss/nesting',
7
+ Once(root, { result }) {
8
+ return nesting(opts)(root, result)
9
+ },
10
+ }
11
+ },
12
+ { postcss: true }
13
+ )
@@ -1,7 +1,7 @@
1
- let postcss = require('postcss')
2
- let postcssNested = require('postcss-nested')
1
+ import postcss from 'postcss'
2
+ import postcssNested from 'postcss-nested'
3
3
 
4
- module.exports = function nesting(opts = postcssNested) {
4
+ export function nesting(opts = postcssNested) {
5
5
  return (root, result) => {
6
6
  root.walkAtRules('screen', (rule) => {
7
7
  rule.name = 'media'
@@ -16,7 +16,7 @@ module.exports = function nesting(opts = postcssNested) {
16
16
  let plugin = (() => {
17
17
  if (
18
18
  typeof opts === 'function' ||
19
- (typeof opts === 'object' && opts.hasOwnProperty('postcssPlugin'))
19
+ (typeof opts === 'object' && opts?.hasOwnProperty?.('postcssPlugin'))
20
20
  ) {
21
21
  return opts
22
22
  }
@@ -16,6 +16,9 @@ export default function processTailwindFeatures(setupContext) {
16
16
  let { tailwindDirectives, applyDirectives } = normalizeTailwindDirectives(root)
17
17
 
18
18
  detectNesting()(root, result)
19
+
20
+ // Partition apply rules that are found in the css
21
+ // itself.
19
22
  partitionApplyAtRules()(root, result)
20
23
 
21
24
  let context = setupContext({
@@ -42,6 +45,9 @@ export default function processTailwindFeatures(setupContext) {
42
45
  issueFlagNotices(context.tailwindConfig)
43
46
 
44
47
  expandTailwindAtRules(context)(root, result)
48
+ // Partition apply rules that are generated by
49
+ // addComponents, addUtilities and so on.
50
+ partitionApplyAtRules()(root, result)
45
51
  expandApplyAtRules(context)(root, result)
46
52
  evaluateTailwindFunctions(context)(root, result)
47
53
  substituteScreenAtRules(context)(root, result)
@@ -124,7 +124,7 @@ export function normalizeConfig(config) {
124
124
  log.warn('purge-deprecation', [
125
125
  'The `purge`/`content` options have changed in Tailwind CSS v3.0.',
126
126
  'Update your configuration file to eliminate this warning.',
127
- // TODO: Add https://tw.wtf/purge-deprecation
127
+ 'https://tailwindcss.com/docs/upgrade-guide#configure-content-sources',
128
128
  ])
129
129
  }
130
130
 
@@ -145,7 +145,7 @@ export function normalizeConfig(config) {
145
145
  log.warn('prefix-function', [
146
146
  'As of Tailwind CSS v3.0, `prefix` cannot be a function.',
147
147
  'Update `prefix` in your configuration to be a string to eliminate this warning.',
148
- // TODO: Add https://tw.wtf/prefix-function
148
+ 'https://tailwindcss.com/docs/upgrade-guide#prefix-cannot-be-a-function',
149
149
  ])
150
150
  config.prefix = ''
151
151
  } else {
@@ -250,8 +250,8 @@ export function normalizeConfig(config) {
250
250
  for (let file of config.content.files) {
251
251
  if (typeof file === 'string' && /{([^,]*?)}/g.test(file)) {
252
252
  log.warn('invalid-glob-braces', [
253
- `The glob pattern ${dim(file)} in your config is invalid.`,
254
- ` Update it to ${dim(file.replace(/{([^,]*?)}/g, '$1'))} to silence this warning.`,
253
+ `The glob pattern ${dim(file)} in your Tailwind CSS configuration is invalid.`,
254
+ `Update it to ${dim(file.replace(/{([^,]*?)}/g, '$1'))} to silence this warning.`,
255
255
  // TODO: Add https://tw.wtf/invalid-glob-braces
256
256
  ])
257
257
  break
@@ -260,7 +260,9 @@ export function normalizeConfig(config) {
260
260
 
261
261
  if (config.content.files.length === 0) {
262
262
  log.warn('content-problems', [
263
- 'The `content` key is missing or empty. Please populate the content key as Tailwind generates utilities on-demand based on the files that use them. For more information see the documentation: https://tailwindcss.com/docs/content-configuration',
263
+ 'The `content` option in your Tailwind CSS configuration is missing or empty.',
264
+ 'Configure your content sources or your generated CSS will be missing styles.',
265
+ 'https://tailwindcss.com/docs/content-configuration',
264
266
  ])
265
267
  }
266
268
 
@@ -1,7 +1,7 @@
1
1
  let KEYWORDS = new Set(['inset', 'inherit', 'initial', 'revert', 'unset'])
2
2
  let COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count.
3
3
  let SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead.
4
- let LENGTH = /^-?(\d+)(.*?)$/g
4
+ let LENGTH = /^-?(\d+|\.\d+)(.*?)$/g
5
5
 
6
6
  export function parseBoxShadowValue(input) {
7
7
  let shadows = input.split(COMMA)
@@ -1,12 +1,14 @@
1
1
  import parser from 'postcss-selector-parser'
2
- import { tap } from './tap'
3
2
 
4
- export default function (prefix, selector) {
3
+ export default function (prefix, selector, prependNegative = false) {
5
4
  return parser((selectors) => {
6
5
  selectors.walkClasses((classSelector) => {
7
- tap(classSelector.value, (baseClass) => {
8
- classSelector.value = `${prefix}${baseClass}`
9
- })
6
+ let baseClass = classSelector.value
7
+ let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-')
8
+
9
+ classSelector.value = shouldPlaceNegativeBeforePrefix
10
+ ? `-${prefix}${baseClass.slice(1)}`
11
+ : `${prefix}${baseClass}`
10
12
  })
11
13
  }).processSync(selector)
12
14
  }