fix(sfc): fix v-slotted attribute injection

This commit is contained in:
Evan You 2019-12-19 17:54:52 -05:00
parent 3a3a24d621
commit 362831d8ab
2 changed files with 93 additions and 55 deletions

View File

@ -55,52 +55,73 @@ describe('SFC scoped CSS', () => {
}) })
test('::v-deep', () => { test('::v-deep', () => {
expect(compile(`::v-deep(.foo) { color: red; }`)).toMatch( expect(compile(`::v-deep(.foo) { color: red; }`)).toMatchInlineSnapshot(`
`[test] .foo { color: red;` "[test] .foo { color: red;
) }"
expect(compile(`::v-deep(.foo .bar) { color: red; }`)).toMatch( `)
`[test] .foo .bar { color: red;` expect(compile(`::v-deep(.foo .bar) { color: red; }`))
) .toMatchInlineSnapshot(`
expect(compile(`.baz .qux ::v-deep(.foo .bar) { color: red; }`)).toMatch( "[test] .foo .bar { color: red;
`.baz .qux[test] .foo .bar { color: red;` }"
) `)
expect(compile(`.baz .qux ::v-deep(.foo .bar) { color: red; }`))
.toMatchInlineSnapshot(`
".baz .qux[test] .foo .bar { color: red;
}"
`)
}) })
test('::v-slotted', () => { test('::v-slotted', () => {
expect(compile(`::v-slotted(.foo) { color: red; }`)).toMatch( expect(compile(`::v-slotted(.foo) { color: red; }`)).toMatchInlineSnapshot(`
`.foo[test-s] { color: red;` ".foo[test-s] { color: red;
) }"
expect(compile(`::v-slotted(.foo .bar) { color: red; }`)).toMatch( `)
`.foo .bar[test-s] { color: red;` expect(compile(`::v-slotted(.foo .bar) { color: red; }`))
) .toMatchInlineSnapshot(`
expect(compile(`.baz .qux ::v-slotted(.foo .bar) { color: red; }`)).toMatch( ".foo .bar[test-s] { color: red;
`.baz .qux[test] .foo .bar[test-s] { color: red;` }"
) `)
expect(compile(`.baz .qux ::v-slotted(.foo .bar) { color: red; }`))
.toMatchInlineSnapshot(`
".baz .qux .foo .bar[test-s] { color: red;
}"
`)
}) })
test('::v-global', () => { test('::v-global', () => {
expect(compile(`::v-global(.foo) { color: red; }`)).toMatch( expect(compile(`::v-global(.foo) { color: red; }`)).toMatchInlineSnapshot(`
`.foo { color: red;` ".foo { color: red;
) }"
expect(compile(`::v-global(.foo .bar) { color: red; }`)).toMatch( `)
`.foo .bar { color: red;` expect(compile(`::v-global(.foo .bar) { color: red; }`))
) .toMatchInlineSnapshot(`
".foo .bar { color: red;
}"
`)
// global ignores anything before it // global ignores anything before it
expect(compile(`.baz .qux ::v-global(.foo .bar) { color: red; }`)).toMatch( expect(compile(`.baz .qux ::v-global(.foo .bar) { color: red; }`))
`.foo .bar { color: red;` .toMatchInlineSnapshot(`
) ".foo .bar { color: red;
}"
`)
}) })
test('media query', () => { test('media query', () => {
expect(compile(`@media print { .foo { color: red }}`)).toMatch( expect(compile(`@media print { .foo { color: red }}`))
/@media print {\s+\.foo\[test\] \{ color: red/ .toMatchInlineSnapshot(`
) "@media print {
.foo[test] { color: red
}}"
`)
}) })
test('supports query', () => { test('supports query', () => {
expect( expect(compile(`@supports(display: grid) { .foo { display: grid }}`))
compile(`@supports(display: grid) { .foo { display: grid }}`) .toMatchInlineSnapshot(`
).toMatch(/@supports\(display: grid\) {\s+\.foo\[test\] \{ display: grid/) "@supports(display: grid) {
.foo[test] { display: grid
}}"
`)
}) })
test('scoped keyframes', () => { test('scoped keyframes', () => {
@ -169,17 +190,23 @@ describe('SFC scoped CSS', () => {
id: 'test' id: 'test'
}) })
expect(code).toMatch(`.foo[test], .bar[test] { color: red;`) expect(code).toMatchInlineSnapshot(`
".foo[test], .bar[test] { color: red;
}"
`)
}) })
describe('deprecated syntax', () => { describe('deprecated syntax', () => {
test('::v-deep as combinator', () => { test('::v-deep as combinator', () => {
expect(compile(`::v-deep .foo { color: red; }`)).toMatch( expect(compile(`::v-deep .foo { color: red; }`)).toMatchInlineSnapshot(`
`[test] .foo { color: red;` "[test] .foo { color: red;
) }"
expect(compile(`.bar ::v-deep .foo { color: red; }`)).toMatch( `)
`.bar[test] .foo { color: red;` expect(compile(`.bar ::v-deep .foo { color: red; }`))
) .toMatchInlineSnapshot(`
".bar[test] .foo { color: red;
}"
`)
expect( expect(
`::v-deep usage as a combinator has been deprecated.` `::v-deep usage as a combinator has been deprecated.`
).toHaveBeenWarned() ).toHaveBeenWarned()
@ -187,7 +214,10 @@ describe('SFC scoped CSS', () => {
test('>>> (deprecated syntax)', () => { test('>>> (deprecated syntax)', () => {
const code = compile(`>>> .foo { color: red; }`) const code = compile(`>>> .foo { color: red; }`)
expect(code).toMatch(`[test] .foo { color: red;`) expect(code).toMatchInlineSnapshot(`
"[test] .foo { color: red;
}"
`)
expect( expect(
`the >>> and /deep/ combinators have been deprecated.` `the >>> and /deep/ combinators have been deprecated.`
).toHaveBeenWarned() ).toHaveBeenWarned()
@ -195,7 +225,10 @@ describe('SFC scoped CSS', () => {
test('/deep/ (deprecated syntax)', () => { test('/deep/ (deprecated syntax)', () => {
const code = compile(`/deep/ .foo { color: red; }`) const code = compile(`/deep/ .foo { color: red; }`)
expect(code).toMatch(`[test] .foo { color: red;`) expect(code).toMatchInlineSnapshot(`
"[test] .foo { color: red;
}"
`)
expect( expect(
`the >>> and /deep/ combinators have been deprecated.` `the >>> and /deep/ combinators have been deprecated.`
).toHaveBeenWarned() ).toHaveBeenWarned()

View File

@ -22,7 +22,7 @@ export default postcss.plugin('vue-scoped', (options: any) => (root: Root) => {
node.selector = selectorParser(selectors => { node.selector = selectorParser(selectors => {
function rewriteSelector(selector: Selector, slotted?: boolean) { function rewriteSelector(selector: Selector, slotted?: boolean) {
let node: Node | null = null let node: Node | null = null
let shouldInject = true
// 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
@ -81,6 +81,9 @@ export default postcss.plugin('vue-scoped', (options: any) => (root: Root) => {
rewriteSelector(n.nodes[0] as Selector, true /* slotted */) rewriteSelector(n.nodes[0] as Selector, true /* slotted */)
selector.insertAfter(n, n.nodes[0]) selector.insertAfter(n, n.nodes[0])
selector.removeChild(n) selector.removeChild(n)
// since slotted attribute already scopes the selector there's no
// need for the non-slot attribute.
shouldInject = false
return false return false
} }
@ -107,18 +110,20 @@ export default postcss.plugin('vue-scoped', (options: any) => (root: Root) => {
selector.first.spaces.before = '' selector.first.spaces.before = ''
} }
const idToAdd = slotted ? id + '-s' : id if (shouldInject) {
selector.insertAfter( const idToAdd = slotted ? id + '-s' : id
// If node is null it means we need to inject [id] at the start selector.insertAfter(
// insertAfter can handle `null` here // If node is null it means we need to inject [id] at the start
node as any, // insertAfter can handle `null` here
selectorParser.attribute({ node as any,
attribute: idToAdd, selectorParser.attribute({
value: idToAdd, attribute: idToAdd,
raws: {}, value: idToAdd,
quoteMark: `"` raws: {},
}) quoteMark: `"`
) })
)
}
} }
selectors.each(selector => rewriteSelector(selector as Selector)) selectors.each(selector => rewriteSelector(selector as Selector))
}).processSync(node.selector) }).processSync(node.selector)