Skip to content

Commit 4a070ac

Browse files
authored
Try to use lowest impact selector when filling in defaults (tailwindlabs#4866)
1 parent 81e9f65 commit 4a070ac

File tree

2 files changed

+134
-4
lines changed

2 files changed

+134
-4
lines changed

src/jit/lib/resolveDefaultsAtRules.js

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,43 @@
11
import postcss from 'postcss'
22
import selectorParser from 'postcss-selector-parser'
33

4+
function minimumImpactSelector(nodes) {
5+
let pseudos = nodes.filter((n) => n.type === 'pseudo')
6+
let [bestNode] = nodes
7+
8+
for (let [type, getNode = (n) => n] of [
9+
['class'],
10+
[
11+
'id',
12+
(n) =>
13+
selectorParser.attribute({
14+
attribute: 'id',
15+
operator: '=',
16+
value: n.value,
17+
quoteMark: '"',
18+
}),
19+
],
20+
['attribute'],
21+
]) {
22+
let match = nodes.find((n) => n.type === type)
23+
24+
if (match) {
25+
bestNode = getNode(match)
26+
break
27+
}
28+
}
29+
30+
return [bestNode, ...pseudos].join('').trim()
31+
}
32+
433
let elementSelectorParser = selectorParser((selectors) => {
534
return selectors.map((s) => {
6-
return s
35+
let nodes = s
736
.split((n) => n.type === 'combinator')
837
.pop()
938
.filter((n) => n.type !== 'pseudo' || n.value.startsWith('::'))
10-
.join('')
11-
.trim()
39+
40+
return minimumImpactSelector(nodes)
1241
})
1342
})
1443

@@ -28,7 +57,7 @@ export default function resolveDefaultsAtRules() {
2857
let universals = new Set()
2958

3059
root.walkAtRules('defaults', (rule) => {
31-
if (rule.nodes.length > 0) {
60+
if (rule.nodes && rule.nodes.length > 0) {
3261
universals.add(rule)
3362
return
3463
}

tests/jit/resolve-defaults-at-rules.test.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,3 +605,104 @@ test('when a utility uses defaults but they do not exist', async () => {
605605
`)
606606
})
607607
})
608+
609+
test('selectors are reduced to the lowest possible specificity', async () => {
610+
let config = {
611+
mode: 'jit',
612+
purge: [
613+
{
614+
raw: '<div class="foo"></div>',
615+
},
616+
],
617+
theme: {},
618+
plugins: [],
619+
corePlugins: [],
620+
}
621+
622+
let css = `
623+
@defaults test {
624+
--color: black;
625+
}
626+
627+
/* --- */
628+
629+
.foo {
630+
@defaults test;
631+
background-color: var(--color);
632+
}
633+
634+
#app {
635+
@defaults test;
636+
border-color: var(--color);
637+
}
638+
639+
span#page {
640+
@defaults test;
641+
color: var(--color);
642+
}
643+
644+
div[data-foo="bar"]#other {
645+
@defaults test;
646+
fill: var(--color);
647+
}
648+
649+
div[data-bar="baz"] {
650+
@defaults test;
651+
stroke: var(--color);
652+
}
653+
654+
article {
655+
@defaults test;
656+
--article: var(--color);
657+
}
658+
659+
div[data-foo="bar"]#another::before {
660+
@defaults test;
661+
fill: var(--color);
662+
}
663+
`
664+
665+
return run(css, config).then((result) => {
666+
expect(result.css).toMatchFormattedCss(`
667+
.foo,
668+
[id="app"],
669+
[id="page"],
670+
[id="other"],
671+
[data-bar="baz"],
672+
article,
673+
[id="another"]::before {
674+
--color: black;
675+
}
676+
677+
/* --- */
678+
679+
.foo {
680+
background-color: var(--color);
681+
}
682+
683+
#app {
684+
border-color: var(--color);
685+
}
686+
687+
span#page {
688+
color: var(--color);
689+
}
690+
691+
div[data-foo="bar"]#other {
692+
fill: var(--color);
693+
}
694+
695+
div[data-bar="baz"] {
696+
stroke: var(--color);
697+
}
698+
699+
article {
700+
--article: var(--color);
701+
}
702+
703+
div[data-foo="bar"]#another::before {
704+
fill: var(--color);
705+
}
706+
`)
707+
})
708+
})

0 commit comments

Comments
 (0)