tailwindcss 0.0.0-insiders.ea10bb9 → 0.0.0-insiders.ea4e1cd

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 (184) hide show
  1. package/README.md +1 -1
  2. package/lib/cli/build/deps.js +17 -9
  3. package/lib/cli/build/index.js +20 -10
  4. package/lib/cli/build/plugin.js +86 -70
  5. package/lib/cli/build/utils.js +18 -8
  6. package/lib/cli/build/watching.js +16 -12
  7. package/lib/cli/help/index.js +6 -4
  8. package/lib/cli/index.js +222 -10
  9. package/lib/cli/init/index.js +29 -12
  10. package/lib/cli-peer-dependencies.js +12 -4
  11. package/lib/cli.js +4 -226
  12. package/lib/corePluginList.js +7 -1
  13. package/lib/corePlugins.js +348 -75
  14. package/lib/css/preflight.css +2 -0
  15. package/lib/featureFlags.js +27 -15
  16. package/lib/index.js +1 -47
  17. package/lib/lib/cacheInvalidation.js +7 -5
  18. package/lib/lib/collapseAdjacentRules.js +5 -3
  19. package/lib/lib/collapseDuplicateDeclarations.js +12 -10
  20. package/lib/lib/content.js +34 -30
  21. package/lib/lib/defaultExtractor.js +15 -8
  22. package/lib/lib/detectNesting.js +10 -2
  23. package/lib/lib/evaluateTailwindFunctions.js +22 -20
  24. package/lib/lib/expandApplyAtRules.js +43 -35
  25. package/lib/lib/expandTailwindAtRules.js +46 -19
  26. package/lib/lib/findAtConfigPath.js +9 -7
  27. package/lib/lib/generateRules.js +157 -92
  28. package/lib/lib/getModuleDependencies.js +85 -37
  29. package/lib/lib/load-config.js +42 -0
  30. package/lib/lib/normalizeTailwindDirectives.js +5 -3
  31. package/lib/lib/offsets.js +93 -4
  32. package/lib/lib/partitionApplyAtRules.js +3 -1
  33. package/lib/lib/regex.js +21 -7
  34. package/lib/lib/remap-bitfield.js +89 -0
  35. package/lib/lib/resolveDefaultsAtRules.js +28 -24
  36. package/lib/lib/setupContextUtils.js +190 -131
  37. package/lib/lib/setupTrackingContext.js +49 -26
  38. package/lib/lib/sharedState.js +37 -10
  39. package/lib/lib/substituteScreenAtRules.js +5 -3
  40. package/lib/oxide/cli/build/deps.js +89 -0
  41. package/lib/oxide/cli/build/index.js +53 -0
  42. package/lib/oxide/cli/build/plugin.js +375 -0
  43. package/lib/oxide/cli/build/utils.js +87 -0
  44. package/lib/oxide/cli/build/watching.js +179 -0
  45. package/lib/oxide/cli/help/index.js +72 -0
  46. package/lib/oxide/cli/index.js +214 -0
  47. package/lib/oxide/cli/init/index.js +52 -0
  48. package/lib/oxide/cli.js +5 -0
  49. package/lib/oxide/postcss-plugin.js +2 -0
  50. package/lib/plugin.js +98 -0
  51. package/lib/postcss-plugins/nesting/index.js +3 -1
  52. package/lib/postcss-plugins/nesting/plugin.js +10 -8
  53. package/lib/processTailwindFeatures.js +14 -12
  54. package/lib/public/colors.js +49 -25
  55. package/lib/public/create-plugin.js +5 -3
  56. package/lib/public/default-config.js +6 -4
  57. package/lib/public/default-theme.js +6 -4
  58. package/lib/public/load-config.js +12 -0
  59. package/lib/public/resolve-config.js +6 -4
  60. package/lib/util/applyImportantSelector.js +36 -0
  61. package/lib/util/bigSign.js +3 -1
  62. package/lib/util/buildMediaQuery.js +3 -1
  63. package/lib/util/cloneDeep.js +3 -1
  64. package/lib/util/cloneNodes.js +5 -3
  65. package/lib/util/color.js +30 -12
  66. package/lib/util/colorNames.js +752 -0
  67. package/lib/util/configurePlugins.js +3 -1
  68. package/lib/util/createPlugin.js +3 -1
  69. package/lib/util/createUtilityPlugin.js +7 -5
  70. package/lib/util/dataTypes.js +74 -17
  71. package/lib/util/defaults.js +9 -7
  72. package/lib/util/escapeClassName.js +10 -8
  73. package/lib/util/escapeCommas.js +3 -1
  74. package/lib/util/flattenColorPalette.js +3 -1
  75. package/lib/util/formatVariantSelector.js +138 -160
  76. package/lib/util/getAllConfigs.js +8 -6
  77. package/lib/util/hashConfig.js +6 -4
  78. package/lib/util/isKeyframeRule.js +3 -1
  79. package/lib/util/isPlainObject.js +3 -1
  80. package/lib/util/isSyntacticallyValidPropertyValue.js +3 -1
  81. package/lib/util/log.js +8 -4
  82. package/lib/util/nameClass.js +12 -6
  83. package/lib/util/negateValue.js +4 -2
  84. package/lib/util/normalizeConfig.js +28 -27
  85. package/lib/util/normalizeScreens.js +12 -4
  86. package/lib/util/parseAnimationValue.js +3 -1
  87. package/lib/util/parseBoxShadowValue.js +6 -2
  88. package/lib/util/parseDependency.js +3 -1
  89. package/lib/util/parseGlob.js +6 -4
  90. package/lib/util/parseObjectStyles.js +9 -7
  91. package/lib/util/pluginUtils.js +54 -54
  92. package/lib/util/prefixSelector.js +27 -11
  93. package/lib/util/pseudoElements.js +229 -0
  94. package/lib/util/removeAlphaVariables.js +3 -1
  95. package/lib/util/resolveConfig.js +17 -15
  96. package/lib/util/resolveConfigPath.js +26 -10
  97. package/lib/util/responsive.js +6 -4
  98. package/lib/util/splitAtTopLevelOnly.js +10 -2
  99. package/lib/util/tap.js +3 -1
  100. package/lib/util/toColorValue.js +3 -1
  101. package/lib/util/toPath.js +3 -1
  102. package/lib/util/transformThemeValue.js +6 -4
  103. package/lib/util/validateConfig.js +16 -3
  104. package/lib/util/validateFormalSyntax.js +3 -1
  105. package/lib/util/withAlphaVariable.js +6 -2
  106. package/loadConfig.d.ts +4 -0
  107. package/loadConfig.js +2 -0
  108. package/package.json +45 -37
  109. package/peers/index.js +63324 -46417
  110. package/resolveConfig.d.ts +11 -2
  111. package/scripts/swap-engines.js +40 -0
  112. package/src/cli/build/index.js +13 -9
  113. package/src/cli/build/plugin.js +42 -32
  114. package/src/cli/build/watching.js +4 -2
  115. package/src/cli/index.js +216 -3
  116. package/src/cli/init/index.js +37 -8
  117. package/src/cli.js +4 -220
  118. package/src/corePluginList.js +1 -1
  119. package/src/corePlugins.js +207 -32
  120. package/src/css/preflight.css +2 -0
  121. package/src/featureFlags.js +6 -0
  122. package/src/index.js +1 -47
  123. package/src/lib/content.js +12 -17
  124. package/src/lib/defaultExtractor.js +9 -3
  125. package/src/lib/detectNesting.js +9 -1
  126. package/src/lib/expandApplyAtRules.js +8 -1
  127. package/src/lib/expandTailwindAtRules.js +36 -7
  128. package/src/lib/generateRules.js +124 -42
  129. package/src/lib/getModuleDependencies.js +70 -30
  130. package/src/lib/load-config.ts +31 -0
  131. package/src/lib/offsets.js +104 -1
  132. package/src/lib/remap-bitfield.js +82 -0
  133. package/src/lib/setupContextUtils.js +103 -54
  134. package/src/lib/setupTrackingContext.js +36 -11
  135. package/src/lib/sharedState.js +15 -4
  136. package/src/oxide/cli/build/deps.ts +91 -0
  137. package/src/oxide/cli/build/index.ts +47 -0
  138. package/src/oxide/cli/build/plugin.ts +442 -0
  139. package/src/oxide/cli/build/utils.ts +74 -0
  140. package/src/oxide/cli/build/watching.ts +225 -0
  141. package/src/oxide/cli/help/index.ts +69 -0
  142. package/src/oxide/cli/index.ts +204 -0
  143. package/src/oxide/cli/init/index.ts +59 -0
  144. package/src/oxide/cli.ts +1 -0
  145. package/src/oxide/postcss-plugin.ts +1 -0
  146. package/src/plugin.js +107 -0
  147. package/src/public/colors.js +22 -0
  148. package/src/public/default-config.js +1 -1
  149. package/src/public/default-theme.js +2 -2
  150. package/src/public/load-config.js +2 -0
  151. package/src/util/applyImportantSelector.js +27 -0
  152. package/src/util/color.js +18 -3
  153. package/src/util/colorNames.js +150 -0
  154. package/src/util/dataTypes.js +33 -4
  155. package/src/util/formatVariantSelector.js +160 -163
  156. package/src/util/getAllConfigs.js +2 -2
  157. package/src/util/negateValue.js +1 -1
  158. package/src/util/normalizeConfig.js +2 -1
  159. package/src/util/pluginUtils.js +16 -31
  160. package/src/util/prefixSelector.js +28 -10
  161. package/src/util/pseudoElements.js +170 -0
  162. package/src/util/resolveConfigPath.js +12 -1
  163. package/src/util/splitAtTopLevelOnly.js +8 -1
  164. package/src/util/validateConfig.js +13 -0
  165. package/stubs/.gitignore +1 -0
  166. package/stubs/.prettierrc.json +6 -0
  167. package/stubs/{defaultConfig.stub.js → config.full.js} +183 -146
  168. package/stubs/{simpleConfig.stub.js → config.simple.js} +0 -1
  169. package/stubs/postcss.config.js +6 -0
  170. package/stubs/tailwind.config.cjs +2 -0
  171. package/stubs/tailwind.config.js +2 -0
  172. package/stubs/tailwind.config.ts +3 -0
  173. package/types/config.d.ts +15 -9
  174. package/types/generated/colors.d.ts +22 -0
  175. package/types/generated/corePluginList.d.ts +1 -1
  176. package/types/generated/default-theme.d.ts +108 -79
  177. package/CHANGELOG.md +0 -2336
  178. package/lib/cli/shared.js +0 -12
  179. package/lib/constants.js +0 -44
  180. package/scripts/install-integrations.js +0 -27
  181. package/scripts/rebuildFixtures.js +0 -68
  182. package/src/cli/shared.js +0 -5
  183. package/src/constants.js +0 -17
  184. /package/stubs/{defaultPostCssConfig.stub.js → postcss.config.cjs} +0 -0
@@ -1,4 +1,5 @@
1
- import LRU from 'quick-lru'
1
+ import fs from 'fs'
2
+ import LRU from '@alloc/quick-lru'
2
3
  import * as sharedState from './sharedState'
3
4
  import { generateRules } from './generateRules'
4
5
  import log from '../util/log'
@@ -124,15 +125,32 @@ export default function expandTailwindAtRules(context) {
124
125
  // ---
125
126
 
126
127
  // Find potential rules in changed files
127
- let candidates = new Set([sharedState.NOT_ON_DEMAND])
128
+ let candidates = new Set([...(context.candidates ?? []), sharedState.NOT_ON_DEMAND])
128
129
  let seen = new Set()
129
130
 
130
131
  env.DEBUG && console.time('Reading changed files')
131
132
 
132
- for (let { content, extension } of context.changedContent) {
133
- let transformer = getTransformer(context.tailwindConfig, extension)
134
- let extractor = getExtractor(context, extension)
135
- getClassCandidates(transformer(content), extractor, candidates, seen)
133
+ if (__OXIDE__) {
134
+ // TODO: Pass through or implement `extractor`
135
+ for (let candidate of require('@tailwindcss/oxide').parseCandidateStringsFromFiles(
136
+ context.changedContent
137
+ // Object.assign({}, builtInTransformers, context.tailwindConfig.content.transform)
138
+ )) {
139
+ candidates.add(candidate)
140
+ }
141
+
142
+ // for (let { file, content, extension } of context.changedContent) {
143
+ // let transformer = getTransformer(context.tailwindConfig, extension)
144
+ // let extractor = getExtractor(context, extension)
145
+ // getClassCandidatesOxide(file, transformer(content), extractor, candidates, seen)
146
+ // }
147
+ } else {
148
+ for (let { file, content, extension } of context.changedContent) {
149
+ let transformer = getTransformer(context.tailwindConfig, extension)
150
+ let extractor = getExtractor(context, extension)
151
+ content = file ? fs.readFileSync(file, 'utf8') : content
152
+ getClassCandidates(transformer(content), extractor, candidates, seen)
153
+ }
136
154
  }
137
155
 
138
156
  env.DEBUG && console.timeEnd('Reading changed files')
@@ -143,7 +161,18 @@ export default function expandTailwindAtRules(context) {
143
161
  let classCacheCount = context.classCache.size
144
162
 
145
163
  env.DEBUG && console.time('Generate rules')
146
- generateRules(candidates, context)
164
+ env.DEBUG && console.time('Sorting candidates')
165
+ let sortedCandidates = __OXIDE__
166
+ ? candidates
167
+ : new Set(
168
+ [...candidates].sort((a, z) => {
169
+ if (a === z) return 0
170
+ if (a < z) return -1
171
+ return 1
172
+ })
173
+ )
174
+ env.DEBUG && console.timeEnd('Sorting candidates')
175
+ generateRules(sortedCandidates, context)
147
176
  env.DEBUG && console.timeEnd('Generate rules')
148
177
 
149
178
  // We only ever add to the classCache, so if it didn't grow, there is nothing new.
@@ -3,16 +3,21 @@ import selectorParser from 'postcss-selector-parser'
3
3
  import parseObjectStyles from '../util/parseObjectStyles'
4
4
  import isPlainObject from '../util/isPlainObject'
5
5
  import prefixSelector from '../util/prefixSelector'
6
- import { updateAllClasses, filterSelectorsForClass, getMatchingTypes } from '../util/pluginUtils'
6
+ import { updateAllClasses, getMatchingTypes } from '../util/pluginUtils'
7
7
  import log from '../util/log'
8
8
  import * as sharedState from './sharedState'
9
- import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector'
9
+ import {
10
+ formatVariantSelector,
11
+ finalizeSelector,
12
+ eliminateIrrelevantSelectors,
13
+ } from '../util/formatVariantSelector'
10
14
  import { asClass } from '../util/nameClass'
11
15
  import { normalize } from '../util/dataTypes'
12
16
  import { isValidVariantFormatString, parseVariant } from './setupContextUtils'
13
17
  import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue'
14
18
  import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js'
15
19
  import { flagEnabled } from '../featureFlags'
20
+ import { applyImportantSelector } from '../util/applyImportantSelector'
16
21
 
17
22
  let classNameParser = selectorParser((selectors) => {
18
23
  return selectors.first.filter(({ type }) => type === 'class').pop().value
@@ -111,22 +116,28 @@ function applyImportant(matches, classCandidate) {
111
116
  if (matches.length === 0) {
112
117
  return matches
113
118
  }
119
+
114
120
  let result = []
115
121
 
116
122
  for (let [meta, rule] of matches) {
117
123
  let container = postcss.root({ nodes: [rule.clone()] })
124
+
118
125
  container.walkRules((r) => {
119
- r.selector = updateAllClasses(
120
- filterSelectorsForClass(r.selector, classCandidate),
121
- (className) => {
122
- if (className === classCandidate) {
123
- return `!${className}`
124
- }
125
- return className
126
- }
126
+ let ast = selectorParser().astSync(r.selector)
127
+
128
+ // Remove extraneous selectors that do not include the base candidate
129
+ ast.each((sel) => eliminateIrrelevantSelectors(sel, classCandidate))
130
+
131
+ // Update all instances of the base candidate to include the important marker
132
+ updateAllClasses(ast, (className) =>
133
+ className === classCandidate ? `!${className}` : className
127
134
  )
135
+
136
+ r.selector = ast.toString()
137
+
128
138
  r.walkDecls((d) => (d.important = true))
129
139
  })
140
+
130
141
  result.push([{ ...meta, important: true }, container.nodes[0]])
131
142
  }
132
143
 
@@ -152,10 +163,18 @@ function applyVariant(variant, matches, context) {
152
163
 
153
164
  // Retrieve "modifier"
154
165
  {
155
- let match = /(.*)\/(.*)$/g.exec(variant)
156
- if (match) {
157
- variant = match[1]
158
- args.modifier = match[2]
166
+ let [baseVariant, ...modifiers] = splitAtTopLevelOnly(variant, '/')
167
+
168
+ // This is a hack to support variants with `/` in them, like `ar-1/10/20:text-red-500`
169
+ // In this case 1/10 is a value but /20 is a modifier
170
+ if (modifiers.length > 1) {
171
+ baseVariant = baseVariant + '/' + modifiers.slice(0, -1).join('/')
172
+ modifiers = modifiers.slice(-1)
173
+ }
174
+
175
+ if (modifiers.length && !context.variantMap.has(variant)) {
176
+ variant = baseVariant
177
+ args.modifier = modifiers[0]
159
178
 
160
179
  if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
161
180
  return []
@@ -187,20 +206,30 @@ function applyVariant(variant, matches, context) {
187
206
 
188
207
  // Register arbitrary variants
189
208
  if (isArbitraryValue(variant) && !context.variantMap.has(variant)) {
209
+ let sort = context.offsets.recordVariant(variant)
210
+
190
211
  let selector = normalize(variant.slice(1, -1))
212
+ let selectors = splitAtTopLevelOnly(selector, ',')
191
213
 
192
- if (!isValidVariantFormatString(selector)) {
214
+ // We do not support multiple selectors for arbitrary variants
215
+ if (selectors.length > 1) {
193
216
  return []
194
217
  }
195
218
 
196
- let fn = parseVariant(selector)
219
+ if (!selectors.every(isValidVariantFormatString)) {
220
+ return []
221
+ }
197
222
 
198
- let sort = context.offsets.recordVariant(variant)
223
+ let records = selectors.map((sel, idx) => [
224
+ context.offsets.applyParallelOffset(sort, idx),
225
+ parseVariant(sel.trim()),
226
+ ])
199
227
 
200
- context.variantMap.set(variant, [[sort, fn]])
228
+ context.variantMap.set(variant, records)
201
229
  }
202
230
 
203
231
  if (context.variantMap.has(variant)) {
232
+ let isArbitraryVariant = isArbitraryValue(variant)
204
233
  let variantFunctionTuples = context.variantMap.get(variant).slice()
205
234
  let result = []
206
235
 
@@ -262,7 +291,10 @@ function applyVariant(variant, matches, context) {
262
291
  clone.append(wrapper)
263
292
  },
264
293
  format(selectorFormat) {
265
- collectedFormats.push(selectorFormat)
294
+ collectedFormats.push({
295
+ format: selectorFormat,
296
+ isArbitraryVariant,
297
+ })
266
298
  },
267
299
  args,
268
300
  })
@@ -288,7 +320,10 @@ function applyVariant(variant, matches, context) {
288
320
  }
289
321
 
290
322
  if (typeof ruleWithVariant === 'string') {
291
- collectedFormats.push(ruleWithVariant)
323
+ collectedFormats.push({
324
+ format: ruleWithVariant,
325
+ isArbitraryVariant,
326
+ })
292
327
  }
293
328
 
294
329
  if (ruleWithVariant === null) {
@@ -329,7 +364,10 @@ function applyVariant(variant, matches, context) {
329
364
  // modified (by plugin): .foo .foo\\:markdown > p
330
365
  // rebuiltBase (internal): .foo\\:markdown > p
331
366
  // format: .foo &
332
- collectedFormats.push(modified.replace(rebuiltBase, '&'))
367
+ collectedFormats.push({
368
+ format: modified.replace(rebuiltBase, '&'),
369
+ isArbitraryVariant,
370
+ })
333
371
  rule.selector = before
334
372
  })
335
373
  }
@@ -349,7 +387,6 @@ function applyVariant(variant, matches, context) {
349
387
  Object.assign(args, context.variantOptions.get(variant))
350
388
  ),
351
389
  collectedFormats: (meta.collectedFormats ?? []).concat(collectedFormats),
352
- isArbitraryVariant: isArbitraryValue(variant),
353
390
  },
354
391
  clone.nodes[0],
355
392
  ]
@@ -412,7 +449,7 @@ function isParsableNode(node) {
412
449
  let isParsable = true
413
450
 
414
451
  node.walkDecls((decl) => {
415
- if (!isParsableCssValue(decl.name, decl.value)) {
452
+ if (!isParsableCssValue(decl.prop, decl.value)) {
416
453
  isParsable = false
417
454
  return false
418
455
  }
@@ -736,24 +773,13 @@ function* resolveMatches(candidate, context, original = candidate) {
736
773
  match[1].raws.tailwind = { ...match[1].raws.tailwind, candidate }
737
774
 
738
775
  // Apply final format selector
739
- if (match[0].collectedFormats) {
740
- let finalFormat = formatVariantSelector('&', ...match[0].collectedFormats)
741
- let container = postcss.root({ nodes: [match[1].clone()] })
742
- container.walkRules((rule) => {
743
- if (inKeyframes(rule)) return
744
-
745
- rule.selector = finalizeSelector(finalFormat, {
746
- selector: rule.selector,
747
- candidate: original,
748
- base: candidate
749
- .split(new RegExp(`\\${context?.tailwindConfig?.separator ?? ':'}(?![^[]*\\])`))
750
- .pop(),
751
- isArbitraryVariant: match[0].isArbitraryVariant,
752
-
753
- context,
754
- })
755
- })
756
- match[1] = container.nodes[0]
776
+ match = applyFinalFormat(match, { context, candidate, original })
777
+
778
+ // Skip rules with invalid selectors
779
+ // This will cause the candidate to be added to the "not class"
780
+ // cache skipping it entirely for future builds
781
+ if (match === null) {
782
+ continue
757
783
  }
758
784
 
759
785
  yield match
@@ -761,6 +787,62 @@ function* resolveMatches(candidate, context, original = candidate) {
761
787
  }
762
788
  }
763
789
 
790
+ function applyFinalFormat(match, { context, candidate, original }) {
791
+ if (!match[0].collectedFormats) {
792
+ return match
793
+ }
794
+
795
+ let isValid = true
796
+ let finalFormat
797
+
798
+ try {
799
+ finalFormat = formatVariantSelector(match[0].collectedFormats, {
800
+ context,
801
+ candidate,
802
+ })
803
+ } catch {
804
+ // The format selector we produced is invalid
805
+ // This could be because:
806
+ // - A bug exists
807
+ // - A plugin introduced an invalid variant selector (ex: `addVariant('foo', '&;foo')`)
808
+ // - The user used an invalid arbitrary variant (ex: `[&;foo]:underline`)
809
+ // Either way the build will fail because of this
810
+ // We would rather that the build pass "silently" given that this could
811
+ // happen because of picking up invalid things when scanning content
812
+ // So we'll throw out the candidate instead
813
+
814
+ return null
815
+ }
816
+
817
+ let container = postcss.root({ nodes: [match[1].clone()] })
818
+
819
+ container.walkRules((rule) => {
820
+ if (inKeyframes(rule)) {
821
+ return
822
+ }
823
+
824
+ try {
825
+ rule.selector = finalizeSelector(rule.selector, finalFormat, {
826
+ candidate: original,
827
+ context,
828
+ })
829
+ } catch {
830
+ // If this selector is invalid we also want to skip it
831
+ // But it's likely that being invalid here means there's a bug in a plugin rather than too loosely matching content
832
+ isValid = false
833
+ return false
834
+ }
835
+ })
836
+
837
+ if (!isValid) {
838
+ return null
839
+ }
840
+
841
+ match[1] = container.nodes[0]
842
+
843
+ return match
844
+ }
845
+
764
846
  function inKeyframes(rule) {
765
847
  return rule.parent && rule.parent.type === 'atrule' && rule.parent.name === 'keyframes'
766
848
  }
@@ -787,7 +869,7 @@ function getImportantStrategy(important) {
787
869
  }
788
870
 
789
871
  rule.selectors = rule.selectors.map((selector) => {
790
- return `${important} ${selector}`
872
+ return applyImportantSelector(selector, important)
791
873
  })
792
874
  }
793
875
  }
@@ -1,39 +1,79 @@
1
1
  import fs from 'fs'
2
2
  import path from 'path'
3
- import resolve from 'resolve'
4
- import detective from 'detective'
5
3
 
6
- function createModule(file) {
7
- const source = fs.readFileSync(file, 'utf-8')
8
- const requires = detective(source)
4
+ let jsExtensions = ['.js', '.cjs', '.mjs']
9
5
 
10
- return { file, requires }
6
+ // Given the current file `a.ts`, we want to make sure that when importing `b` that we resolve
7
+ // `b.ts` before `b.js`
8
+ //
9
+ // E.g.:
10
+ //
11
+ // a.ts
12
+ // b // .ts
13
+ // c // .ts
14
+ // a.js
15
+ // b // .js or .ts
16
+
17
+ let jsResolutionOrder = ['', '.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.jsx', '.tsx']
18
+ let tsResolutionOrder = ['', '.ts', '.cts', '.mts', '.tsx', '.js', '.cjs', '.mjs', '.jsx']
19
+
20
+ function resolveWithExtension(file, extensions) {
21
+ // Try to find `./a.ts`, `./a.ts`, ... from `./a`
22
+ for (let ext of extensions) {
23
+ let full = `${file}${ext}`
24
+ if (fs.existsSync(full) && fs.statSync(full).isFile()) {
25
+ return full
26
+ }
27
+ }
28
+
29
+ // Try to find `./a/index.js` from `./a`
30
+ for (let ext of extensions) {
31
+ let full = `${file}/index${ext}`
32
+ if (fs.existsSync(full)) {
33
+ return full
34
+ }
35
+ }
36
+
37
+ return null
11
38
  }
12
39
 
13
- export default function getModuleDependencies(entryFile) {
14
- const rootModule = createModule(entryFile)
15
- const modules = [rootModule]
16
-
17
- // Iterate over the modules, even when new
18
- // ones are being added
19
- for (const mdl of modules) {
20
- mdl.requires
21
- .filter((dep) => {
22
- // Only track local modules, not node_modules
23
- return dep.startsWith('./') || dep.startsWith('../')
24
- })
25
- .forEach((dep) => {
26
- try {
27
- const basedir = path.dirname(mdl.file)
28
- const depPath = resolve.sync(dep, { basedir })
29
- const depModule = createModule(depPath)
30
-
31
- modules.push(depModule)
32
- } catch (_err) {
33
- // eslint-disable-next-line no-empty
34
- }
35
- })
40
+ function* _getModuleDependencies(filename, base, seen, ext = path.extname(filename)) {
41
+ // Try to find the file
42
+ let absoluteFile = resolveWithExtension(
43
+ path.resolve(base, filename),
44
+ jsExtensions.includes(ext) ? jsResolutionOrder : tsResolutionOrder
45
+ )
46
+ if (absoluteFile === null) return // File doesn't exist
47
+
48
+ // Prevent infinite loops when there are circular dependencies
49
+ if (seen.has(absoluteFile)) return // Already seen
50
+ seen.add(absoluteFile)
51
+
52
+ // Mark the file as a dependency
53
+ yield absoluteFile
54
+
55
+ // Resolve new base for new imports/requires
56
+ base = path.dirname(absoluteFile)
57
+ ext = path.extname(absoluteFile)
58
+
59
+ let contents = fs.readFileSync(absoluteFile, 'utf-8')
60
+
61
+ // Find imports/requires
62
+ for (let match of [
63
+ ...contents.matchAll(/import[\s\S]*?['"](.{3,}?)['"]/gi),
64
+ ...contents.matchAll(/import[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi),
65
+ ...contents.matchAll(/require\(['"`](.+)['"`]\)/gi),
66
+ ]) {
67
+ // Bail out if it's not a relative file
68
+ if (!match[1].startsWith('.')) continue
69
+
70
+ yield* _getModuleDependencies(match[1], base, seen, ext)
36
71
  }
72
+ }
37
73
 
38
- return modules
74
+ export default function getModuleDependencies(absoluteFilePath) {
75
+ if (absoluteFilePath === null) return new Set()
76
+ return new Set(
77
+ _getModuleDependencies(absoluteFilePath, path.dirname(absoluteFilePath), new Set())
78
+ )
39
79
  }
@@ -0,0 +1,31 @@
1
+ import jitiFactory from 'jiti'
2
+ import { transform } from 'sucrase'
3
+
4
+ import { Config } from '../../types/config'
5
+
6
+ let jiti: ReturnType<typeof jitiFactory> | null = null
7
+ function lazyJiti() {
8
+ return (
9
+ jiti ??
10
+ (jiti = jitiFactory(__filename, {
11
+ interopDefault: true,
12
+ transform: (opts) => {
13
+ return transform(opts.source, {
14
+ transforms: ['typescript', 'imports'],
15
+ })
16
+ },
17
+ }))
18
+ )
19
+ }
20
+
21
+ export function loadConfig(path: string): Config {
22
+ let config = (function () {
23
+ try {
24
+ return path ? require(path) : {}
25
+ } catch {
26
+ return lazyJiti()(path)
27
+ }
28
+ })()
29
+
30
+ return config.default ?? config
31
+ }
@@ -1,6 +1,7 @@
1
1
  // @ts-check
2
2
 
3
3
  import bigSign from '../util/bigSign'
4
+ import { remapBitfield } from './remap-bitfield.js'
4
5
 
5
6
  /**
6
7
  * @typedef {'base' | 'defaults' | 'components' | 'utilities' | 'variants' | 'user'} Layer
@@ -12,6 +13,7 @@ import bigSign from '../util/bigSign'
12
13
  * @property {function | undefined} sort The sort function
13
14
  * @property {string|null} value The value we want to compare
14
15
  * @property {string|null} modifier The modifier that was used (if any)
16
+ * @property {bigint} variant The variant bitmask
15
17
  */
16
18
 
17
19
  /**
@@ -126,6 +128,8 @@ export class Offsets {
126
128
  * @returns {RuleOffset}
127
129
  */
128
130
  applyVariantOffset(rule, variant, options) {
131
+ options.variant = variant.variants
132
+
129
133
  return {
130
134
  ...rule,
131
135
  layer: 'variants',
@@ -190,7 +194,7 @@ export class Offsets {
190
194
 
191
195
  return {
192
196
  ...this.create('variants'),
193
- variants: 1n << this.reservedVariantBits,
197
+ variants: this.variantOffsets.get(variant),
194
198
  }
195
199
  }
196
200
 
@@ -205,11 +209,30 @@ export class Offsets {
205
209
  return this.layerPositions[a.layer] - this.layerPositions[b.layer]
206
210
  }
207
211
 
212
+ // When sorting the `variants` layer, we need to sort based on the parent layer as well within
213
+ // this variants layer.
214
+ if (a.parentLayer !== b.parentLayer) {
215
+ return this.layerPositions[a.parentLayer] - this.layerPositions[b.parentLayer]
216
+ }
217
+
208
218
  // Sort based on the sorting function
209
219
  for (let aOptions of a.options) {
210
220
  for (let bOptions of b.options) {
211
221
  if (aOptions.id !== bOptions.id) continue
212
222
  if (!aOptions.sort || !bOptions.sort) continue
223
+
224
+ let maxFnVariant = max([aOptions.variant, bOptions.variant]) ?? 0n
225
+
226
+ // Create a mask of 0s from bits 1..N where N represents the mask of the Nth bit
227
+ let mask = ~(maxFnVariant | (maxFnVariant - 1n))
228
+ let aVariantsAfterFn = a.variants & mask
229
+ let bVariantsAfterFn = b.variants & mask
230
+
231
+ // If the variants the same, we _can_ sort them
232
+ if (aVariantsAfterFn !== bVariantsAfterFn) {
233
+ continue
234
+ }
235
+
213
236
  let result = aOptions.sort(
214
237
  {
215
238
  value: aOptions.value,
@@ -243,12 +266,68 @@ export class Offsets {
243
266
  return a.index - b.index
244
267
  }
245
268
 
269
+ /**
270
+ * Arbitrary variants are recorded in the order they're encountered.
271
+ * This means that the order is not stable between environments and sets of content files.
272
+ *
273
+ * In order to make the order stable, we need to remap the arbitrary variant offsets to
274
+ * be in alphabetical order starting from the offset of the first arbitrary variant.
275
+ */
276
+ recalculateVariantOffsets() {
277
+ // Sort the variants by their name
278
+ let variants = Array.from(this.variantOffsets.entries())
279
+ .filter(([v]) => v.startsWith('['))
280
+ .sort(([a], [z]) => fastCompare(a, z))
281
+
282
+ // Sort the list of offsets
283
+ // This is not necessarily a discrete range of numbers which is why
284
+ // we're using sort instead of creating a range from min/max
285
+ let newOffsets = variants.map(([, offset]) => offset).sort((a, z) => bigSign(a - z))
286
+
287
+ // Create a map from the old offsets to the new offsets in the new sort order
288
+ /** @type {[bigint, bigint][]} */
289
+ let mapping = variants.map(([, oldOffset], i) => [oldOffset, newOffsets[i]])
290
+
291
+ // Remove any variants that will not move letting us skip
292
+ // remapping if everything happens to be in order
293
+ return mapping.filter(([a, z]) => a !== z)
294
+ }
295
+
296
+ /**
297
+ * @template T
298
+ * @param {[RuleOffset, T][]} list
299
+ * @returns {[RuleOffset, T][]}
300
+ */
301
+ remapArbitraryVariantOffsets(list) {
302
+ let mapping = this.recalculateVariantOffsets()
303
+
304
+ // No arbitrary variants? Nothing to do.
305
+ // Everyhing already in order? Nothing to do.
306
+ if (mapping.length === 0) {
307
+ return list
308
+ }
309
+
310
+ // Remap every variant offset in the list
311
+ return list.map((item) => {
312
+ let [offset, rule] = item
313
+
314
+ offset = {
315
+ ...offset,
316
+ variants: remapBitfield(offset.variants, mapping),
317
+ }
318
+
319
+ return [offset, rule]
320
+ })
321
+ }
322
+
246
323
  /**
247
324
  * @template T
248
325
  * @param {[RuleOffset, T][]} list
249
326
  * @returns {[RuleOffset, T][]}
250
327
  */
251
328
  sort(list) {
329
+ list = this.remapArbitraryVariantOffsets(list)
330
+
252
331
  return list.sort(([a], [b]) => bigSign(this.compare(a, b)))
253
332
  }
254
333
  }
@@ -268,3 +347,27 @@ function max(nums) {
268
347
 
269
348
  return max
270
349
  }
350
+
351
+ /**
352
+ * A fast ASCII order string comparison function.
353
+ *
354
+ * Using `.sort()` without a custom compare function is faster
355
+ * But you can only use that if you're sorting an array of
356
+ * only strings. If you're sorting strings inside objects
357
+ * or arrays, you need must use a custom compare function.
358
+ *
359
+ * @param {string} a
360
+ * @param {string} b
361
+ */
362
+ function fastCompare(a, b) {
363
+ let aLen = a.length
364
+ let bLen = b.length
365
+ let minLen = aLen < bLen ? aLen : bLen
366
+
367
+ for (let i = 0; i < minLen; i++) {
368
+ let cmp = a.charCodeAt(i) - b.charCodeAt(i)
369
+ if (cmp !== 0) return cmp
370
+ }
371
+
372
+ return aLen - bLen
373
+ }