Skip to content

Commit 44f41c9

Browse files
authored
fix :host and :global css scoping (#5957)
1 parent ebbdb42 commit 44f41c9

File tree

8 files changed

+162
-2
lines changed

8 files changed

+162
-2
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Svelte changelog
22

3+
## Unreleased
4+
5+
* Fix scoping of selectors with `:global()` and `~` sibling combinators ([#5499](https://github.com/sveltejs/svelte/issues/5499))
6+
* Fix removal of `:host` selectors as unused when compiling to a custom element ([#5946](https://github.com/sveltejs/svelte/issues/5946))
7+
38
## 3.32.1
49

510
* Warn when using `module` variables reactively, and close weird reactivity loophole ([#5847](https://github.com/sveltejs/svelte/pull/5847))

src/compiler/compile/css/Selector.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export default class Selector {
8484
while (i--) {
8585
const selector = block.selectors[i];
8686
if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') {
87-
if (selector.name !== 'root') {
87+
if (selector.name !== 'root' && selector.name !== 'host') {
8888
if (i === 0) code.prependRight(selector.start, attr);
8989
}
9090
continue;
@@ -162,7 +162,10 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]):
162162
if (!block) return false;
163163

164164
if (!node) {
165-
return block.global && blocks.every(block => block.global);
165+
return (
166+
(block.global && blocks.every(block => block.global)) ||
167+
(block.host && blocks.length === 0)
168+
);
166169
}
167170

168171
switch (block_might_apply_to_node(block, node)) {
@@ -182,6 +185,11 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]):
182185
continue;
183186
}
184187

188+
if (ancestor_block.host) {
189+
to_encapsulate.push({ node, block });
190+
return true;
191+
}
192+
185193
let parent = node;
186194
while (parent = get_element_parent(parent)) {
187195
if (block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible) {
@@ -211,6 +219,19 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]):
211219
} else if (block.combinator.name === '+' || block.combinator.name === '~') {
212220
const siblings = get_possible_element_siblings(node, block.combinator.name === '+');
213221
let has_match = false;
222+
223+
// NOTE: if we have :global(), we couldn't figure out what is selected within `:global` due to the
224+
// css-tree limitation that does not parse the inner selector of :global
225+
// so unless we are sure there will be no sibling to match, we will consider it as matched
226+
const has_global = blocks.some(block => block.global);
227+
if (has_global) {
228+
if (siblings.size === 0 && get_element_parent(node) !== null) {
229+
return false;
230+
}
231+
to_encapsulate.push({ node, block });
232+
return true;
233+
}
234+
214235
for (const possible_sibling of siblings.keys()) {
215236
if (apply_selector(blocks.slice(), possible_sibling, to_encapsulate)) {
216237
to_encapsulate.push({ node, block });
@@ -236,6 +257,10 @@ function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToN
236257
const selector = block.selectors[i];
237258
const name = typeof selector.name === 'string' && selector.name.replace(/\\(.)/g, '$1');
238259

260+
if (selector.type === 'PseudoClassSelector' && name === 'host') {
261+
return BlockAppliesToNode.NotPossible;
262+
}
263+
239264
if (selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') {
240265
continue;
241266
}
@@ -541,6 +566,7 @@ function loop_child(children: INode[], adjacent_only: boolean) {
541566

542567
class Block {
543568
global: boolean;
569+
host: boolean;
544570
combinator: CssNode;
545571
selectors: CssNode[]
546572
start: number;
@@ -550,6 +576,7 @@ class Block {
550576
constructor(combinator: CssNode) {
551577
this.combinator = combinator;
552578
this.global = false;
579+
this.host = false;
553580
this.selectors = [];
554581

555582
this.start = null;
@@ -562,6 +589,7 @@ class Block {
562589
if (this.selectors.length === 0) {
563590
this.start = selector.start;
564591
this.global = selector.type === 'PseudoClassSelector' && selector.name === 'global';
592+
this.host = selector.type === 'PseudoClassSelector' && selector.name === 'host';
565593
}
566594

567595
this.selectors.push(selector);

test/css/samples/host/_config.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export default {
2+
warnings: [
3+
{
4+
code: 'css-unused-selector',
5+
message: 'Unused CSS selector ":host > span"',
6+
pos: 147,
7+
start: {
8+
character: 147,
9+
column: 1,
10+
line: 18
11+
},
12+
end: {
13+
character: 159,
14+
column: 13,
15+
line: 18
16+
},
17+
frame: `
18+
16: }
19+
17:
20+
18: :host > span {
21+
^
22+
19: color: red;
23+
20: }
24+
`
25+
}
26+
]
27+
};

test/css/samples/host/expected.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/css/samples/host/input.svelte

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<style>
2+
:host h1 {
3+
color: red;
4+
}
5+
6+
:host > h1 {
7+
color: red;
8+
}
9+
10+
:host > * {
11+
color: red;
12+
}
13+
14+
:host span {
15+
color: red;
16+
}
17+
18+
:host > span {
19+
color: red;
20+
}
21+
</style>
22+
23+
<h1>Hello!</h1>
24+
25+
<div>
26+
<span>World!</span>
27+
</div>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
export default {
2+
warnings: [
3+
{
4+
code: 'css-unused-selector',
5+
message: 'Unused CSS selector ":global(input) + span"',
6+
pos: 239,
7+
start: {
8+
character: 239,
9+
column: 2,
10+
line: 9
11+
},
12+
end: {
13+
character: 260,
14+
column: 23,
15+
line: 9
16+
},
17+
frame: `
18+
7: :global(input) ~ p { color: red; }
19+
8:
20+
9: :global(input) + span { color: red; }
21+
^
22+
10: :global(input) ~ span { color: red; }
23+
11: </style>
24+
`
25+
},
26+
{
27+
code: 'css-unused-selector',
28+
message: 'Unused CSS selector ":global(input) ~ span"',
29+
pos: 279,
30+
start: {
31+
character: 279,
32+
column: 2,
33+
line: 10
34+
},
35+
end: {
36+
character: 300,
37+
column: 23,
38+
line: 10
39+
},
40+
frame: `
41+
8:
42+
9: :global(input) + span { color: red; }
43+
10: :global(input) ~ span { color: red; }
44+
^
45+
11: </style>
46+
12:
47+
`
48+
}
49+
]
50+
};

test/css/samples/siblings-combinator-global/expected.css

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<style>
2+
:global(input) + div { color: red; }
3+
:global(input) ~ div { color: red; }
4+
:global(input) + h1 { color: red; }
5+
:global(input) ~ h1 { color: red; }
6+
:global(input) + p { color: red; }
7+
:global(input) ~ p { color: red; }
8+
9+
:global(input) + span { color: red; }
10+
:global(input) ~ span { color: red; }
11+
</style>
12+
13+
<h1>Hello!</h1>
14+
15+
<div>
16+
<span>World!</span>
17+
</div>
18+
19+
{#each [] as _}
20+
<p />
21+
{/each}

0 commit comments

Comments
 (0)