Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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
  • Loading branch information
RobinMalfait committed Jun 28, 2023
commit 504fd1956f3a922b7e773e78aea55633b6b6877c
21 changes: 9 additions & 12 deletions src/corePlugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { formatBoxShadowValue, parseBoxShadowValue } from './util/parseBoxShadow
import { removeAlphaVariables } from './util/removeAlphaVariables'
import { flagEnabled } from './featureFlags'
import { normalize } from './util/dataTypes'
import { Features } from './lib/setupContextUtils'

export let variantPlugins = {
pseudoElementVariants: ({ addVariant }) => {
Expand Down Expand Up @@ -79,7 +80,7 @@ export let variantPlugins = {
})
},

pseudoClassVariants: ({ addVariant, matchVariant, config }) => {
pseudoClassVariants: ({ addVariant, matchVariant, config, prefix }) => {
let pseudoVariants = [
// Positional
['first', '&:first-child'],
Expand Down Expand Up @@ -150,12 +151,12 @@ export let variantPlugins = {
let variants = {
group: (_, { modifier }) =>
modifier
? [`:merge(.group\\/${escapeClassName(modifier)})`, ' &']
: [`:merge(.group)`, ' &'],
? [`:merge(${prefix('.group')}\\/${escapeClassName(modifier)})`, ' &']
: [`:merge(${prefix('.group')})`, ' &'],
peer: (_, { modifier }) =>
modifier
? [`:merge(.peer\\/${escapeClassName(modifier)})`, ' ~ &']
: [`:merge(.peer)`, ' ~ &'],
? [`:merge(${prefix('.peer')}\\/${escapeClassName(modifier)})`, ' ~ &']
: [`:merge(${prefix('.peer')})`, ' ~ &'],
}

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

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

let pseudo = result.slice(start + 1, end)

pseudo = config('prefix') ? `:tw-no-prefix(${pseudo})` : pseudo

return result.slice(0, start) + a + pseudo + b + result.slice(end)
return result.slice(0, start) + a + result.slice(start + 1, end) + b + result.slice(end)
},
{ values: Object.fromEntries(pseudoVariants) }
{ values: Object.fromEntries(pseudoVariants), [Features]: Features.NoPrefix }
)
}
},
Expand Down
15 changes: 11 additions & 4 deletions src/lib/generateRules.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from '../util/formatVariantSelector'
import { asClass } from '../util/nameClass'
import { normalize } from '../util/dataTypes'
import { isValidVariantFormatString, parseVariant } from './setupContextUtils'
import { isValidVariantFormatString, parseVariant, Features } from './setupContextUtils'
import isValidArbitraryValue from '../util/isSyntacticallyValidPropertyValue'
import { splitAtTopLevelOnly } from '../util/splitAtTopLevelOnly.js'
import { flagEnabled } from '../featureFlags'
Expand Down Expand Up @@ -226,9 +226,16 @@ function applyVariant(variant, matches, context) {

if (context.variantMap.has(variant)) {
let isArbitraryVariant = isArbitraryValue(variant)
let features = context.variantOptions.get(variant)?.[Features] ?? Features.None
let variantFunctionTuples = context.variantMap.get(variant).slice()
let result = []

let respectPrefix = (() => {
if (isArbitraryVariant) return false
if ((features & Features.NoPrefix) === Features.NoPrefix) return false
return true
})()

for (let [meta, rule] of matches) {
// Don't generate variants for user css
if (meta.layer === 'user') {
Expand Down Expand Up @@ -289,7 +296,7 @@ function applyVariant(variant, matches, context) {
format(selectorFormat) {
collectedFormats.push({
format: selectorFormat,
isArbitraryVariant,
respectPrefix,
})
},
args,
Expand Down Expand Up @@ -318,7 +325,7 @@ function applyVariant(variant, matches, context) {
if (typeof ruleWithVariant === 'string') {
collectedFormats.push({
format: ruleWithVariant,
isArbitraryVariant,
respectPrefix,
})
}

Expand Down Expand Up @@ -362,7 +369,7 @@ function applyVariant(variant, matches, context) {
// format: .foo &
collectedFormats.push({
format: modified.replace(rebuiltBase, '&'),
isArbitraryVariant,
respectPrefix,
})
rule.selector = before
})
Expand Down
19 changes: 17 additions & 2 deletions src/lib/setupContextUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ import { hasContentChanged } from './cacheInvalidation.js'
import { Offsets } from './offsets.js'
import { finalizeSelector, formatVariantSelector } from '../util/formatVariantSelector'

export const Features = Object.assign(Symbol(), {
// No features are enabled
None: 0,

// Whether or not we should respect the prefix
NoPrefix: 1 << 0,
})

const VARIANT_TYPES = {
AddVariant: Symbol.for('ADD_VARIANT'),
MatchVariant: Symbol.for('MATCH_VARIANT'),
Expand Down Expand Up @@ -1110,17 +1118,24 @@ function registerPlugins(plugins, context) {
}

let isArbitraryVariant = !(value in (options.values ?? {}))
let features = options[Features] ?? Features.None

let respectPrefix = (() => {
if (isArbitraryVariant) return false
if ((features & Features.NoPrefix) === Features.NoPrefix) return false
return true
})()

formatStrings = formatStrings.map((format) =>
format.map((str) => ({
format: str,
isArbitraryVariant,
respectPrefix,
}))
)

manualFormatStrings = manualFormatStrings.map((format) => ({
format,
isArbitraryVariant,
respectPrefix,
}))

let opts = {
Expand Down
4 changes: 2 additions & 2 deletions src/util/formatVariantSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { movePseudos } from './pseudoElements'
/** @typedef {import('postcss-selector-parser').Pseudo} Pseudo */
/** @typedef {import('postcss-selector-parser').Node} Node */

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

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

return {
...format,
ast: format.isArbitraryVariant ? ast : prefixSelector(prefix, ast),
ast: format.respectPrefix ? prefixSelector(prefix, ast) : ast,
}
})

Expand Down
35 changes: 8 additions & 27 deletions src/util/prefixSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,14 @@ export default function (prefix, selector, prependNegative = false) {
/** @type {import('postcss-selector-parser').Root} */
let ast = typeof selector === 'string' ? parser().astSync(selector) : selector

// ast.walk bails too early when returning so it's not usable here
function prefixClasses(node) {
// Here we look for `:tw-no-prefix` which is an *internal-use-only* marker
// used to stop traversal so we don't replace any classes inside it
if (node.type === 'pseudo' && node.value === ':tw-no-prefix') {
node.replaceWith(...node.nodes)
return
}

// Prefix any classes we find
if (node.type === 'class') {
let baseClass = node.value
let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-')

node.value = shouldPlaceNegativeBeforePrefix
? `-${prefix}${baseClass.slice(1)}`
: `${prefix}${baseClass}`
return
}

// Keep looking for classes
if (node.length) {
node.each(prefixClasses)
}
}

ast.each(prefixClasses)
ast.walkClasses((classSelector) => {
let baseClass = classSelector.value
let shouldPlaceNegativeBeforePrefix = prependNegative && baseClass.startsWith('-')

classSelector.value = shouldPlaceNegativeBeforePrefix
? `-${prefix}${baseClass.slice(1)}`
: `${prefix}${baseClass}`
})

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