tailwindcss 3.4.6 → 3.4.8

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.
@@ -12,7 +12,7 @@ import { loadAutoprefixer, loadCssNano, loadPostcss, loadPostcssImport } from '.
12
12
  import { formatNodes, drainStdin, outputFile } from './utils'
13
13
  import { env } from '../../lib/sharedState'
14
14
  import resolveConfig from '../../../resolveConfig.js'
15
- import { parseCandidateFiles } from '../../lib/content.js'
15
+ import { createBroadPatternCheck, parseCandidateFiles } from '../../lib/content.js'
16
16
  import { createWatcher } from './watching.js'
17
17
  import fastGlob from 'fast-glob'
18
18
  import { findAtConfigPath } from '../../lib/findAtConfigPath.js'
@@ -184,7 +184,11 @@ let state = {
184
184
  // TODO: When we make the postcss plugin async-capable this can become async
185
185
  let files = fastGlob.sync(this.contentPatterns.all)
186
186
 
187
+ let checkBroadPattern = createBroadPatternCheck(this.contentPatterns.all)
188
+
187
189
  for (let file of files) {
190
+ checkBroadPattern(file)
191
+
188
192
  content.push({
189
193
  content: fs.readFileSync(path.resolve(file), 'utf8'),
190
194
  extension: path.extname(file).slice(1),
@@ -318,7 +322,7 @@ export async function createProcessor(args, cliConfigPath) {
318
322
  return fs.promises.readFile(path.resolve(input), 'utf8')
319
323
  }
320
324
 
321
- // No input file provided, fallback to default atrules
325
+ // No input file provided, fallback to default at-rules
322
326
  return '@tailwind base; @tailwind components; @tailwind utilities'
323
327
  }
324
328
 
@@ -21,7 +21,7 @@ import {
21
21
  import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadowValue'
22
22
  import { removeAlphaVariables } from './util/removeAlphaVariables'
23
23
  import { flagEnabled } from './featureFlags'
24
- import { normalize } from './util/dataTypes'
24
+ import { normalize, normalizeAttributeSelectors } from './util/dataTypes'
25
25
  import { INTERNAL_FEATURES } from './lib/setupContextUtils'
26
26
 
27
27
  export let variantPlugins = {
@@ -472,41 +472,45 @@ export let variantPlugins = {
472
472
  },
473
473
 
474
474
  ariaVariants: ({ matchVariant, theme }) => {
475
- matchVariant('aria', (value) => `&[aria-${normalize(value)}]`, { values: theme('aria') ?? {} })
475
+ matchVariant('aria', (value) => `&[aria-${normalizeAttributeSelectors(normalize(value))}]`, {
476
+ values: theme('aria') ?? {},
477
+ })
476
478
  matchVariant(
477
479
  'group-aria',
478
480
  (value, { modifier }) =>
479
481
  modifier
480
- ? `:merge(.group\\/${modifier})[aria-${normalize(value)}] &`
481
- : `:merge(.group)[aria-${normalize(value)}] &`,
482
+ ? `:merge(.group\\/${modifier})[aria-${normalizeAttributeSelectors(normalize(value))}] &`
483
+ : `:merge(.group)[aria-${normalizeAttributeSelectors(normalize(value))}] &`,
482
484
  { values: theme('aria') ?? {} }
483
485
  )
484
486
  matchVariant(
485
487
  'peer-aria',
486
488
  (value, { modifier }) =>
487
489
  modifier
488
- ? `:merge(.peer\\/${modifier})[aria-${normalize(value)}] ~ &`
489
- : `:merge(.peer)[aria-${normalize(value)}] ~ &`,
490
+ ? `:merge(.peer\\/${modifier})[aria-${normalizeAttributeSelectors(normalize(value))}] ~ &`
491
+ : `:merge(.peer)[aria-${normalizeAttributeSelectors(normalize(value))}] ~ &`,
490
492
  { values: theme('aria') ?? {} }
491
493
  )
492
494
  },
493
495
 
494
496
  dataVariants: ({ matchVariant, theme }) => {
495
- matchVariant('data', (value) => `&[data-${normalize(value)}]`, { values: theme('data') ?? {} })
497
+ matchVariant('data', (value) => `&[data-${normalizeAttributeSelectors(normalize(value))}]`, {
498
+ values: theme('data') ?? {},
499
+ })
496
500
  matchVariant(
497
501
  'group-data',
498
502
  (value, { modifier }) =>
499
503
  modifier
500
- ? `:merge(.group\\/${modifier})[data-${normalize(value)}] &`
501
- : `:merge(.group)[data-${normalize(value)}] &`,
504
+ ? `:merge(.group\\/${modifier})[data-${normalizeAttributeSelectors(normalize(value))}] &`
505
+ : `:merge(.group)[data-${normalizeAttributeSelectors(normalize(value))}] &`,
502
506
  { values: theme('data') ?? {} }
503
507
  )
504
508
  matchVariant(
505
509
  'peer-data',
506
510
  (value, { modifier }) =>
507
511
  modifier
508
- ? `:merge(.peer\\/${modifier})[data-${normalize(value)}] ~ &`
509
- : `:merge(.peer)[data-${normalize(value)}] ~ &`,
512
+ ? `:merge(.peer\\/${modifier})[data-${normalizeAttributeSelectors(normalize(value))}] ~ &`
513
+ : `:merge(.peer)[data-${normalizeAttributeSelectors(normalize(value))}] ~ &`,
510
514
  { values: theme('data') ?? {} }
511
515
  )
512
516
  },
@@ -7,6 +7,8 @@ import fastGlob from 'fast-glob'
7
7
  import normalizePath from 'normalize-path'
8
8
  import { parseGlob } from '../util/parseGlob'
9
9
  import { env } from './sharedState'
10
+ import log from '../util/log'
11
+ import micromatch from 'micromatch'
10
12
 
11
13
  /** @typedef {import('../../types/config.js').RawFile} RawFile */
12
14
  /** @typedef {import('../../types/config.js').FilePath} FilePath */
@@ -181,6 +183,74 @@ export function resolvedChangedContent(context, candidateFiles, fileModifiedMap)
181
183
  return [changedContent, mTimesToCommit]
182
184
  }
183
185
 
186
+ const LARGE_DIRECTORIES = [
187
+ 'node_modules', // Node
188
+ 'vendor', // PHP
189
+ ]
190
+
191
+ // Ensures that `node_modules` has to match as-is, otherwise `mynode_modules`
192
+ // would match as well, but that is not a known large directory.
193
+ const LARGE_DIRECTORIES_REGEX = new RegExp(
194
+ `(${LARGE_DIRECTORIES.map((dir) => String.raw`\b${dir}\b`).join('|')})`
195
+ )
196
+
197
+ /**
198
+ * @param {string[]} paths
199
+ */
200
+ export function createBroadPatternCheck(paths) {
201
+ // Detect whether a glob pattern might be too broad. This means that it:
202
+ // - Includes `**`
203
+ // - Does not include any of the known large directories (e.g.: node_modules)
204
+ let maybeBroadPattern = paths.some(
205
+ (path) => path.includes('**') && !LARGE_DIRECTORIES_REGEX.test(path)
206
+ )
207
+
208
+ // Didn't detect any potentially broad patterns, so we can skip further
209
+ // checks.
210
+ if (!maybeBroadPattern) {
211
+ return () => {}
212
+ }
213
+
214
+ // All globs that explicitly contain any of the known large directories (e.g.:
215
+ // node_modules).
216
+ let explicitGlobs = paths.filter((path) => LARGE_DIRECTORIES_REGEX.test(path))
217
+
218
+ // Keep track of whether we already warned about the broad pattern issue or
219
+ // not. The `log.warn` function already does something similar where we only
220
+ // output the log once. However, with this we can also skip the other checks
221
+ // when we already warned about the broad pattern.
222
+ let warned = false
223
+
224
+ /**
225
+ * @param {string} file
226
+ */
227
+ return (file) => {
228
+ if (warned) return // Already warned about the broad pattern
229
+ if (micromatch.isMatch(file, explicitGlobs)) return // Explicitly included, so we can skip further checks
230
+
231
+ // When a broad pattern is used, we have to double check that the file was
232
+ // not explicitly included in the globs.
233
+ let matchingGlob = paths.find((path) => micromatch.isMatch(file, path))
234
+ if (!matchingGlob) return // This should never happen
235
+
236
+ // Create relative paths to make the output a bit more readable.
237
+ let relativeMatchingGlob = path.relative(process.cwd(), matchingGlob)
238
+ if (relativeMatchingGlob[0] !== '.') relativeMatchingGlob = `./${relativeMatchingGlob}`
239
+
240
+ let largeDirectory = LARGE_DIRECTORIES.find((directory) => file.includes(directory))
241
+ if (largeDirectory) {
242
+ warned = true
243
+
244
+ log.warn('broad-content-glob-pattern', [
245
+ `Your \`content\` configuration includes a pattern which looks like it's accidentally matching all of \`${largeDirectory}\` and can cause serious performance issues.`,
246
+ `Pattern: \`${relativeMatchingGlob}\``,
247
+ `See our documentation for recommendations:`,
248
+ 'https://tailwindcss.com/docs/content-configuration#pattern-recommendations',
249
+ ])
250
+ }
251
+ }
252
+ }
253
+
184
254
  /**
185
255
  *
186
256
  * @param {ContentPath[]} candidateFiles
@@ -191,10 +261,14 @@ function resolveChangedFiles(candidateFiles, fileModifiedMap) {
191
261
  let paths = candidateFiles.map((contentPath) => contentPath.pattern)
192
262
  let mTimesToCommit = new Map()
193
263
 
264
+ let checkBroadPattern = createBroadPatternCheck(paths)
265
+
194
266
  let changedFiles = new Set()
195
267
  env.DEBUG && console.time('Finding changed files')
196
268
  let files = fastGlob.sync(paths, { absolute: true })
197
269
  for (let file of files) {
270
+ checkBroadPattern(file)
271
+
198
272
  let prevModified = fileModifiedMap.get(file) || -Infinity
199
273
  let modified = fs.statSync(file).mtimeMs
200
274
 
@@ -152,6 +152,9 @@ function* buildRegExps(context) {
152
152
  utility,
153
153
  ])
154
154
  }
155
+
156
+ // 5. Inner matches
157
+ yield /[^<>"'`\s.(){}[\]#=%$][^<>"'`\s(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g
155
158
  }
156
159
 
157
160
  // We want to capture any "special" characters
@@ -81,6 +81,34 @@ export function normalize(value, context = null, isRoot = true) {
81
81
  return value
82
82
  }
83
83
 
84
+ export function normalizeAttributeSelectors(value) {
85
+ // Wrap values in attribute selectors with quotes
86
+ if (value.includes('=')) {
87
+ value = value.replace(/(=.*)/g, (_fullMatch, match) => {
88
+ if (match[1] === "'" || match[1] === '"') {
89
+ return match
90
+ }
91
+
92
+ // Handle regex flags on unescaped values
93
+ if (match.length > 2) {
94
+ let trailingCharacter = match[match.length - 1]
95
+ if (
96
+ match[match.length - 2] === ' ' &&
97
+ (trailingCharacter === 'i' ||
98
+ trailingCharacter === 'I' ||
99
+ trailingCharacter === 's' ||
100
+ trailingCharacter === 'S')
101
+ ) {
102
+ return `="${match.slice(1, -2)}" ${match[match.length - 1]}`
103
+ }
104
+ }
105
+
106
+ return `="${match.slice(1)}"`
107
+ })
108
+ }
109
+ return value
110
+ }
111
+
84
112
  /**
85
113
  * Add spaces around operators inside math functions
86
114
  * like calc() that do not follow an operator, '(', or `,`.