test(sfc): test scoped css compilation
This commit is contained in:
parent
b689ca6e85
commit
a894a350cd
192
packages/compiler-sfc/__tests__/stylePluginScoped.spec.ts
Normal file
192
packages/compiler-sfc/__tests__/stylePluginScoped.spec.ts
Normal file
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user