fix(compiler-sfc): correct scoped injection for nesting selector (#11854)

close #10567
This commit is contained in:
山吹色御守 2024-09-10 15:38:33 +08:00 committed by GitHub
parent fe2ab1bbac
commit b1de75ed04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 59 additions and 5 deletions

View File

@ -41,6 +41,12 @@ describe('SFC scoped CSS', () => {
)
})
test('nesting selector', () => {
expect(compileScoped(`h1 { color: red; .foo { color: red; } }`)).toMatch(
`h1 {\n&[data-v-test] { color: red;\n}\n.foo[data-v-test] { color: red;`,
)
})
test('multiple selectors', () => {
expect(compileScoped(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
`h1 .foo[data-v-test], .bar[data-v-test], .baz[data-v-test] { color: red;`,
@ -95,6 +101,13 @@ describe('SFC scoped CSS', () => {
":where(.foo[data-v-test] .bar) { color: red;
}"
`)
expect(compileScoped(`:deep(.foo) { color: red; .bar { color: red; } }`))
.toMatchInlineSnapshot(`
"[data-v-test] .foo { color: red;
.bar { color: red;
}
}"
`)
})
test('::v-slotted', () => {

View File

@ -1,4 +1,10 @@
import type { AtRule, PluginCreator, Rule } from 'postcss'
import {
type AtRule,
type Container,
type Document,
type PluginCreator,
Rule,
} from 'postcss'
import selectorParser from 'postcss-selector-parser'
import { warn } from '../warn'
@ -71,21 +77,32 @@ function processRule(id: string, rule: Rule) {
return
}
processedRules.add(rule)
let deep = false
let parent: Document | Container | undefined = rule.parent
while (parent && parent.type !== 'root') {
if ((parent as any).__deep) {
deep = true
break
}
parent = parent.parent
}
rule.selector = selectorParser(selectorRoot => {
selectorRoot.each(selector => {
rewriteSelector(id, selector, selectorRoot)
rewriteSelector(id, rule, selector, selectorRoot, deep)
})
}).processSync(rule.selector)
}
function rewriteSelector(
id: string,
rule: Rule,
selector: selectorParser.Selector,
selectorRoot: selectorParser.Root,
deep: boolean,
slotted = false,
) {
let node: selectorParser.Node | null = null
let shouldInject = true
let shouldInject = !deep
// find the last child node to insert attribute selector
selector.each(n => {
// DEPRECATED ">>>" and "/deep/" combinator
@ -107,6 +124,7 @@ function rewriteSelector(
// deep: inject [id] attribute at the node before the ::v-deep
// combinator.
if (value === ':deep' || value === '::v-deep') {
;(rule as any).__deep = true
if (n.nodes.length) {
// .foo ::v-deep(.bar) -> .foo[xxxxxxx] .bar
// replace the current node with ::v-deep's inner selector
@ -147,7 +165,14 @@ function rewriteSelector(
// instead.
// ::v-slotted(.foo) -> .foo[xxxxxxx-s]
if (value === ':slotted' || value === '::v-slotted') {
rewriteSelector(id, n.nodes[0], selectorRoot, true /* slotted */)
rewriteSelector(
id,
rule,
n.nodes[0],
selectorRoot,
deep,
true /* slotted */,
)
let last: selectorParser.Selector['nodes'][0] = n
n.nodes[0].each(ss => {
selector.insertAfter(last, ss)
@ -206,11 +231,27 @@ function rewriteSelector(
}
})
if (rule.nodes.some(node => node.type === 'rule')) {
const deep = (rule as any).__deep
const decls = rule.nodes.filter(node => node.type === 'decl')
if (!deep && decls.length) {
for (const decl of decls) {
rule.removeChild(decl)
}
const hostRule = new Rule({
nodes: decls,
selector: '&',
})
rule.prepend(hostRule)
}
shouldInject = deep
}
if (node) {
const { type, value } = node as selectorParser.Node
if (type === 'pseudo' && (value === ':is' || value === ':where')) {
;(node as selectorParser.Pseudo).nodes.forEach(value =>
rewriteSelector(id, value, selectorRoot, slotted),
rewriteSelector(id, rule, value, selectorRoot, deep, slotted),
)
shouldInject = false
}