From b1e2c8fd5cb5dfa46bc440a12eafaf56cd844b1c Mon Sep 17 00:00:00 2001 From: Philipp Tanlak Date: Mon, 24 Nov 2025 20:54:57 +0100 Subject: Docs --- .../tailwindcss/src/lib/resolveDefaultsAtRules.js | 163 +++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 node_modules/tailwindcss/src/lib/resolveDefaultsAtRules.js (limited to 'node_modules/tailwindcss/src/lib/resolveDefaultsAtRules.js') diff --git a/node_modules/tailwindcss/src/lib/resolveDefaultsAtRules.js b/node_modules/tailwindcss/src/lib/resolveDefaultsAtRules.js new file mode 100644 index 0000000..389ea4b --- /dev/null +++ b/node_modules/tailwindcss/src/lib/resolveDefaultsAtRules.js @@ -0,0 +1,163 @@ +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) => { + // Keep non-pseudo nodes + if (node.type !== 'pseudo') return true + + // Keep pseudo nodes that have subnodes + // E.g.: `:not()` contains subnodes inside the parentheses + if (node.nodes.length > 0) return true + + // Keep pseudo `elements` + // This implicitly means that we ignore pseudo `classes` + 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() + + /** @type {Set} */ + 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) { + /** @type {Map>} */ + let selectorGroups = new Map() + + let rules = variableNodeMap.get(universal.params) ?? [] + + for (let rule of rules) { + for (let selector of extractElementSelector(rule.selector)) { + // If selector contains a vendor prefix after a pseudo element or class, + // we consider them separately because merging the declarations into + // a single rule will cause browsers that do not understand the + // vendor prefix to throw out the whole rule + 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) + } + } +} -- cgit v1.2.3