Skip to content

Commit b1de75e

Browse files
authoredSep 10, 2024··
fix(compiler-sfc): correct scoped injection for nesting selector (#11854)
close #10567
1 parent fe2ab1b commit b1de75e

File tree

2 files changed

+59
-5
lines changed

2 files changed

+59
-5
lines changed
 

‎packages/compiler-sfc/__tests__/compileStyle.spec.ts

+13
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ describe('SFC scoped CSS', () => {
4141
)
4242
})
4343

44+
test('nesting selector', () => {
45+
expect(compileScoped(`h1 { color: red; .foo { color: red; } }`)).toMatch(
46+
`h1 {\n&[data-v-test] { color: red;\n}\n.foo[data-v-test] { color: red;`,
47+
)
48+
})
49+
4450
test('multiple selectors', () => {
4551
expect(compileScoped(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
4652
`h1 .foo[data-v-test], .bar[data-v-test], .baz[data-v-test] { color: red;`,
@@ -95,6 +101,13 @@ describe('SFC scoped CSS', () => {
95101
":where(.foo[data-v-test] .bar) { color: red;
96102
}"
97103
`)
104+
expect(compileScoped(`:deep(.foo) { color: red; .bar { color: red; } }`))
105+
.toMatchInlineSnapshot(`
106+
"[data-v-test] .foo { color: red;
107+
.bar { color: red;
108+
}
109+
}"
110+
`)
98111
})
99112

100113
test('::v-slotted', () => {

‎packages/compiler-sfc/src/style/pluginScoped.ts

+46-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import type { AtRule, PluginCreator, Rule } from 'postcss'
1+
import {
2+
type AtRule,
3+
type Container,
4+
type Document,
5+
type PluginCreator,
6+
Rule,
7+
} from 'postcss'
28
import selectorParser from 'postcss-selector-parser'
39
import { warn } from '../warn'
410

@@ -71,21 +77,32 @@ function processRule(id: string, rule: Rule) {
7177
return
7278
}
7379
processedRules.add(rule)
80+
let deep = false
81+
let parent: Document | Container | undefined = rule.parent
82+
while (parent && parent.type !== 'root') {
83+
if ((parent as any).__deep) {
84+
deep = true
85+
break
86+
}
87+
parent = parent.parent
88+
}
7489
rule.selector = selectorParser(selectorRoot => {
7590
selectorRoot.each(selector => {
76-
rewriteSelector(id, selector, selectorRoot)
91+
rewriteSelector(id, rule, selector, selectorRoot, deep)
7792
})
7893
}).processSync(rule.selector)
7994
}
8095

8196
function rewriteSelector(
8297
id: string,
98+
rule: Rule,
8399
selector: selectorParser.Selector,
84100
selectorRoot: selectorParser.Root,
101+
deep: boolean,
85102
slotted = false,
86103
) {
87104
let node: selectorParser.Node | null = null
88-
let shouldInject = true
105+
let shouldInject = !deep
89106
// find the last child node to insert attribute selector
90107
selector.each(n => {
91108
// DEPRECATED ">>>" and "/deep/" combinator
@@ -107,6 +124,7 @@ function rewriteSelector(
107124
// deep: inject [id] attribute at the node before the ::v-deep
108125
// combinator.
109126
if (value === ':deep' || value === '::v-deep') {
127+
;(rule as any).__deep = true
110128
if (n.nodes.length) {
111129
// .foo ::v-deep(.bar) -> .foo[xxxxxxx] .bar
112130
// replace the current node with ::v-deep's inner selector
@@ -147,7 +165,14 @@ function rewriteSelector(
147165
// instead.
148166
// ::v-slotted(.foo) -> .foo[xxxxxxx-s]
149167
if (value === ':slotted' || value === '::v-slotted') {
150-
rewriteSelector(id, n.nodes[0], selectorRoot, true /* slotted */)
168+
rewriteSelector(
169+
id,
170+
rule,
171+
n.nodes[0],
172+
selectorRoot,
173+
deep,
174+
true /* slotted */,
175+
)
151176
let last: selectorParser.Selector['nodes'][0] = n
152177
n.nodes[0].each(ss => {
153178
selector.insertAfter(last, ss)
@@ -206,11 +231,27 @@ function rewriteSelector(
206231
}
207232
})
208233

234+
if (rule.nodes.some(node => node.type === 'rule')) {
235+
const deep = (rule as any).__deep
236+
const decls = rule.nodes.filter(node => node.type === 'decl')
237+
if (!deep && decls.length) {
238+
for (const decl of decls) {
239+
rule.removeChild(decl)
240+
}
241+
const hostRule = new Rule({
242+
nodes: decls,
243+
selector: '&',
244+
})
245+
rule.prepend(hostRule)
246+
}
247+
shouldInject = deep
248+
}
249+
209250
if (node) {
210251
const { type, value } = node as selectorParser.Node
211252
if (type === 'pseudo' && (value === ':is' || value === ':where')) {
212253
;(node as selectorParser.Pseudo).nodes.forEach(value =>
213-
rewriteSelector(id, value, selectorRoot, slotted),
254+
rewriteSelector(id, rule, value, selectorRoot, deep, slotted),
214255
)
215256
shouldInject = false
216257
}

0 commit comments

Comments
 (0)
Please sign in to comment.