]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test(sfc): test scoped css compilation
authorEvan You <yyx990803@gmail.com>
Tue, 17 Dec 2019 20:20:30 +0000 (15:20 -0500)
committerEvan You <yyx990803@gmail.com>
Tue, 17 Dec 2019 20:20:30 +0000 (15:20 -0500)
packages/compiler-sfc/__tests__/stylePluginScoped.spec.ts [new file with mode: 0644]
packages/compiler-sfc/src/stylePluginScoped.ts

diff --git a/packages/compiler-sfc/__tests__/stylePluginScoped.spec.ts b/packages/compiler-sfc/__tests__/stylePluginScoped.spec.ts
new file mode 100644 (file)
index 0000000..a618ef6
--- /dev/null
@@ -0,0 +1,192 @@
+import { compileStyle } from '../src/compileStyle'
+import { mockWarn } from '@vue/runtime-test'
+
+function compile(source: string): string {
+  const res = compileStyle({
+    source,
+    filename: 'test.css',
+    id: 'test'
+  })
+  if (res.errors.length) {
+    res.errors.forEach(err => {
+      console.error(err)
+    })
+    expect(res.errors.length).toBe(0)
+  }
+  return res.code
+}
+
+describe('SFC scoped CSS', () => {
+  mockWarn()
+
+  test('simple selectors', () => {
+    expect(compile(`h1 { color: red; }`)).toMatch(`h1[test] { color: red;`)
+    expect(compile(`.foo { color: red; }`)).toMatch(`.foo[test] { color: red;`)
+  })
+
+  test('descendent selector', () => {
+    expect(compile(`h1 .foo { color: red; }`)).toMatch(
+      `h1 .foo[test] { color: red;`
+    )
+  })
+
+  test('multiple selectors', () => {
+    expect(compile(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
+      `h1 .foo[test], .bar[test], .baz[test] { color: red;`
+    )
+  })
+
+  test('pseudo class', () => {
+    expect(compile(`.foo:after { color: red; }`)).toMatch(
+      `.foo[test]:after { color: red;`
+    )
+  })
+
+  test('pseudo element', () => {
+    expect(compile(`::selection { display: none; }`)).toMatch(
+      '[test]::selection {'
+    )
+  })
+
+  test('spaces before pseudo element', () => {
+    const code = compile(`.abc, ::selection { color: red; }`)
+    expect(code).toMatch('.abc[test],')
+    expect(code).toMatch('[test]::selection {')
+  })
+
+  test('::v-deep', () => {
+    expect(compile(`::v-deep(.foo) { color: red; }`)).toMatch(
+      `[test] .foo { color: red;`
+    )
+    expect(compile(`::v-deep(.foo .bar) { color: red; }`)).toMatch(
+      `[test] .foo .bar { color: red;`
+    )
+    expect(compile(`.baz .qux ::v-deep(.foo .bar) { color: red; }`)).toMatch(
+      `.baz .qux[test] .foo .bar { color: red;`
+    )
+  })
+
+  test('::v-slotted', () => {
+    expect(compile(`::v-slotted(.foo) { color: red; }`)).toMatch(
+      `.foo[test-s] { color: red;`
+    )
+    expect(compile(`::v-slotted(.foo .bar) { color: red; }`)).toMatch(
+      `.foo .bar[test-s] { color: red;`
+    )
+    expect(compile(`.baz .qux ::v-slotted(.foo .bar) { color: red; }`)).toMatch(
+      `.baz .qux[test] .foo .bar[test-s] { color: red;`
+    )
+  })
+
+  test('::v-global', () => {
+    expect(compile(`::v-global(.foo) { color: red; }`)).toMatch(
+      `.foo { color: red;`
+    )
+    expect(compile(`::v-global(.foo .bar) { color: red; }`)).toMatch(
+      `.foo .bar { color: red;`
+    )
+    // global ignores anything before it
+    expect(compile(`.baz .qux ::v-global(.foo .bar) { color: red; }`)).toMatch(
+      `.foo .bar { color: red;`
+    )
+  })
+
+  test('scoped keyframes', () => {
+    const style = compile(`
+.anim {
+  animation: color 5s infinite, other 5s;
+}
+.anim-2 {
+  animation-name: color;
+  animation-duration: 5s;
+}
+.anim-3 {
+  animation: 5s color infinite, 5s other;
+}
+.anim-multiple {
+  animation: color 5s infinite, opacity 2s;
+}
+.anim-multiple-2 {
+  animation-name: color, opacity;
+  animation-duration: 5s, 2s;
+}
+
+@keyframes color {
+  from { color: red; }
+  to { color: green; }
+}
+@-webkit-keyframes color {
+  from { color: red; }
+  to { color: green; }
+}
+@keyframes opacity {
+  from { opacity: 0; }
+  to { opacity: 1; }
+}
+@-webkit-keyframes opacity {
+  from { opacity: 0; }
+  to { opacity: 1; }
+}
+    `)
+
+    expect(style).toContain(
+      `.anim[test] {\n  animation: color-test 5s infinite, other 5s;`
+    )
+    expect(style).toContain(`.anim-2[test] {\n  animation-name: color-test`)
+    expect(style).toContain(
+      `.anim-3[test] {\n  animation: 5s color-test infinite, 5s other;`
+    )
+    expect(style).toContain(`@keyframes color-test {`)
+    expect(style).toContain(`@-webkit-keyframes color-test {`)
+
+    expect(style).toContain(
+      `.anim-multiple[test] {\n  animation: color-test 5s infinite,opacity-test 2s;`
+    )
+    expect(style).toContain(
+      `.anim-multiple-2[test] {\n  animation-name: color-test,opacity-test;`
+    )
+    expect(style).toContain(`@keyframes opacity-test {`)
+    expect(style).toContain(`@-webkit-keyframes opacity-test {`)
+  })
+
+  // vue-loader/#1370
+  test('spaces after selector', () => {
+    const { code } = compileStyle({
+      source: `.foo , .bar { color: red; }`,
+      filename: 'test.css',
+      id: 'test'
+    })
+
+    expect(code).toMatch(`.foo[test], .bar[test] { color: red;`)
+  })
+
+  describe('deprecated syntax', () => {
+    test('::v-deep as combinator', () => {
+      expect(compile(`::v-deep .foo { color: red; }`)).toMatch(
+        `[test] .foo { color: red;`
+      )
+      expect(compile(`.bar ::v-deep .foo { color: red; }`)).toMatch(
+        `.bar[test] .foo { color: red;`
+      )
+      expect(
+        `::v-deep usage as a combinator has been deprecated.`
+      ).toHaveBeenWarned()
+    })
+
+    test('>>> (deprecated syntax)', () => {
+      const code = compile(`>>> .foo { color: red; }`)
+      expect(code).toMatch(`[test] .foo { color: red;`)
+      expect(
+        `the >>> and /deep/ combinators have been deprecated.`
+      ).toHaveBeenWarned()
+    })
+
+    test('/deep/ (deprecated syntax)', () => {
+      const code = compile(`/deep/ .foo { color: red; }`)
+      expect(code).toMatch(`[test] .foo { color: red;`)
+      expect(
+        `the >>> and /deep/ combinators have been deprecated.`
+      ).toHaveBeenWarned()
+    })
+  })
+})
index adb94889356364150eae6f4427a2f256181ae032..112e7944b40aced6539bd78a80618f568c02f76c 100644 (file)
@@ -1,12 +1,12 @@
 import postcss, { Root } from 'postcss'
-import selectorParser from 'postcss-selector-parser'
+import selectorParser, { Node, Selector } from 'postcss-selector-parser'
 
-export default postcss.plugin('add-id', (options: any) => (root: Root) => {
+export default postcss.plugin('vue-scoped', (options: any) => (root: Root) => {
   const id: string = options
   const keyframes = Object.create(null)
 
-  root.each(function rewriteSelectors(node: any) {
-    if (!node.selector) {
+  root.each(function rewriteSelectors(node) {
+    if (node.type !== 'rule') {
       // handle media queries
       if (node.type === 'atrule') {
         if (node.name === 'media' || node.name === 'supports') {
@@ -19,22 +19,58 @@ export default postcss.plugin('add-id', (options: any) => (root: Root) => {
       return
     }
 
-    node.selector = selectorParser((selectors: any) => {
-      selectors.each(function rewriteSelector(
-        selector: any,
-        _i: number,
-        slotted?: boolean
-      ) {
-        let node: any = null
+    node.selector = selectorParser(selectors => {
+      function rewriteSelector(selector: Selector, slotted?: boolean) {
+        let node: Node | null = null
 
         // find the last child node to insert attribute selector
-        selector.each((n: any) => {
+        selector.each(n => {
+          // DEPRECATED ">>>" and "/deep/" combinator
+          if (
+            n.type === 'combinator' &&
+            (n.value === '>>>' || n.value === '/deep/')
+          ) {
+            n.value = ' '
+            n.spaces.before = n.spaces.after = ''
+            console.warn(
+              `[@vue/compiler-sfc] the >>> and /deep/ combinators have ` +
+                `been deprecated. Use ::v-deep instead.`
+            )
+            return false
+          }
+
           if (n.type === 'pseudo') {
             // deep: inject [id] attribute at the node before the ::v-deep
             // combinator.
-            // .foo ::v-deep .bar -> .foo[xxxxxxx] .bar
             if (n.value === '::v-deep') {
-              n.value = n.spaces.before = n.spaces.after = ''
+              if (n.nodes.length) {
+                // .foo ::v-deep(.bar) -> .foo[xxxxxxx] .bar
+                // replace the current node with ::v-deep's inner selector
+                selector.insertAfter(n, n.nodes[0])
+                // insert a space combinator before if it doesn't already have one
+                const prev = selector.at(selector.index(n) - 1)
+                if (!prev || !isSpaceCombinator(prev)) {
+                  selector.insertAfter(
+                    n,
+                    selectorParser.combinator({
+                      value: ' '
+                    })
+                  )
+                }
+                selector.removeChild(n)
+              } else {
+                // DEPRECATED usage
+                // .foo ::v-deep .bar -> .foo[xxxxxxx] .bar
+                console.warn(
+                  `[@vue/compiler-sfc] ::v-deep usage as a combinator has ` +
+                    `been deprecated. Use ::v-deep(<inner-selector>) instead.`
+                )
+                const prev = selector.at(selector.index(n) - 1)
+                if (prev && isSpaceCombinator(prev)) {
+                  selector.removeChild(prev)
+                }
+                selector.removeChild(n)
+              }
               return false
             }
 
@@ -42,9 +78,9 @@ export default postcss.plugin('add-id', (options: any) => (root: Root) => {
             // instead.
             // ::v-slotted(.foo) -> .foo[xxxxxxx-s]
             if (n.value === '::v-slotted') {
-              rewriteSelector(n.nodes[0], 0, true /* slotted */)
-              selectors.insertAfter(selector, n.nodes[0])
-              selectors.removeChild(selector)
+              rewriteSelector(n.nodes[0] as Selector, true /* slotted */)
+              selector.insertAfter(n, n.nodes[0])
+              selector.removeChild(n)
               return false
             }
 
@@ -63,7 +99,7 @@ export default postcss.plugin('add-id', (options: any) => (root: Root) => {
         })
 
         if (node) {
-          node.spaces.after = ''
+          ;(node as Node).spaces.after = ''
         } else {
           // For deep selectors & standalone pseudo selectors,
           // the attribute selectors are prepended rather than appended.
@@ -73,7 +109,9 @@ export default postcss.plugin('add-id', (options: any) => (root: Root) => {
 
         const idToAdd = slotted ? id + '-s' : id
         selector.insertAfter(
-          node,
+          // If node is null it means we need to inject [id] at the start
+          // insertAfter can handle `null` here
+          node as any,
           selectorParser.attribute({
             attribute: idToAdd,
             value: idToAdd,
@@ -81,7 +119,8 @@ export default postcss.plugin('add-id', (options: any) => (root: Root) => {
             quoteMark: `"`
           })
         )
-      })
+      }
+      selectors.each(selector => rewriteSelector(selector as Selector))
     }).processSync(node.selector)
   })
 
@@ -117,3 +156,7 @@ export default postcss.plugin('add-id', (options: any) => (root: Root) => {
     })
   }
 })
+
+function isSpaceCombinator(node: Node) {
+  return node.type === 'combinator' && /^\s+$/.test(node.value)
+}