Skip to content

Commit 999fca9

Browse files
authored
fix: use hybrid scoping strategy for consistent specificity increase (#10443)
* end the specificity wars * tweaks * document breaking change * blurgh --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 5dd9951 commit 999fca9

File tree

51 files changed

+314
-322
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+314
-322
lines changed

.changeset/long-lobsters-mate.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: use hybrid scoping strategy for consistent specificity increase

packages/svelte/src/compiler/phases/2-analyze/css/Selector.js

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,9 @@ export default class Selector {
7070

7171
/**
7272
* @param {import('magic-string').default} code
73-
* @param {string} attr
74-
* @param {number} max_amount_class_specificity_increased
73+
* @param {string} modifier
7574
*/
76-
transform(code, attr, max_amount_class_specificity_increased) {
77-
const amount_class_specificity_to_increase =
78-
max_amount_class_specificity_increased -
79-
this.blocks.filter((block) => block.should_encapsulate).length;
80-
75+
transform(code, modifier) {
8176
/** @param {import('#compiler').Css.SimpleSelector} selector */
8277
function remove_global_pseudo_class(selector) {
8378
code
@@ -87,43 +82,50 @@ export default class Selector {
8782

8883
/**
8984
* @param {Block} block
90-
* @param {string} attr
85+
* @param {string} modifier
9186
*/
92-
function encapsulate_block(block, attr) {
87+
function encapsulate_block(block, modifier) {
9388
for (const selector of block.selectors) {
9489
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
9590
remove_global_pseudo_class(selector);
9691
}
9792
}
93+
9894
let i = block.selectors.length;
9995
while (i--) {
10096
const selector = block.selectors[i];
97+
10198
if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') {
10299
if (selector.name !== 'root' && selector.name !== 'host') {
103-
if (i === 0) code.prependRight(selector.start, attr);
100+
if (i === 0) code.prependRight(selector.start, modifier);
104101
}
105102
continue;
106103
}
104+
107105
if (selector.type === 'TypeSelector' && selector.name === '*') {
108-
code.update(selector.start, selector.end, attr);
106+
code.update(selector.start, selector.end, modifier);
109107
} else {
110-
code.appendLeft(selector.end, attr);
108+
code.appendLeft(selector.end, modifier);
111109
}
110+
112111
break;
113112
}
114113
}
115-
this.blocks.forEach((block, index) => {
114+
115+
let first = true;
116+
for (const block of this.blocks) {
116117
if (block.global) {
117118
remove_global_pseudo_class(block.selectors[0]);
118119
}
119-
if (block.should_encapsulate)
120-
encapsulate_block(
121-
block,
122-
index === this.blocks.length - 1
123-
? attr.repeat(amount_class_specificity_to_increase + 1)
124-
: attr
125-
);
126-
});
120+
121+
if (block.should_encapsulate) {
122+
// for the first occurrence, we use a classname selector, so that every
123+
// encapsulated selector gets a +0-1-0 specificity bump. thereafter,
124+
// we use a `:where` selector, which does not affect specificity
125+
encapsulate_block(block, first ? modifier : `:where(${modifier})`);
126+
first = false;
127+
}
128+
}
127129
}
128130

129131
/** @param {import('../../types.js').ComponentAnalysis} analysis */
@@ -200,10 +202,6 @@ export default class Selector {
200202
}
201203
}
202204
}
203-
204-
get_amount_class_specificity_increased() {
205-
return this.blocks.filter((block) => block.should_encapsulate).length;
206-
}
207205
}
208206

209207
/**

packages/svelte/src/compiler/phases/2-analyze/css/Stylesheet.js

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,14 @@ class Rule {
9797
* @param {import('magic-string').default} code
9898
* @param {string} id
9999
* @param {Map<string, string>} keyframes
100-
* @param {number} max_amount_class_specificity_increased
101100
*/
102-
transform(code, id, keyframes, max_amount_class_specificity_increased) {
101+
transform(code, id, keyframes) {
103102
if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) {
104103
return;
105104
}
106105

107-
const attr = `.${id}`;
108-
this.selectors.forEach((selector) =>
109-
selector.transform(code, attr, max_amount_class_specificity_increased)
110-
);
106+
const modifier = `.${id}`;
107+
this.selectors.forEach((selector) => selector.transform(code, modifier));
111108
this.declarations.forEach((declaration) => declaration.transform(code, keyframes));
112109
}
113110

@@ -125,13 +122,6 @@ class Rule {
125122
});
126123
}
127124

128-
/** @returns number */
129-
get_max_amount_class_specificity_increased() {
130-
return Math.max(
131-
...this.selectors.map((selector) => selector.get_amount_class_specificity_increased())
132-
);
133-
}
134-
135125
/**
136126
* @param {MagicString} code
137127
* @param {boolean} dev
@@ -287,9 +277,8 @@ class Atrule {
287277
* @param {import('magic-string').default} code
288278
* @param {string} id
289279
* @param {Map<string, string>} keyframes
290-
* @param {number} max_amount_class_specificity_increased
291280
*/
292-
transform(code, id, keyframes, max_amount_class_specificity_increased) {
281+
transform(code, id, keyframes) {
293282
if (is_keyframes_node(this.node)) {
294283
let start = this.node.start + this.node.name.length + 1;
295284
while (code.original[start] === ' ') start += 1;
@@ -309,7 +298,7 @@ class Atrule {
309298
}
310299
}
311300
this.children.forEach((child) => {
312-
child.transform(code, id, keyframes, max_amount_class_specificity_increased);
301+
child.transform(code, id, keyframes);
313302
});
314303
}
315304

@@ -328,13 +317,6 @@ class Atrule {
328317
});
329318
}
330319

331-
/** @returns {number} */
332-
get_max_amount_class_specificity_increased() {
333-
return Math.max(
334-
...this.children.map((rule) => rule.get_max_amount_class_specificity_increased())
335-
);
336-
}
337-
338320
/**
339321
* @param {MagicString} code
340322
* @param {boolean} dev
@@ -517,12 +499,8 @@ export default class Stylesheet {
517499
}
518500
});
519501

520-
const max = Math.max(
521-
...this.children.map((rule) => rule.get_max_amount_class_specificity_increased())
522-
);
523-
524502
for (const child of this.children) {
525-
child.transform(code, this.id, this.keyframes, max);
503+
child.transform(code, this.id, this.keyframes);
526504
}
527505

528506
code.remove(0, this.ast.content.start);
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
main.svelte-xyz button.svelte-xyz.svelte-xyz {
1+
main.svelte-xyz button:where(.svelte-xyz) {
22
background-color: red;
33
}
44

5-
main.svelte-xyz div.svelte-xyz > button.svelte-xyz {
5+
main.svelte-xyz div:where(.svelte-xyz) > button:where(.svelte-xyz) {
66
background-color: blue;
77
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
.test.svelte-xyz > div.svelte-xyz {
1+
.test.svelte-xyz > div:where(.svelte-xyz) {
22
color: #0af;
33
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
p.svelte-xyz span.svelte-xyz {
1+
p.svelte-xyz span:where(.svelte-xyz) {
22
color: red;
33
}
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
div.svelte-xyz.svelte-xyz.svelte-xyz {
1+
div.svelte-xyz {
22
color: red;
33
}
4-
h2.svelte-xyz > p.svelte-xyz.svelte-xyz {
4+
h2.svelte-xyz > p:where(.svelte-xyz) {
55
color: red;
66
}
7-
h2.svelte-xyz span.svelte-xyz.svelte-xyz {
7+
h2.svelte-xyz span:where(.svelte-xyz) {
88
color: red;
99
}
10-
h2.svelte-xyz > span.svelte-xyz > b.svelte-xyz {
10+
h2.svelte-xyz > span:where(.svelte-xyz) > b:where(.svelte-xyz) {
1111
color: red;
1212
}
13-
h2.svelte-xyz span b.svelte-xyz.svelte-xyz {
13+
h2.svelte-xyz span b:where(.svelte-xyz) {
1414
color: red;
1515
}
16-
h2.svelte-xyz b.svelte-xyz.svelte-xyz {
16+
h2.svelte-xyz b:where(.svelte-xyz) {
1717
color: red;
1818
}
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
.a.svelte-xyz ~ .b.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
2-
.a.svelte-xyz ~ .c.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
3-
.a.svelte-xyz ~ .d.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
4-
.a.svelte-xyz ~ .e.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
5-
.a.svelte-xyz ~ .f.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
6-
.a.svelte-xyz ~ .g.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
7-
.a.svelte-xyz ~ .h.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
1+
.a.svelte-xyz ~ .b:where(.svelte-xyz) { color: green; }
2+
.a.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
3+
.a.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
4+
.a.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
5+
.a.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
6+
.a.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
7+
.a.svelte-xyz ~ .h:where(.svelte-xyz) { color: green; }
88

9-
.b.svelte-xyz ~ .d.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
10-
.c.svelte-xyz ~ .d.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
11-
.b.svelte-xyz ~ .e.svelte-xyz ~ .f.svelte-xyz ~ .h.svelte-xyz { color: green; }
12-
.b.svelte-xyz ~ .d.svelte-xyz ~ .h.svelte-xyz.svelte-xyz { color: green; }
13-
.c.svelte-xyz ~ .g.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
9+
.b.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
10+
.c.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
11+
.b.svelte-xyz ~ .e:where(.svelte-xyz) ~ .f:where(.svelte-xyz) ~ .h:where(.svelte-xyz) { color: green; }
12+
.b.svelte-xyz ~ .d:where(.svelte-xyz) ~ .h:where(.svelte-xyz) { color: green; }
13+
.c.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }

packages/svelte/tests/css/samples/general-siblings-combinator-await/expected.css

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
.a.svelte-xyz ~ .b.svelte-xyz { color: green; }
2-
.a.svelte-xyz ~ .c.svelte-xyz { color: green; }
3-
.a.svelte-xyz ~ .d.svelte-xyz { color: green; }
4-
.b.svelte-xyz ~ .e.svelte-xyz { color: green; }
5-
.c.svelte-xyz ~ .e.svelte-xyz { color: green; }
6-
.d.svelte-xyz ~ .e.svelte-xyz { color: green; }
7-
.a.svelte-xyz ~ .e.svelte-xyz { color: green; }
1+
.a.svelte-xyz ~ .b:where(.svelte-xyz) { color: green; }
2+
.a.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
3+
.a.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
4+
.b.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
5+
.c.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
6+
.d.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
7+
.a.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
88

99
/* no match */
1010
/* (unused) .b ~ .c { color: green; }*/
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
/* boundary of each */
2-
.a.svelte-xyz ~ .b.svelte-xyz {
2+
.a.svelte-xyz ~ .b:where(.svelte-xyz) {
33
color: green;
44
}
5-
.c.svelte-xyz ~ .d.svelte-xyz {
5+
.c.svelte-xyz ~ .d:where(.svelte-xyz) {
66
color: green;
77
}
88
/* if array is empty */
9-
.a.svelte-xyz ~ .d.svelte-xyz {
9+
.a.svelte-xyz ~ .d:where(.svelte-xyz) {
1010
color: green;
1111
}
1212
/* if array has multiple items */
13-
.c.svelte-xyz ~ .b.svelte-xyz {
13+
.c.svelte-xyz ~ .b:where(.svelte-xyz) {
1414
color: green;
1515
}
1616
/* normal sibling */
17-
.b.svelte-xyz ~ .c.svelte-xyz {
17+
.b.svelte-xyz ~ .c:where(.svelte-xyz) {
1818
color: green;
1919
}
20-
.a.svelte-xyz ~ .c.svelte-xyz {
20+
.a.svelte-xyz ~ .c:where(.svelte-xyz) {
2121
color: green;
2222
}
Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
1-
.a.svelte-xyz ~ .e.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
2-
.a.svelte-xyz ~ .f.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
3-
.b.svelte-xyz ~ .c.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
4-
.b.svelte-xyz ~ .d.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
5-
.c.svelte-xyz ~ .e.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
6-
.c.svelte-xyz ~ .f.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
7-
.d.svelte-xyz ~ .e.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
8-
.d.svelte-xyz ~ .f.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
9-
.e.svelte-xyz ~ .e.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
10-
.i.svelte-xyz ~ .j.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
11-
.g.svelte-xyz ~ .h.svelte-xyz ~ .j.svelte-xyz.svelte-xyz { color: green; }
12-
.g.svelte-xyz ~ .i.svelte-xyz ~ .j.svelte-xyz.svelte-xyz { color: green; }
13-
.m.svelte-xyz ~ .m.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
14-
.m.svelte-xyz ~ .l.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
15-
.l.svelte-xyz ~ .m.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
16-
.a.svelte-xyz ~ .c.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
17-
.a.svelte-xyz ~ .g.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
18-
.b.svelte-xyz ~ .e.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
19-
.c.svelte-xyz ~ .g.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
20-
.c.svelte-xyz ~ .k.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
21-
.d.svelte-xyz ~ .d.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
22-
.g.svelte-xyz ~ .g.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
23-
.h.svelte-xyz ~ .h.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
24-
.i.svelte-xyz ~ .i.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
25-
.j.svelte-xyz ~ .j.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
26-
.g.svelte-xyz ~ .j.svelte-xyz.svelte-xyz.svelte-xyz { color: green; }
27-
.g.svelte-xyz ~ .h.svelte-xyz ~ .i.svelte-xyz ~ .j.svelte-xyz { color: green; }
1+
.a.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
2+
.a.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
3+
.b.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
4+
.b.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
5+
.c.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
6+
.c.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
7+
.d.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
8+
.d.svelte-xyz ~ .f:where(.svelte-xyz) { color: green; }
9+
.e.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
10+
.i.svelte-xyz ~ .j:where(.svelte-xyz) { color: green; }
11+
.g.svelte-xyz ~ .h:where(.svelte-xyz) ~ .j:where(.svelte-xyz) { color: green; }
12+
.g.svelte-xyz ~ .i:where(.svelte-xyz) ~ .j:where(.svelte-xyz) { color: green; }
13+
.m.svelte-xyz ~ .m:where(.svelte-xyz) { color: green; }
14+
.m.svelte-xyz ~ .l:where(.svelte-xyz) { color: green; }
15+
.l.svelte-xyz ~ .m:where(.svelte-xyz) { color: green; }
16+
.a.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
17+
.a.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
18+
.b.svelte-xyz ~ .e:where(.svelte-xyz) { color: green; }
19+
.c.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
20+
.c.svelte-xyz ~ .k:where(.svelte-xyz) { color: green; }
21+
.d.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
22+
.g.svelte-xyz ~ .g:where(.svelte-xyz) { color: green; }
23+
.h.svelte-xyz ~ .h:where(.svelte-xyz) { color: green; }
24+
.i.svelte-xyz ~ .i:where(.svelte-xyz) { color: green; }
25+
.j.svelte-xyz ~ .j:where(.svelte-xyz) { color: green; }
26+
.g.svelte-xyz ~ .j:where(.svelte-xyz) { color: green; }
27+
.g.svelte-xyz ~ .h:where(.svelte-xyz) ~ .i:where(.svelte-xyz) ~ .j:where(.svelte-xyz) { color: green; }
2828

2929
/* no match */
3030
/* (unused) .e ~ .f { color: green; }*/
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
.a.svelte-xyz ~ .b.svelte-xyz { color: green; }
2-
.a.svelte-xyz ~ .c.svelte-xyz { color: green; }
3-
.b.svelte-xyz ~ .d.svelte-xyz { color: green; }
4-
.c.svelte-xyz ~ .d.svelte-xyz { color: green; }
5-
.a.svelte-xyz ~ .d.svelte-xyz { color: green; }
1+
.a.svelte-xyz ~ .b:where(.svelte-xyz) { color: green; }
2+
.a.svelte-xyz ~ .c:where(.svelte-xyz) { color: green; }
3+
.b.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
4+
.c.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
5+
.a.svelte-xyz ~ .d:where(.svelte-xyz) { color: green; }
66

77
/* no match */
88
/* (unused) .b ~ .c { color: green; }*/

0 commit comments

Comments
 (0)