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', () => { test('multiple selectors', () => {
expect(compileScoped(`h1 .foo, .bar, .baz { color: red; }`)).toMatch( expect(compileScoped(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
`h1 .foo[data-v-test], .bar[data-v-test], .baz[data-v-test] { color: red;`, `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; ":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', () => { 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 selectorParser from 'postcss-selector-parser'
import { warn } from '../warn' import { warn } from '../warn'
@ -71,21 +77,32 @@ function processRule(id: string, rule: Rule) {
return return
} }
processedRules.add(rule) 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 => { rule.selector = selectorParser(selectorRoot => {
selectorRoot.each(selector => { selectorRoot.each(selector => {
rewriteSelector(id, selector, selectorRoot) rewriteSelector(id, rule, selector, selectorRoot, deep)
}) })
}).processSync(rule.selector) }).processSync(rule.selector)
} }
function rewriteSelector( function rewriteSelector(
id: string, id: string,
rule: Rule,
selector: selectorParser.Selector, selector: selectorParser.Selector,
selectorRoot: selectorParser.Root, selectorRoot: selectorParser.Root,
deep: boolean,
slotted = false, slotted = false,
) { ) {
let node: selectorParser.Node | null = null let node: selectorParser.Node | null = null
let shouldInject = true let shouldInject = !deep
// find the last child node to insert attribute selector // find the last child node to insert attribute selector
selector.each(n => { selector.each(n => {
// DEPRECATED ">>>" and "/deep/" combinator // DEPRECATED ">>>" and "/deep/" combinator
@ -107,6 +124,7 @@ function rewriteSelector(
// deep: inject [id] attribute at the node before the ::v-deep // deep: inject [id] attribute at the node before the ::v-deep
// combinator. // combinator.
if (value === ':deep' || value === '::v-deep') { if (value === ':deep' || value === '::v-deep') {
;(rule as any).__deep = true
if (n.nodes.length) { if (n.nodes.length) {
// .foo ::v-deep(.bar) -> .foo[xxxxxxx] .bar // .foo ::v-deep(.bar) -> .foo[xxxxxxx] .bar
// replace the current node with ::v-deep's inner selector // replace the current node with ::v-deep's inner selector
@ -147,7 +165,14 @@ function rewriteSelector(
// instead. // instead.
// ::v-slotted(.foo) -> .foo[xxxxxxx-s] // ::v-slotted(.foo) -> .foo[xxxxxxx-s]
if (value === ':slotted' || value === '::v-slotted') { 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 let last: selectorParser.Selector['nodes'][0] = n
n.nodes[0].each(ss => { n.nodes[0].each(ss => {
selector.insertAfter(last, 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) { if (node) {
const { type, value } = node as selectorParser.Node const { type, value } = node as selectorParser.Node
if (type === 'pseudo' && (value === ':is' || value === ':where')) { if (type === 'pseudo' && (value === ':is' || value === ':where')) {
;(node as selectorParser.Pseudo).nodes.forEach(value => ;(node as selectorParser.Pseudo).nodes.forEach(value =>
rewriteSelector(id, value, selectorRoot, slotted), rewriteSelector(id, rule, value, selectorRoot, deep, slotted),
) )
shouldInject = false shouldInject = false
} }