| 1 | import postcss from 'postcss'
|
|---|
| 2 | import selectorParser from 'postcss-selector-parser'
|
|---|
| 3 | import { flagEnabled } from '../featureFlags'
|
|---|
| 4 |
|
|---|
| 5 | let getNode = {
|
|---|
| 6 | id(node) {
|
|---|
| 7 | return selectorParser.attribute({
|
|---|
| 8 | attribute: 'id',
|
|---|
| 9 | operator: '=',
|
|---|
| 10 | value: node.value,
|
|---|
| 11 | quoteMark: '"',
|
|---|
| 12 | })
|
|---|
| 13 | },
|
|---|
| 14 | }
|
|---|
| 15 |
|
|---|
| 16 | function minimumImpactSelector(nodes) {
|
|---|
| 17 | let rest = nodes
|
|---|
| 18 | .filter((node) => {
|
|---|
| 19 | // Keep non-pseudo nodes
|
|---|
| 20 | if (node.type !== 'pseudo') return true
|
|---|
| 21 |
|
|---|
| 22 | // Keep pseudo nodes that have subnodes
|
|---|
| 23 | // E.g.: `:not()` contains subnodes inside the parentheses
|
|---|
| 24 | if (node.nodes.length > 0) return true
|
|---|
| 25 |
|
|---|
| 26 | // Keep pseudo `elements`
|
|---|
| 27 | // This implicitly means that we ignore pseudo `classes`
|
|---|
| 28 | return (
|
|---|
| 29 | node.value.startsWith('::') ||
|
|---|
| 30 | [':before', ':after', ':first-line', ':first-letter'].includes(node.value)
|
|---|
| 31 | )
|
|---|
| 32 | })
|
|---|
| 33 | .reverse()
|
|---|
| 34 |
|
|---|
| 35 | let searchFor = new Set(['tag', 'class', 'id', 'attribute'])
|
|---|
| 36 |
|
|---|
| 37 | let splitPointIdx = rest.findIndex((n) => searchFor.has(n.type))
|
|---|
| 38 | if (splitPointIdx === -1) return rest.reverse().join('').trim()
|
|---|
| 39 |
|
|---|
| 40 | let node = rest[splitPointIdx]
|
|---|
| 41 | let bestNode = getNode[node.type] ? getNode[node.type](node) : node
|
|---|
| 42 |
|
|---|
| 43 | rest = rest.slice(0, splitPointIdx)
|
|---|
| 44 |
|
|---|
| 45 | let combinatorIdx = rest.findIndex((n) => n.type === 'combinator' && n.value === '>')
|
|---|
| 46 | if (combinatorIdx !== -1) {
|
|---|
| 47 | rest.splice(0, combinatorIdx)
|
|---|
| 48 | rest.unshift(selectorParser.universal())
|
|---|
| 49 | }
|
|---|
| 50 |
|
|---|
| 51 | return [bestNode, ...rest.reverse()].join('').trim()
|
|---|
| 52 | }
|
|---|
| 53 |
|
|---|
| 54 | export let elementSelectorParser = selectorParser((selectors) => {
|
|---|
| 55 | return selectors.map((s) => {
|
|---|
| 56 | let nodes = s.split((n) => n.type === 'combinator' && n.value === ' ').pop()
|
|---|
| 57 | return minimumImpactSelector(nodes)
|
|---|
| 58 | })
|
|---|
| 59 | })
|
|---|
| 60 |
|
|---|
| 61 | let cache = new Map()
|
|---|
| 62 |
|
|---|
| 63 | function extractElementSelector(selector) {
|
|---|
| 64 | if (!cache.has(selector)) {
|
|---|
| 65 | cache.set(selector, elementSelectorParser.transformSync(selector))
|
|---|
| 66 | }
|
|---|
| 67 |
|
|---|
| 68 | return cache.get(selector)
|
|---|
| 69 | }
|
|---|
| 70 |
|
|---|
| 71 | export default function resolveDefaultsAtRules({ tailwindConfig }) {
|
|---|
| 72 | return (root) => {
|
|---|
| 73 | let variableNodeMap = new Map()
|
|---|
| 74 |
|
|---|
| 75 | /** @type {Set<import('postcss').AtRule>} */
|
|---|
| 76 | let universals = new Set()
|
|---|
| 77 |
|
|---|
| 78 | root.walkAtRules('defaults', (rule) => {
|
|---|
| 79 | if (rule.nodes && rule.nodes.length > 0) {
|
|---|
| 80 | universals.add(rule)
|
|---|
| 81 | return
|
|---|
| 82 | }
|
|---|
| 83 |
|
|---|
| 84 | let variable = rule.params
|
|---|
| 85 | if (!variableNodeMap.has(variable)) {
|
|---|
| 86 | variableNodeMap.set(variable, new Set())
|
|---|
| 87 | }
|
|---|
| 88 |
|
|---|
| 89 | variableNodeMap.get(variable).add(rule.parent)
|
|---|
| 90 |
|
|---|
| 91 | rule.remove()
|
|---|
| 92 | })
|
|---|
| 93 |
|
|---|
| 94 | if (flagEnabled(tailwindConfig, 'optimizeUniversalDefaults')) {
|
|---|
| 95 | for (let universal of universals) {
|
|---|
| 96 | /** @type {Map<string, Set<string>>} */
|
|---|
| 97 | let selectorGroups = new Map()
|
|---|
| 98 |
|
|---|
| 99 | let rules = variableNodeMap.get(universal.params) ?? []
|
|---|
| 100 |
|
|---|
| 101 | for (let rule of rules) {
|
|---|
| 102 | for (let selector of extractElementSelector(rule.selector)) {
|
|---|
| 103 | // If selector contains a vendor prefix after a pseudo element or class,
|
|---|
| 104 | // we consider them separately because merging the declarations into
|
|---|
| 105 | // a single rule will cause browsers that do not understand the
|
|---|
| 106 | // vendor prefix to throw out the whole rule
|
|---|
| 107 | // Additionally if a selector contains `:has` we also consider
|
|---|
| 108 | // it separately because FF only recently gained support for it
|
|---|
| 109 | let selectorGroupName =
|
|---|
| 110 | selector.includes(':-') || selector.includes('::-') || selector.includes(':has')
|
|---|
| 111 | ? selector
|
|---|
| 112 | : '__DEFAULT__'
|
|---|
| 113 |
|
|---|
| 114 | let selectors = selectorGroups.get(selectorGroupName) ?? new Set()
|
|---|
| 115 | selectorGroups.set(selectorGroupName, selectors)
|
|---|
| 116 |
|
|---|
| 117 | selectors.add(selector)
|
|---|
| 118 | }
|
|---|
| 119 | }
|
|---|
| 120 |
|
|---|
| 121 | if (selectorGroups.size === 0) {
|
|---|
| 122 | universal.remove()
|
|---|
| 123 | continue
|
|---|
| 124 | }
|
|---|
| 125 |
|
|---|
| 126 | for (let [, selectors] of selectorGroups) {
|
|---|
| 127 | let universalRule = postcss.rule({
|
|---|
| 128 | source: universal.source,
|
|---|
| 129 | })
|
|---|
| 130 |
|
|---|
| 131 | universalRule.selectors = [...selectors]
|
|---|
| 132 |
|
|---|
| 133 | universalRule.append(universal.nodes.map((node) => node.clone()))
|
|---|
| 134 | universal.before(universalRule)
|
|---|
| 135 | }
|
|---|
| 136 |
|
|---|
| 137 | universal.remove()
|
|---|
| 138 | }
|
|---|
| 139 | } else if (universals.size) {
|
|---|
| 140 | let universalRule = postcss.rule({
|
|---|
| 141 | selectors: ['*', '::before', '::after'],
|
|---|
| 142 | })
|
|---|
| 143 |
|
|---|
| 144 | for (let universal of universals) {
|
|---|
| 145 | universalRule.append(universal.nodes)
|
|---|
| 146 |
|
|---|
| 147 | if (!universalRule.parent) {
|
|---|
| 148 | universal.before(universalRule)
|
|---|
| 149 | }
|
|---|
| 150 |
|
|---|
| 151 | if (!universalRule.source) {
|
|---|
| 152 | universalRule.source = universal.source
|
|---|
| 153 | }
|
|---|
| 154 |
|
|---|
| 155 | universal.remove()
|
|---|
| 156 | }
|
|---|
| 157 |
|
|---|
| 158 | let backdropRule = universalRule.clone({
|
|---|
| 159 | selectors: ['::backdrop'],
|
|---|
| 160 | })
|
|---|
| 161 |
|
|---|
| 162 | universalRule.after(backdropRule)
|
|---|
| 163 | }
|
|---|
| 164 | }
|
|---|
| 165 | }
|
|---|