]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-sfc): correct scoped injection for nesting selector (#11854)
author山吹色御守 <85992002+KazariEX@users.noreply.github.com>
Tue, 10 Sep 2024 07:38:33 +0000 (15:38 +0800)
committerGitHub <noreply@github.com>
Tue, 10 Sep 2024 07:38:33 +0000 (15:38 +0800)
close #10567

packages/compiler-sfc/__tests__/compileStyle.spec.ts
packages/compiler-sfc/src/style/pluginScoped.ts

index 71c0689a397fae32715d4eac96bdd97a87050786..72727c43196e3e9f3d331cfd59c587c8d85f6e72 100644 (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', () => {
index 3812e67092a33621562de25289d08653dc836c85..6a588b56726ee5ecd920ffe87d48c8dbb6dc8d15 100644 (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
     }