Skip to content

Commit 4341237

Browse files
committed
handle the prefix inside the group/peer variants
Then add the `NoPrefix` feature to the variant itself, which will skip prefixing any other class in the generated selector (because we already took care of prefixing `.group` and `.peer`). We are using an internal symbol such that: - We can keep it as a private API - We don't introduce a breaking change
1 parent 8e2a3df commit 4341237

8 files changed

+109
-103
lines changed

src/corePlugins.js

+9-12
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadow
2121
import { removeAlphaVariables } from './util/removeAlphaVariables'
2222
import { flagEnabled } from './featureFlags'
2323
import { normalize } from './util/dataTypes'
24+
import { Features } from './lib/setupContextUtils'
2425

2526
export let variantPlugins = {
2627
pseudoElementVariants: ({ addVariant }) => {
@@ -79,7 +80,7 @@ export let variantPlugins = {
7980
})
8081
},
8182

82-
pseudoClassVariants: ({ addVariant, matchVariant, config }) => {
83+
pseudoClassVariants: ({ addVariant, matchVariant, config, prefix }) => {
8384
let pseudoVariants = [
8485
// Positional
8586
['first', '&:first-child'],
@@ -150,12 +151,12 @@ export let variantPlugins = {
150151
let variants = {
151152
group: (_, { modifier }) =>
152153
modifier
153-
? [`:merge(.group\\/${escapeClassName(modifier)})`, ' &']
154-
: [`:merge(.group)`, ' &'],
154+
? [`:merge(${prefix('.group')}\\/${escapeClassName(modifier)})`, ' &']
155+
: [`:merge(${prefix('.group')})`, ' &'],
155156
peer: (_, { modifier }) =>
156157
modifier
157-
? [`:merge(.peer\\/${escapeClassName(modifier)})`, ' ~ &']
158-
: [`:merge(.peer)`, ' ~ &'],
158+
? [`:merge(${prefix('.peer')}\\/${escapeClassName(modifier)})`, ' ~ &']
159+
: [`:merge(${prefix('.peer')})`, ' ~ &'],
159160
}
160161

161162
for (let [name, fn] of Object.entries(variants)) {
@@ -187,15 +188,11 @@ export let variantPlugins = {
187188
}
188189

189190
// Basically this but can handle quotes:
190-
// result.replace(/&(\S+)?/g, (_, pseudo = '') => a + `:tw-no-prefix(${pseudo})` + b)
191+
// result.replace(/&(\S+)?/g, (_, pseudo = '') => a + pseudo + b)
191192

192-
let pseudo = result.slice(start + 1, end)
193-
194-
pseudo = config('prefix') ? `:tw-no-prefix(${pseudo})` : pseudo
195-
196-
return result.slice(0, start) + a + pseudo + b + result.slice(end)
193+
return result.slice(0, start) + a + result.slice(start + 1, end) + b + result.slice(end)
197194
},
198-
{ values: Object.fromEntries(pseudoVariants) }
195+
{ values: Object.fromEntries(pseudoVariants), [Features]: Features.RespectPrefix }
199196
)
200197
}
201198
},

src/lib/generateRules.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from '../util/formatVariantSelector'
1414
import { asClass } from '../util/nameClass'
1515
import { normalize } from '../util/dataTypes'
16-
import { isValidVariantFormatString, parseVariant } from './setupContextUtils'
16+
import { isValidVariantFormatString, parseVariant, Features } from './setupContextUtils'
1717
import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue'
1818
import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js'
1919
import { flagEnabled } from '../featureFlags'
@@ -226,9 +226,16 @@ function applyVariant(variant, matches, context) {
226226

227227
if (context.variantMap.has(variant)) {
228228
let isArbitraryVariant = isArbitraryValue(variant)
229+
let features = context.variantOptions.get(variant)?.[Features] ?? Features.None
229230
let variantFunctionTuples = context.variantMap.get(variant).slice()
230231
let result = []
231232

233+
let respectPrefix = (() => {
234+
if (isArbitraryVariant) return false
235+
if ((features & Features.RespectPrefix) === Features.RespectPrefix) return false
236+
return true
237+
})()
238+
232239
for (let [meta, rule] of matches) {
233240
// Don't generate variants for user css
234241
if (meta.layer === 'user') {
@@ -289,7 +296,7 @@ function applyVariant(variant, matches, context) {
289296
format(selectorFormat) {
290297
collectedFormats.push({
291298
format: selectorFormat,
292-
isArbitraryVariant,
299+
respectPrefix,
293300
})
294301
},
295302
args,
@@ -318,7 +325,7 @@ function applyVariant(variant, matches, context) {
318325
if (typeof ruleWithVariant === 'string') {
319326
collectedFormats.push({
320327
format: ruleWithVariant,
321-
isArbitraryVariant,
328+
respectPrefix,
322329
})
323330
}
324331

@@ -362,7 +369,7 @@ function applyVariant(variant, matches, context) {
362369
// format: .foo &
363370
collectedFormats.push({
364371
format: modified.replace(rebuiltBase, '&'),
365-
isArbitraryVariant,
372+
respectPrefix,
366373
})
367374
rule.selector = before
368375
})

src/lib/setupContextUtils.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ import { hasContentChanged } from './cacheInvalidation.js'
2323
import { Offsets } from './offsets.js'
2424
import { finalizeSelector, formatVariantSelector } from '../util/formatVariantSelector'
2525

26+
export const Features = Object.assign(Symbol(), {
27+
// No features are enabled
28+
None: 0,
29+
30+
// Whether or not we should respect the prefix
31+
RespectPrefix: 1 << 0,
32+
})
33+
2634
const VARIANT_TYPES = {
2735
AddVariant: Symbol.for('ADD_VARIANT'),
2836
MatchVariant: Symbol.for('MATCH_VARIANT'),
@@ -1110,17 +1118,24 @@ function registerPlugins(plugins, context) {
11101118
}
11111119

11121120
let isArbitraryVariant = !(value in (options.values ?? {}))
1121+
let features = options[Features] ?? Features.None
1122+
1123+
let respectPrefix = (() => {
1124+
if (isArbitraryVariant) return false
1125+
if ((features & Features.RespectPrefix) === Features.RespectPrefix) return false
1126+
return true
1127+
})()
11131128

11141129
formatStrings = formatStrings.map((format) =>
11151130
format.map((str) => ({
11161131
format: str,
1117-
isArbitraryVariant,
1132+
respectPrefix,
11181133
}))
11191134
)
11201135

11211136
manualFormatStrings = manualFormatStrings.map((format) => ({
11221137
format,
1123-
isArbitraryVariant,
1138+
respectPrefix,
11241139
}))
11251140

11261141
let opts = {

src/util/formatVariantSelector.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { movePseudos } from './pseudoElements'
99
/** @typedef {import('postcss-selector-parser').Pseudo} Pseudo */
1010
/** @typedef {import('postcss-selector-parser').Node} Node */
1111

12-
/** @typedef {{format: string, isArbitraryVariant: boolean}[]} RawFormats */
12+
/** @typedef {{format: string, respectPrefix: boolean}[]} RawFormats */
1313
/** @typedef {import('postcss-selector-parser').Root} ParsedFormats */
1414
/** @typedef {RawFormats | ParsedFormats} AcceptedFormats */
1515

@@ -29,7 +29,7 @@ export function formatVariantSelector(formats, { context, candidate }) {
2929

3030
return {
3131
...format,
32-
ast: format.isArbitraryVariant ? ast : prefixSelector(prefix, ast),
32+
ast: format.respectPrefix ? prefixSelector(prefix, ast) : ast,
3333
}
3434
})
3535

src/util/prefixSelector.js

+8-27
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,14 @@ export default function (prefix, selector, prependNegative = false) {
2020
/** @type {import('postcss-selector-parser').Root} */
2121
let ast = typeof selector === 'string' ? parser().astSync(selector) : selector
2222

23-
// ast.walk bails too early when returning so it's not usable here
24-
function prefixClasses(node) {
25-
// Here we look for `:tw-no-prefix` which is an *internal-use-only* marker
26-
// used to stop traversal so we don't replace any classes inside it
27-
if (node.type === 'pseudo' && node.value === ':tw-no-prefix') {
28-
node.replaceWith(...node.nodes)
29-
return
30-
}
31-
32-
// Prefix any classes we find
33-
if (node.type === 'class') {
34-
let baseClass = node.value
35-
let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-')
36-
37-
node.value = shouldPlaceNegativeBeforePrefix
38-
? `-${prefix}${baseClass.slice(1)}`
39-
: `${prefix}${baseClass}`
40-
return
41-
}
42-
43-
// Keep looking for classes
44-
if (node.length) {
45-
node.each(prefixClasses)
46-
}
47-
}
48-
49-
ast.each(prefixClasses)
23+
ast.walkClasses((classSelector) => {
24+
let baseClass = classSelector.value
25+
let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-')
26+
27+
classSelector.value = shouldPlaceNegativeBeforePrefix
28+
? `-${prefix}${baseClass.slice(1)}`
29+
: `${prefix}${baseClass}`
30+
})
5031

5132
return typeof selector === 'string' ? ast.toString() : ast
5233
}

0 commit comments

Comments
 (0)