- Add npm scripts for CSS/JS compilation (build:css, build:js, build) - Create PowerShell build automation script - Document development workflow in README - Add troubleshooting guide for build issues - Specify proper file structure and compilation process Supports Tailwind CSS v3.4.0 and esbuild bundling with source maps.
166 lines
4.6 KiB
JavaScript
166 lines
4.6 KiB
JavaScript
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<import('postcss').AtRule>} */
|
|
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<string, Set<string>>} */
|
|
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
|
|
// Additionally if a selector contains `:has` we also consider
|
|
// it separately because FF only recently gained support for it
|
|
let selectorGroupName =
|
|
selector.includes(':-') || selector.includes('::-') || selector.includes(':has')
|
|
? selector
|
|
: '__DEFAULT__'
|
|
|
|
let selectors = selectorGroups.get(selectorGroupName) ?? new Set()
|
|
selectorGroups.set(selectorGroupName, selectors)
|
|
|
|
selectors.add(selector)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|