|
import postcss from 'postcss' |
|
import selectorParser from 'postcss-selector-parser' |
|
import { flagEnabled } from '../featureFlags' |
|
|
|
let getNode = { |
|
id(node) { |
|
return selectorParser.attribute({ |
|
attribute: 'id', |
|
operator: '=', |
|
value: node.value, |
|
quoteMark: '"', |
|
}) |
|
}, |
|
} |
|
|
|
function minimumImpactSelector(nodes) { |
|
let rest = nodes |
|
.filter((node) => { |
|
|
|
if (node.type !== 'pseudo') return true |
|
|
|
|
|
|
|
if (node.nodes.length > 0) return true |
|
|
|
|
|
|
|
return ( |
|
node.value.startsWith('::') || |
|
[':before', ':after', ':first-line', ':first-letter'].includes(node.value) |
|
) |
|
}) |
|
.reverse() |
|
|
|
let searchFor = new Set(['tag', 'class', 'id', 'attribute']) |
|
|
|
let splitPointIdx = rest.findIndex((n) => searchFor.has(n.type)) |
|
if (splitPointIdx === -1) return rest.reverse().join('').trim() |
|
|
|
let node = rest[splitPointIdx] |
|
let bestNode = getNode[node.type] ? getNode[node.type](node) : node |
|
|
|
rest = rest.slice(0, splitPointIdx) |
|
|
|
let combinatorIdx = rest.findIndex((n) => n.type === 'combinator' && n.value === '>') |
|
if (combinatorIdx !== -1) { |
|
rest.splice(0, combinatorIdx) |
|
rest.unshift(selectorParser.universal()) |
|
} |
|
|
|
return [bestNode, ...rest.reverse()].join('').trim() |
|
} |
|
|
|
export let elementSelectorParser = selectorParser((selectors) => { |
|
return selectors.map((s) => { |
|
let nodes = s.split((n) => n.type === 'combinator' && n.value === ' ').pop() |
|
return minimumImpactSelector(nodes) |
|
}) |
|
}) |
|
|
|
let cache = new Map() |
|
|
|
function extractElementSelector(selector) { |
|
if (!cache.has(selector)) { |
|
cache.set(selector, elementSelectorParser.transformSync(selector)) |
|
} |
|
|
|
return cache.get(selector) |
|
} |
|
|
|
export default function resolveDefaultsAtRules({ tailwindConfig }) { |
|
return (root) => { |
|
let variableNodeMap = new Map() |
|
|
|
|
|
let universals = new Set() |
|
|
|
root.walkAtRules('defaults', (rule) => { |
|
if (rule.nodes && rule.nodes.length > 0) { |
|
universals.add(rule) |
|
return |
|
} |
|
|
|
let variable = rule.params |
|
if (!variableNodeMap.has(variable)) { |
|
variableNodeMap.set(variable, new Set()) |
|
} |
|
|
|
variableNodeMap.get(variable).add(rule.parent) |
|
|
|
rule.remove() |
|
}) |
|
|
|
if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) { |
|
for (let universal of universals) { |
|
|
|
let selectorGroups = new Map() |
|
|
|
let rules = variableNodeMap.get(universal.params) ?? [] |
|
|
|
for (let rule of rules) { |
|
for (let selector of extractElementSelector(rule.selector)) { |
|
|
|
|
|
|
|
|
|
let selectorGroupName = |
|
selector.includes(':-') || selector.includes('::-') ? selector : '__DEFAULT__' |
|
|
|
let selectors = selectorGroups.get(selectorGroupName) ?? new Set() |
|
selectorGroups.set(selectorGroupName, selectors) |
|
|
|
selectors.add(selector) |
|
} |
|
} |
|
|
|
if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) { |
|
if (selectorGroups.size === 0) { |
|
universal.remove() |
|
continue |
|
} |
|
|
|
for (let [, selectors] of selectorGroups) { |
|
let universalRule = postcss.rule({ |
|
source: universal.source, |
|
}) |
|
|
|
universalRule.selectors = [...selectors] |
|
|
|
universalRule.append(universal.nodes.map((node) => node.clone())) |
|
universal.before(universalRule) |
|
} |
|
} |
|
|
|
universal.remove() |
|
} |
|
} else if (universals.size) { |
|
let universalRule = postcss.rule({ |
|
selectors: ['*', '::before', '::after'], |
|
}) |
|
|
|
for (let universal of universals) { |
|
universalRule.append(universal.nodes) |
|
|
|
if (!universalRule.parent) { |
|
universal.before(universalRule) |
|
} |
|
|
|
if (!universalRule.source) { |
|
universalRule.source = universal.source |
|
} |
|
|
|
universal.remove() |
|
} |
|
|
|
let backdropRule = universalRule.clone({ |
|
selectors: ['::backdrop'], |
|
}) |
|
|
|
universalRule.after(backdropRule) |
|
} |
|
} |
|
} |
|
|