tailwindcss 0.0.0-insiders.fe08e91 → 0.0.0-oxide.dd87d75

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 (178) hide show
  1. package/CHANGELOG.md +379 -3
  2. package/LICENSE +1 -2
  3. package/README.md +12 -8
  4. package/colors.d.ts +3 -0
  5. package/defaultConfig.d.ts +3 -0
  6. package/defaultTheme.d.ts +4 -0
  7. package/lib/cli/build/deps.js +54 -0
  8. package/lib/cli/build/index.js +48 -0
  9. package/lib/cli/build/plugin.js +367 -0
  10. package/lib/cli/build/utils.js +78 -0
  11. package/lib/cli/build/watching.js +178 -0
  12. package/lib/cli/help/index.js +71 -0
  13. package/lib/cli/index.js +239 -0
  14. package/lib/cli/init/index.js +46 -0
  15. package/lib/cli/shared.js +13 -0
  16. package/lib/cli-peer-dependencies.js +20 -7
  17. package/lib/cli.js +4 -740
  18. package/lib/constants.js +27 -20
  19. package/lib/corePluginList.js +6 -3
  20. package/lib/corePlugins.js +2064 -1811
  21. package/lib/css/preflight.css +5 -5
  22. package/lib/featureFlags.js +31 -22
  23. package/lib/index.js +4 -28
  24. package/lib/lib/cacheInvalidation.js +90 -0
  25. package/lib/lib/collapseAdjacentRules.js +27 -9
  26. package/lib/lib/collapseDuplicateDeclarations.js +12 -9
  27. package/lib/lib/content.js +176 -0
  28. package/lib/lib/defaultExtractor.js +225 -31
  29. package/lib/lib/detectNesting.js +13 -10
  30. package/lib/lib/evaluateTailwindFunctions.js +118 -55
  31. package/lib/lib/expandApplyAtRules.js +439 -190
  32. package/lib/lib/expandTailwindAtRules.js +151 -134
  33. package/lib/lib/findAtConfigPath.js +44 -0
  34. package/lib/lib/generateRules.js +454 -187
  35. package/lib/lib/getModuleDependencies.js +11 -8
  36. package/lib/lib/normalizeTailwindDirectives.js +36 -32
  37. package/lib/lib/offsets.js +217 -0
  38. package/lib/lib/partitionApplyAtRules.js +56 -0
  39. package/lib/lib/regex.js +60 -0
  40. package/lib/lib/resolveDefaultsAtRules.js +89 -67
  41. package/lib/lib/setupContextUtils.js +667 -376
  42. package/lib/lib/setupTrackingContext.js +38 -67
  43. package/lib/lib/sharedState.js +27 -14
  44. package/lib/lib/substituteScreenAtRules.js +11 -9
  45. package/lib/plugin.js +48 -0
  46. package/{nesting → lib/postcss-plugins/nesting}/README.md +2 -2
  47. package/lib/postcss-plugins/nesting/index.js +19 -0
  48. package/lib/postcss-plugins/nesting/plugin.js +87 -0
  49. package/lib/processTailwindFeatures.js +35 -25
  50. package/lib/public/colors.js +247 -245
  51. package/lib/public/create-plugin.js +6 -4
  52. package/lib/public/default-config.js +7 -5
  53. package/lib/public/default-theme.js +7 -5
  54. package/lib/public/resolve-config.js +8 -5
  55. package/lib/util/bigSign.js +4 -1
  56. package/lib/util/buildMediaQuery.js +11 -6
  57. package/lib/util/cloneDeep.js +7 -6
  58. package/lib/util/cloneNodes.js +21 -3
  59. package/lib/util/color.js +53 -54
  60. package/lib/util/configurePlugins.js +5 -2
  61. package/lib/util/createPlugin.js +6 -6
  62. package/lib/util/createUtilityPlugin.js +12 -14
  63. package/lib/util/dataTypes.js +119 -110
  64. package/lib/util/defaults.js +4 -1
  65. package/lib/util/escapeClassName.js +7 -4
  66. package/lib/util/escapeCommas.js +5 -2
  67. package/lib/util/flattenColorPalette.js +9 -12
  68. package/lib/util/formatVariantSelector.js +184 -85
  69. package/lib/util/getAllConfigs.js +27 -8
  70. package/lib/util/hashConfig.js +6 -3
  71. package/lib/util/isKeyframeRule.js +5 -2
  72. package/lib/util/isPlainObject.js +5 -2
  73. package/lib/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +23 -15
  74. package/lib/util/log.js +20 -14
  75. package/lib/util/nameClass.js +20 -9
  76. package/lib/util/negateValue.js +23 -8
  77. package/lib/util/normalizeConfig.js +116 -72
  78. package/lib/util/normalizeScreens.js +120 -11
  79. package/lib/util/parseAnimationValue.js +42 -40
  80. package/lib/util/parseBoxShadowValue.js +30 -23
  81. package/lib/util/parseDependency.js +38 -56
  82. package/lib/util/parseGlob.js +34 -0
  83. package/lib/util/parseObjectStyles.js +11 -8
  84. package/lib/util/pluginUtils.js +147 -50
  85. package/lib/util/prefixSelector.js +10 -8
  86. package/lib/util/removeAlphaVariables.js +29 -0
  87. package/lib/util/resolveConfig.js +97 -85
  88. package/lib/util/resolveConfigPath.js +11 -9
  89. package/lib/util/responsive.js +8 -5
  90. package/lib/util/splitAtTopLevelOnly.js +43 -0
  91. package/lib/util/tap.js +4 -1
  92. package/lib/util/toColorValue.js +5 -3
  93. package/lib/util/toPath.js +20 -4
  94. package/lib/util/transformThemeValue.js +37 -29
  95. package/lib/util/validateConfig.js +24 -0
  96. package/lib/util/validateFormalSyntax.js +24 -0
  97. package/lib/util/withAlphaVariable.js +23 -15
  98. package/nesting/index.js +2 -12
  99. package/package.json +47 -42
  100. package/peers/index.js +11381 -7950
  101. package/plugin.d.ts +11 -0
  102. package/resolveConfig.d.ts +12 -0
  103. package/scripts/generate-types.js +105 -0
  104. package/scripts/release-channel.js +18 -0
  105. package/scripts/release-notes.js +21 -0
  106. package/scripts/type-utils.js +27 -0
  107. package/src/cli/build/deps.js +56 -0
  108. package/src/cli/build/index.js +49 -0
  109. package/src/cli/build/plugin.js +439 -0
  110. package/src/cli/build/utils.js +76 -0
  111. package/src/cli/build/watching.js +227 -0
  112. package/src/cli/help/index.js +70 -0
  113. package/src/cli/index.js +234 -0
  114. package/src/cli/init/index.js +50 -0
  115. package/src/cli/shared.js +6 -0
  116. package/src/cli-peer-dependencies.js +7 -1
  117. package/src/cli.js +4 -810
  118. package/src/corePluginList.js +1 -1
  119. package/src/corePlugins.js +532 -217
  120. package/src/css/preflight.css +5 -5
  121. package/src/featureFlags.js +15 -9
  122. package/src/index.js +4 -27
  123. package/src/lib/cacheInvalidation.js +52 -0
  124. package/src/lib/collapseAdjacentRules.js +21 -2
  125. package/src/lib/content.js +212 -0
  126. package/src/lib/defaultExtractor.js +196 -33
  127. package/src/lib/evaluateTailwindFunctions.js +78 -7
  128. package/src/lib/expandApplyAtRules.js +482 -183
  129. package/src/lib/expandTailwindAtRules.js +106 -85
  130. package/src/lib/findAtConfigPath.js +48 -0
  131. package/src/lib/generateRules.js +418 -129
  132. package/src/lib/normalizeTailwindDirectives.js +1 -0
  133. package/src/lib/offsets.js +270 -0
  134. package/src/lib/partitionApplyAtRules.js +52 -0
  135. package/src/lib/regex.js +74 -0
  136. package/src/lib/resolveDefaultsAtRules.js +51 -30
  137. package/src/lib/setupContextUtils.js +556 -208
  138. package/src/lib/setupTrackingContext.js +11 -48
  139. package/src/lib/sharedState.js +5 -0
  140. package/src/plugin.js +47 -0
  141. package/src/postcss-plugins/nesting/README.md +42 -0
  142. package/src/postcss-plugins/nesting/index.js +13 -0
  143. package/src/postcss-plugins/nesting/plugin.js +80 -0
  144. package/src/processTailwindFeatures.js +8 -0
  145. package/src/util/buildMediaQuery.js +5 -3
  146. package/src/util/cloneNodes.js +19 -2
  147. package/src/util/color.js +25 -21
  148. package/src/util/dataTypes.js +29 -21
  149. package/src/util/formatVariantSelector.js +184 -61
  150. package/src/util/getAllConfigs.js +19 -0
  151. package/src/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +1 -1
  152. package/src/util/log.js +8 -8
  153. package/src/util/nameClass.js +4 -0
  154. package/src/util/negateValue.js +11 -3
  155. package/src/util/normalizeConfig.js +44 -6
  156. package/src/util/normalizeScreens.js +99 -4
  157. package/src/util/parseBoxShadowValue.js +4 -3
  158. package/src/util/parseDependency.js +37 -42
  159. package/src/util/parseGlob.js +24 -0
  160. package/src/util/pluginUtils.js +132 -10
  161. package/src/util/prefixSelector.js +7 -5
  162. package/src/util/removeAlphaVariables.js +24 -0
  163. package/src/util/resolveConfig.js +70 -32
  164. package/src/util/splitAtTopLevelOnly.js +45 -0
  165. package/src/util/toPath.js +1 -1
  166. package/src/util/transformThemeValue.js +13 -3
  167. package/src/util/validateConfig.js +13 -0
  168. package/src/util/validateFormalSyntax.js +34 -0
  169. package/src/util/withAlphaVariable.js +1 -1
  170. package/stubs/defaultConfig.stub.js +23 -20
  171. package/stubs/simpleConfig.stub.js +1 -0
  172. package/types/config.d.ts +362 -0
  173. package/types/generated/.gitkeep +0 -0
  174. package/types/generated/colors.d.ts +276 -0
  175. package/types/generated/corePluginList.d.ts +1 -0
  176. package/types/generated/default-theme.d.ts +342 -0
  177. package/types/index.d.ts +7 -0
  178. package/nesting/plugin.js +0 -41
@@ -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,
@@ -22,6 +22,7 @@
22
22
  2. Prevent adjustments of font size after orientation changes in iOS.
23
23
  3. Use a more readable tab size.
24
24
  4. Use the user's configured `sans` font-family by default.
25
+ 5. Use the user's configured `sans` font-feature-settings by default.
25
26
  */
26
27
 
27
28
  html {
@@ -30,6 +31,7 @@ html {
30
31
  -moz-tab-size: 4; /* 3 */
31
32
  tab-size: 4; /* 3 */
32
33
  font-family: theme('fontFamily.sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); /* 4 */
34
+ font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings', normal); /* 5 */
33
35
  }
34
36
 
35
37
  /*
@@ -160,6 +162,7 @@ select,
160
162
  textarea {
161
163
  font-family: inherit; /* 1 */
162
164
  font-size: 100%; /* 1 */
165
+ font-weight: inherit; /* 1 */
163
166
  line-height: inherit; /* 1 */
164
167
  color: inherit; /* 1 */
165
168
  margin: 0; /* 2 */
@@ -358,10 +361,7 @@ video {
358
361
  height: auto;
359
362
  }
360
363
 
361
- /*
362
- Ensure the default browser behavior of the `hidden` attribute.
363
- */
364
-
364
+ /* Make elements with the HTML hidden attribute stay hidden by default */
365
365
  [hidden] {
366
366
  display: none;
367
367
  }
@@ -1,17 +1,23 @@
1
- import chalk from 'chalk'
1
+ import colors from 'picocolors'
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,
6
+ generalizedModifiers: true,
10
7
  }
11
8
 
12
9
  let featureFlags = {
13
- future: [],
14
- experimental: ['optimizeUniversalDefaults'],
10
+ future: [
11
+ 'hoverOnlyWhenSupported',
12
+ 'respectDefaultRingColorOpacity',
13
+ 'disableColorOpacityUtilitiesByDefault',
14
+ 'relativeContentPathsByDefault',
15
+ ],
16
+ experimental: [
17
+ 'optimizeUniversalDefaults',
18
+ 'generalizedModifiers',
19
+ // 'variantGrouping',
20
+ ],
15
21
  }
16
22
 
17
23
  export function flagEnabled(config, flag) {
@@ -45,7 +51,7 @@ export function issueFlagNotices(config) {
45
51
 
46
52
  if (experimentalFlagsEnabled(config).length > 0) {
47
53
  let changes = experimentalFlagsEnabled(config)
48
- .map((s) => chalk.yellow(s))
54
+ .map((s) => colors.yellow(s))
49
55
  .join(', ')
50
56
 
51
57
  log.warn('experimental-flags-enabled', [
package/src/index.js CHANGED
@@ -1,28 +1,5 @@
1
- import setupTrackingContext from './lib/setupTrackingContext'
2
- import processTailwindFeatures from './processTailwindFeatures'
3
- import { env } from './lib/sharedState'
4
-
5
- module.exports = function tailwindcss(configOrPath) {
6
- return {
7
- postcssPlugin: 'tailwindcss',
8
- plugins: [
9
- env.DEBUG &&
10
- function (root) {
11
- console.log('\n')
12
- console.time('JIT TOTAL')
13
- return root
14
- },
15
- function (root, result) {
16
- processTailwindFeatures(setupTrackingContext(configOrPath))(root, result)
17
- },
18
- env.DEBUG &&
19
- function (root) {
20
- console.timeEnd('JIT TOTAL')
21
- console.log('\n')
22
- return root
23
- },
24
- ].filter(Boolean),
25
- }
1
+ if (process.env.OXIDE) {
2
+ module.exports = require('../oxide/packages/tailwindcss/dist/postcss-plugin.js')
3
+ } else {
4
+ module.exports = require('./plugin.js')
26
5
  }
27
-
28
- module.exports.postcss = true
@@ -0,0 +1,52 @@
1
+ import crypto from 'crypto'
2
+ import * as sharedState from './sharedState'
3
+
4
+ /**
5
+ * Calculate the hash of a string.
6
+ *
7
+ * This doesn't need to be cryptographically secure or
8
+ * anything like that since it's used only to detect
9
+ * when the CSS changes to invalidate the context.
10
+ *
11
+ * This is wrapped in a try/catch because it's really dependent
12
+ * on how Node itself is build and the environment and OpenSSL
13
+ * version / build that is installed on the user's machine.
14
+ *
15
+ * Based on the environment this can just outright fail.
16
+ *
17
+ * See https://github.com/nodejs/node/issues/40455
18
+ *
19
+ * @param {string} str
20
+ */
21
+ function getHash(str) {
22
+ try {
23
+ return crypto.createHash('md5').update(str, 'utf-8').digest('binary')
24
+ } catch (err) {
25
+ return ''
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Determine if the CSS tree is different from the
31
+ * previous version for the given `sourcePath`.
32
+ *
33
+ * @param {string} sourcePath
34
+ * @param {import('postcss').Node} root
35
+ */
36
+ export function hasContentChanged(sourcePath, root) {
37
+ let css = root.toString()
38
+
39
+ // We only care about files with @tailwind directives
40
+ // Other files use an existing context
41
+ if (!css.includes('@tailwind')) {
42
+ return false
43
+ }
44
+
45
+ let existingHash = sharedState.sourceHashMap.get(sourcePath)
46
+ let rootHash = getHash(css)
47
+ let didChange = existingHash !== rootHash
48
+
49
+ sharedState.sourceHashMap.set(sourcePath, rootHash)
50
+
51
+ return didChange
52
+ }
@@ -5,7 +5,7 @@ let comparisonMap = {
5
5
  let types = new Set(Object.keys(comparisonMap))
6
6
 
7
7
  export default function collapseAdjacentRules() {
8
- return (root) => {
8
+ function collapseRulesIn(root) {
9
9
  let currentRule = null
10
10
  root.each((node) => {
11
11
  if (!types.has(node.type)) {
@@ -29,11 +29,30 @@ export default function collapseAdjacentRules() {
29
29
  (currentRule[property] ?? '').replace(/\s+/g, ' ')
30
30
  )
31
31
  ) {
32
- currentRule.append(node.nodes)
32
+ // An AtRule may not have children (for example if we encounter duplicate @import url(…) rules)
33
+ if (node.nodes) {
34
+ currentRule.append(node.nodes)
35
+ }
36
+
33
37
  node.remove()
34
38
  } else {
35
39
  currentRule = node
36
40
  }
37
41
  })
42
+
43
+ // After we've collapsed adjacent rules & at-rules, we need to collapse
44
+ // adjacent rules & at-rules that are children of at-rules.
45
+ // We do not care about nesting rules because Tailwind CSS
46
+ // explicitly does not handle rule nesting on its own as
47
+ // the user is expected to use a nesting plugin
48
+ root.each((node) => {
49
+ if (node.type === 'atrule') {
50
+ collapseRulesIn(node)
51
+ }
52
+ })
53
+ }
54
+
55
+ return (root) => {
56
+ collapseRulesIn(root)
38
57
  }
39
58
  }
@@ -0,0 +1,212 @@
1
+ // @ts-check
2
+
3
+ import fs from 'fs'
4
+ import path from 'path'
5
+ import isGlob from 'is-glob'
6
+ import fastGlob from 'fast-glob'
7
+ import normalizePath from 'normalize-path'
8
+ import { parseGlob } from '../util/parseGlob'
9
+ import { env } from './sharedState'
10
+
11
+ /** @typedef {import('../../types/config.js').RawFile} RawFile */
12
+ /** @typedef {import('../../types/config.js').FilePath} FilePath */
13
+
14
+ /**
15
+ * @typedef {object} ContentPath
16
+ * @property {string} original
17
+ * @property {string} base
18
+ * @property {string | null} glob
19
+ * @property {boolean} ignore
20
+ * @property {string} pattern
21
+ */
22
+
23
+ /**
24
+ * Turn a list of content paths (absolute or not; glob or not) into a list of
25
+ * absolute file paths that exist on the filesystem
26
+ *
27
+ * If there are symlinks in the path then multiple paths will be returned
28
+ * one for the symlink and one for the actual file
29
+ *
30
+ * @param {*} context
31
+ * @param {import('tailwindcss').Config} tailwindConfig
32
+ * @returns {ContentPath[]}
33
+ */
34
+ export function parseCandidateFiles(context, tailwindConfig) {
35
+ let files = tailwindConfig.content.files
36
+
37
+ // Normalize the file globs
38
+ files = files.filter((filePath) => typeof filePath === 'string')
39
+ files = files.map(normalizePath)
40
+
41
+ // Split into included and excluded globs
42
+ let tasks = fastGlob.generateTasks(files)
43
+
44
+ /** @type {ContentPath[]} */
45
+ let included = []
46
+
47
+ /** @type {ContentPath[]} */
48
+ let excluded = []
49
+
50
+ for (const task of tasks) {
51
+ included.push(...task.positive.map((filePath) => parseFilePath(filePath, false)))
52
+ excluded.push(...task.negative.map((filePath) => parseFilePath(filePath, true)))
53
+ }
54
+
55
+ let paths = [...included, ...excluded]
56
+
57
+ // Resolve paths relative to the config file or cwd
58
+ paths = resolveRelativePaths(context, paths)
59
+
60
+ // Resolve symlinks if possible
61
+ paths = paths.flatMap(resolvePathSymlinks)
62
+
63
+ // Update cached patterns
64
+ paths = paths.map(resolveGlobPattern)
65
+
66
+ return paths
67
+ }
68
+
69
+ /**
70
+ *
71
+ * @param {string} filePath
72
+ * @param {boolean} ignore
73
+ * @returns {ContentPath}
74
+ */
75
+ function parseFilePath(filePath, ignore) {
76
+ let contentPath = {
77
+ original: filePath,
78
+ base: filePath,
79
+ ignore,
80
+ pattern: filePath,
81
+ glob: null,
82
+ }
83
+
84
+ if (isGlob(filePath)) {
85
+ Object.assign(contentPath, parseGlob(filePath))
86
+ }
87
+
88
+ return contentPath
89
+ }
90
+
91
+ /**
92
+ *
93
+ * @param {ContentPath} contentPath
94
+ * @returns {ContentPath}
95
+ */
96
+ function resolveGlobPattern(contentPath) {
97
+ // This is required for Windows support to properly pick up Glob paths.
98
+ // Afaik, this technically shouldn't be needed but there's probably
99
+ // some internal, direct path matching with a normalized path in
100
+ // a package which can't handle mixed directory separators
101
+ let base = normalizePath(contentPath.base)
102
+
103
+ // If the user's file path contains any special characters (like parens) for instance fast-glob
104
+ // is like "OOOH SHINY" and treats them as such. So we have to escape the base path to fix this
105
+ base = fastGlob.escapePath(base)
106
+
107
+ contentPath.pattern = contentPath.glob ? `${base}/${contentPath.glob}` : base
108
+ contentPath.pattern = contentPath.ignore ? `!${contentPath.pattern}` : contentPath.pattern
109
+
110
+ return contentPath
111
+ }
112
+
113
+ /**
114
+ * Resolve each path relative to the config file (when possible) if the experimental flag is enabled
115
+ * Otherwise, resolve relative to the current working directory
116
+ *
117
+ * @param {any} context
118
+ * @param {ContentPath[]} contentPaths
119
+ * @returns {ContentPath[]}
120
+ */
121
+ function resolveRelativePaths(context, contentPaths) {
122
+ let resolveFrom = []
123
+
124
+ // Resolve base paths relative to the config file (when possible) if the experimental flag is enabled
125
+ if (context.userConfigPath && context.tailwindConfig.content.relative) {
126
+ resolveFrom = [path.dirname(context.userConfigPath)]
127
+ }
128
+
129
+ return contentPaths.map((contentPath) => {
130
+ contentPath.base = path.resolve(...resolveFrom, contentPath.base)
131
+
132
+ return contentPath
133
+ })
134
+ }
135
+
136
+ /**
137
+ * Resolve the symlink for the base directory / file in each path
138
+ * These are added as additional dependencies to watch for changes because
139
+ * some tools (like webpack) will only watch the actual file or directory
140
+ * but not the symlink itself even in projects that use monorepos.
141
+ *
142
+ * @param {ContentPath} contentPath
143
+ * @returns {ContentPath[]}
144
+ */
145
+ function resolvePathSymlinks(contentPath) {
146
+ let paths = [contentPath]
147
+
148
+ try {
149
+ let resolvedPath = fs.realpathSync(contentPath.base)
150
+ if (resolvedPath !== contentPath.base) {
151
+ paths.push({
152
+ ...contentPath,
153
+ base: resolvedPath,
154
+ })
155
+ }
156
+ } catch {
157
+ // TODO: log this?
158
+ }
159
+
160
+ return paths
161
+ }
162
+
163
+ /**
164
+ * @param {any} context
165
+ * @param {ContentPath[]} candidateFiles
166
+ * @param {Map<string, number>} fileModifiedMap
167
+ * @returns {{ content: string, extension: string }[]}
168
+ */
169
+ export function resolvedChangedContent(context, candidateFiles, fileModifiedMap) {
170
+ let changedContent = context.tailwindConfig.content.files
171
+ .filter((item) => typeof item.raw === 'string')
172
+ .map(({ raw, extension = 'html' }) => ({ content: raw, extension }))
173
+
174
+ for (let changedFile of resolveChangedFiles(candidateFiles, fileModifiedMap)) {
175
+ let extension = path.extname(changedFile).slice(1)
176
+ changedContent.push({ file: changedFile, extension })
177
+ }
178
+
179
+ return changedContent
180
+ }
181
+
182
+ /**
183
+ *
184
+ * @param {ContentPath[]} candidateFiles
185
+ * @param {Map<string, number>} fileModifiedMap
186
+ * @returns {Set<string>}
187
+ */
188
+ function resolveChangedFiles(candidateFiles, fileModifiedMap) {
189
+ let paths = candidateFiles.map((contentPath) => contentPath.pattern)
190
+
191
+ let changedFiles = new Set()
192
+ env.DEBUG && console.time('Finding changed files')
193
+ let files = fastGlob.sync(paths, { absolute: true })
194
+ for (let file of files) {
195
+ let prevModified = fileModifiedMap.has(file) ? fileModifiedMap.get(file) : -Infinity
196
+ let modified = fs.statSync(file).mtimeMs
197
+
198
+ // This check is intentionally >= because we track the last modified time of context dependencies
199
+ // earier in the process and we want to make sure we don't miss any changes that happen
200
+ // when a context dependency is also a content dependency
201
+ // Ideally, we'd do all this tracking at one time but that is a larger refactor
202
+ // than we want to commit to right now, so this is a decent compromise.
203
+ // This should be sufficient because file modification times will be off by at least
204
+ // 1ms (the precision of fstat in Node) in most cases if they exist and were changed.
205
+ if (modified >= prevModified) {
206
+ changedFiles.add(file)
207
+ fileModifiedMap.set(file, modified)
208
+ }
209
+ }
210
+ env.DEBUG && console.timeEnd('Finding changed files')
211
+ return changedFiles
212
+ }
@@ -1,40 +1,203 @@
1
- const PATTERNS = [
2
- /(?:\['([^'\s]+[^<>"'`\s:\\])')/.source, // ['text-lg' -> text-lg
3
- /(?:\["([^"\s]+[^<>"'`\s:\\])")/.source, // ["text-lg" -> text-lg
4
- /(?:\[`([^`\s]+[^<>"'`\s:\\])`)/.source, // [`text-lg` -> text-lg
5
- /([^<>"'`\s]*\[\w*'[^"`\s]*'?\])/.source, // font-['some_font',sans-serif]
6
- /([^<>"'`\s]*\[\w*"[^'`\s]*"?\])/.source, // font-["some_font",sans-serif]
7
- /([^<>"'`\s]*\[\w*\('[^"'`\s]*'\)\])/.source, // bg-[url('...')]
8
- /([^<>"'`\s]*\[\w*\("[^"'`\s]*"\)\])/.source, // bg-[url("...")]
9
- /([^<>"'`\s]*\[\w*\('[^"`\s]*'\)\])/.source, // bg-[url('...'),url('...')]
10
- /([^<>"'`\s]*\[\w*\("[^'`\s]*"\)\])/.source, // bg-[url("..."),url("...")]
11
- /([^<>"'`\s]*\['[^"'`\s]*'\])/.source, // `content-['hello']` but not `content-['hello']']`
12
- /([^<>"'`\s]*\["[^"'`\s]*"\])/.source, // `content-["hello"]` but not `content-["hello"]"]`
13
- /([^<>"'`\s]*\[[^<>"'`\s]*:[^\]\s]*\])/.source, // `[attr:value]`
14
- /([^<>"'`\s]*\[[^<>"'`\s]*:'[^"'`\s]*'\])/.source, // `[content:'hello']` but not `[content:"hello"]`
15
- /([^<>"'`\s]*\[[^<>"'`\s]*:"[^"'`\s]*"\])/.source, // `[content:"hello"]` but not `[content:'hello']`
16
- /([^<>"'`\s]*\[[^"'`\s]+\][^<>"'`\s]*)/.source, // `fill-[#bada55]`, `fill-[#bada55]/50`
17
- /([^"'`\s]*[^<>"'`\s:\\])/.source, // `<sm:underline`, `md>:font-bold`
18
- /([^<>"'`\s]*[^"'`\s:\\])/.source, // `px-1.5`, `uppercase` but not `uppercase:`
19
-
20
- // Arbitrary properties
21
- // /([^"\s]*\[[^\s]+?\][^"\s]*)/.source,
22
- // /([^'\s]*\[[^\s]+?\][^'\s]*)/.source,
23
- // /([^`\s]*\[[^\s]+?\][^`\s]*)/.source,
24
- ].join('|')
25
-
26
- const BROAD_MATCH_GLOBAL_REGEXP = new RegExp(PATTERNS, 'g')
27
- const INNER_MATCH_GLOBAL_REGEXP = /[^<>"'`\s.(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g
1
+ import { flagEnabled } from '../featureFlags'
2
+ import * as regex from './regex'
3
+
4
+ export function defaultExtractor(context) {
5
+ let patterns = Array.from(buildRegExps(context))
6
+
7
+ /**
8
+ * @param {string} content
9
+ */
10
+ return (content) => {
11
+ /** @type {(string|string)[]} */
12
+ let results = []
13
+
14
+ for (let pattern of patterns) {
15
+ results = [...results, ...(content.match(pattern) ?? [])]
16
+ }
17
+
18
+ return results.filter((v) => v !== undefined).map(clipAtBalancedParens)
19
+ }
20
+ }
21
+
22
+ function* buildRegExps(context) {
23
+ let separator = context.tailwindConfig.separator
24
+ let variantGroupingEnabled = flagEnabled(context.tailwindConfig, 'variantGrouping')
25
+ let prefix =
26
+ context.tailwindConfig.prefix !== ''
27
+ ? regex.optional(regex.pattern([/-?/, regex.escape(context.tailwindConfig.prefix)]))
28
+ : ''
29
+
30
+ let utility = regex.any([
31
+ // Arbitrary properties
32
+ /\[[^\s:'"`]+:[^\s]+\]/,
33
+
34
+ // Utilities
35
+ regex.pattern([
36
+ // Utility Name / Group Name
37
+ /-?(?:\w+)/,
38
+
39
+ // Normal/Arbitrary values
40
+ regex.optional(
41
+ regex.any([
42
+ regex.pattern([
43
+ // Arbitrary values
44
+ /-(?:\w+-)*\[[^\s:]+\]/,
45
+
46
+ // Not immediately followed by an `{[(`
47
+ /(?![{([]])/,
48
+
49
+ // optionally followed by an opacity modifier
50
+ /(?:\/[^\s'"`\\><$]*)?/,
51
+ ]),
52
+
53
+ regex.pattern([
54
+ // Arbitrary values
55
+ /-(?:\w+-)*\[[^\s]+\]/,
56
+
57
+ // Not immediately followed by an `{[(`
58
+ /(?![{([]])/,
59
+
60
+ // optionally followed by an opacity modifier
61
+ /(?:\/[^\s'"`\\$]*)?/,
62
+ ]),
63
+
64
+ // Normal values w/o quotes — may include an opacity modifier
65
+ /[-\/][^\s'"`\\$={><]*/,
66
+ ])
67
+ ),
68
+ ]),
69
+ ])
70
+
71
+ let variantPatterns = [
72
+ // Without quotes
73
+ regex.any([
74
+ // This is here to provide special support for the `@` variant
75
+ regex.pattern([/@\[[^\s"'`]+\](\/[^\s"'`]+)?/, separator]),
76
+
77
+ regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/, separator]),
78
+ regex.pattern([/[^\s"'`\[\\]+/, separator]),
79
+ ]),
80
+
81
+ // With quotes allowed
82
+ regex.any([
83
+ regex.pattern([/([^\s"'`\[\\]+-)?\[[^\s`]+\]/, separator]),
84
+ regex.pattern([/[^\s`\[\\]+/, separator]),
85
+ ]),
86
+ ]
87
+
88
+ for (const variantPattern of variantPatterns) {
89
+ yield regex.pattern([
90
+ // Variants
91
+ '((?=((',
92
+ variantPattern,
93
+ ')+))\\2)?',
94
+
95
+ // Important (optional)
96
+ /!?/,
97
+
98
+ prefix,
99
+
100
+ variantGroupingEnabled
101
+ ? regex.any([
102
+ // Or any of those things but grouped separated by commas
103
+ regex.pattern([/\(/, utility, regex.zeroOrMore([/,/, utility]), /\)/]),
104
+
105
+ // Arbitrary properties, constrained utilities, arbitrary values, etc…
106
+ utility,
107
+ ])
108
+ : utility,
109
+ ])
110
+ }
111
+
112
+ // 5. Inner matches
113
+ yield /[^<>"'`\s.(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g
114
+ }
115
+
116
+ // We want to capture any "special" characters
117
+ // AND the characters immediately following them (if there is one)
118
+ let SPECIALS = /([\[\]'"`])([^\[\]'"`])?/g
119
+ let ALLOWED_CLASS_CHARACTERS = /[^"'`\s<>\]]+/
28
120
 
29
121
  /**
30
- * @param {string} content
122
+ * Clips a string ensuring that parentheses, quotes, etc… are balanced
123
+ * Used for arbitrary values only
124
+ *
125
+ * We will go past the end of the balanced parens until we find a non-class character
126
+ *
127
+ * Depth matching behavior:
128
+ * w-[calc(100%-theme('spacing[some_key][1.5]'))]']
129
+ * ┬ ┬ ┬┬ ┬ ┬┬ ┬┬┬┬┬┬┬
130
+ * 1 2 3 4 34 3 210 END
131
+ * ╰────┴──────────┴────────┴────────┴┴───┴─┴┴┴
132
+ *
133
+ * @param {string} input
31
134
  */
32
- export function defaultExtractor(content) {
33
- let broadMatches = content.matchAll(BROAD_MATCH_GLOBAL_REGEXP)
34
- let innerMatches = content.match(INNER_MATCH_GLOBAL_REGEXP) || []
35
- let results = [...broadMatches, ...innerMatches].flat().filter((v) => v !== undefined)
135
+ function clipAtBalancedParens(input) {
136
+ // We are care about this for arbitrary values
137
+ if (!input.includes('-[')) {
138
+ return input
139
+ }
140
+
141
+ let depth = 0
142
+ let openStringTypes = []
143
+
144
+ // Find all parens, brackets, quotes, etc
145
+ // Stop when we end at a balanced pair
146
+ // This is naive and will treat mismatched parens as balanced
147
+ // This shouldn't be a problem in practice though
148
+ let matches = input.matchAll(SPECIALS)
149
+
150
+ // We can't use lookbehind assertions because we have to support Safari
151
+ // So, instead, we've emulated it using capture groups and we'll re-work the matches to accommodate
152
+ matches = Array.from(matches).flatMap((match) => {
153
+ const [, ...groups] = match
154
+
155
+ return groups.map((group, idx) =>
156
+ Object.assign([], match, {
157
+ index: match.index + idx,
158
+ 0: group,
159
+ })
160
+ )
161
+ })
162
+
163
+ for (let match of matches) {
164
+ let char = match[0]
165
+ let inStringType = openStringTypes[openStringTypes.length - 1]
166
+
167
+ if (char === inStringType) {
168
+ openStringTypes.pop()
169
+ } else if (char === "'" || char === '"' || char === '`') {
170
+ openStringTypes.push(char)
171
+ }
172
+
173
+ if (inStringType) {
174
+ continue
175
+ } else if (char === '[') {
176
+ depth++
177
+ continue
178
+ } else if (char === ']') {
179
+ depth--
180
+ continue
181
+ }
182
+
183
+ // We've gone one character past the point where we should stop
184
+ // This means that there was an extra closing `]`
185
+ // We'll clip to just before it
186
+ if (depth < 0) {
187
+ return input.substring(0, match.index)
188
+ }
189
+
190
+ // We've finished balancing the brackets but there still may be characters that can be included
191
+ // For example in the class `text-[#336699]/[.35]`
192
+ // The depth goes to `0` at the closing `]` but goes up again at the `[`
193
+
194
+ // If we're at zero and encounter a non-class character then we clip the class there
195
+ if (depth === 0 && !ALLOWED_CLASS_CHARACTERS.test(char)) {
196
+ return input.substring(0, match.index)
197
+ }
198
+ }
36
199
 
37
- return results
200
+ return input
38
201
  }
39
202
 
40
203
  // Regular utilities