feat(compiler-sfc): built-in support for css modules

This commit is contained in:
Evan You
2020-04-24 09:59:52 -04:00
parent 20d425fb19
commit fa216a0c3a
4 changed files with 333 additions and 52 deletions

View File

@@ -1,70 +1,76 @@
import { compileStyle } from '../src/compileStyle'
import { compileStyle, compileStyleAsync } from '../src/compileStyle'
import { mockWarn } from '@vue/shared'
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()
function compileScoped(source: string): string {
const res = compileStyle({
source,
filename: 'test.css',
id: 'test',
scoped: true
})
if (res.errors.length) {
res.errors.forEach(err => {
console.error(err)
})
expect(res.errors.length).toBe(0)
}
return res.code
}
test('simple selectors', () => {
expect(compile(`h1 { color: red; }`)).toMatch(`h1[test] { color: red;`)
expect(compile(`.foo { color: red; }`)).toMatch(`.foo[test] { color: red;`)
expect(compileScoped(`h1 { color: red; }`)).toMatch(
`h1[test] { color: red;`
)
expect(compileScoped(`.foo { color: red; }`)).toMatch(
`.foo[test] { color: red;`
)
})
test('descendent selector', () => {
expect(compile(`h1 .foo { color: red; }`)).toMatch(
expect(compileScoped(`h1 .foo { color: red; }`)).toMatch(
`h1 .foo[test] { color: red;`
)
})
test('multiple selectors', () => {
expect(compile(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
expect(compileScoped(`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(
expect(compileScoped(`.foo:after { color: red; }`)).toMatch(
`.foo[test]:after { color: red;`
)
})
test('pseudo element', () => {
expect(compile(`::selection { display: none; }`)).toMatch(
expect(compileScoped(`::selection { display: none; }`)).toMatch(
'[test]::selection {'
)
})
test('spaces before pseudo element', () => {
const code = compile(`.abc, ::selection { color: red; }`)
const code = compileScoped(`.abc, ::selection { color: red; }`)
expect(code).toMatch('.abc[test],')
expect(code).toMatch('[test]::selection {')
})
test('::v-deep', () => {
expect(compile(`::v-deep(.foo) { color: red; }`)).toMatchInlineSnapshot(`
expect(compileScoped(`::v-deep(.foo) { color: red; }`))
.toMatchInlineSnapshot(`
"[test] .foo { color: red;
}"
`)
expect(compile(`::v-deep(.foo .bar) { color: red; }`))
expect(compileScoped(`::v-deep(.foo .bar) { color: red; }`))
.toMatchInlineSnapshot(`
"[test] .foo .bar { color: red;
}"
`)
expect(compile(`.baz .qux ::v-deep(.foo .bar) { color: red; }`))
expect(compileScoped(`.baz .qux ::v-deep(.foo .bar) { color: red; }`))
.toMatchInlineSnapshot(`
".baz .qux[test] .foo .bar { color: red;
}"
@@ -72,16 +78,17 @@ describe('SFC scoped CSS', () => {
})
test('::v-slotted', () => {
expect(compile(`::v-slotted(.foo) { color: red; }`)).toMatchInlineSnapshot(`
expect(compileScoped(`::v-slotted(.foo) { color: red; }`))
.toMatchInlineSnapshot(`
".foo[test-s] { color: red;
}"
`)
expect(compile(`::v-slotted(.foo .bar) { color: red; }`))
expect(compileScoped(`::v-slotted(.foo .bar) { color: red; }`))
.toMatchInlineSnapshot(`
".foo .bar[test-s] { color: red;
}"
`)
expect(compile(`.baz .qux ::v-slotted(.foo .bar) { color: red; }`))
expect(compileScoped(`.baz .qux ::v-slotted(.foo .bar) { color: red; }`))
.toMatchInlineSnapshot(`
".baz .qux .foo .bar[test-s] { color: red;
}"
@@ -89,17 +96,18 @@ describe('SFC scoped CSS', () => {
})
test('::v-global', () => {
expect(compile(`::v-global(.foo) { color: red; }`)).toMatchInlineSnapshot(`
expect(compileScoped(`::v-global(.foo) { color: red; }`))
.toMatchInlineSnapshot(`
".foo { color: red;
}"
`)
expect(compile(`::v-global(.foo .bar) { color: red; }`))
expect(compileScoped(`::v-global(.foo .bar) { color: red; }`))
.toMatchInlineSnapshot(`
".foo .bar { color: red;
}"
`)
// global ignores anything before it
expect(compile(`.baz .qux ::v-global(.foo .bar) { color: red; }`))
expect(compileScoped(`.baz .qux ::v-global(.foo .bar) { color: red; }`))
.toMatchInlineSnapshot(`
".foo .bar { color: red;
}"
@@ -107,7 +115,7 @@ describe('SFC scoped CSS', () => {
})
test('media query', () => {
expect(compile(`@media print { .foo { color: red }}`))
expect(compileScoped(`@media print { .foo { color: red }}`))
.toMatchInlineSnapshot(`
"@media print {
.foo[test] { color: red
@@ -116,7 +124,7 @@ describe('SFC scoped CSS', () => {
})
test('supports query', () => {
expect(compile(`@supports(display: grid) { .foo { display: grid }}`))
expect(compileScoped(`@supports(display: grid) { .foo { display: grid }}`))
.toMatchInlineSnapshot(`
"@supports(display: grid) {
.foo[test] { display: grid
@@ -125,7 +133,7 @@ describe('SFC scoped CSS', () => {
})
test('scoped keyframes', () => {
const style = compile(`
const style = compileScoped(`
.anim {
animation: color 5s infinite, other 5s;
}
@@ -184,13 +192,7 @@ describe('SFC scoped CSS', () => {
// vue-loader/#1370
test('spaces after selector', () => {
const { code } = compileStyle({
source: `.foo , .bar { color: red; }`,
filename: 'test.css',
id: 'test'
})
expect(code).toMatchInlineSnapshot(`
expect(compileScoped(`.foo , .bar { color: red; }`)).toMatchInlineSnapshot(`
".foo[test], .bar[test] { color: red;
}"
`)
@@ -198,11 +200,12 @@ describe('SFC scoped CSS', () => {
describe('deprecated syntax', () => {
test('::v-deep as combinator', () => {
expect(compile(`::v-deep .foo { color: red; }`)).toMatchInlineSnapshot(`
expect(compileScoped(`::v-deep .foo { color: red; }`))
.toMatchInlineSnapshot(`
"[test] .foo { color: red;
}"
`)
expect(compile(`.bar ::v-deep .foo { color: red; }`))
expect(compileScoped(`.bar ::v-deep .foo { color: red; }`))
.toMatchInlineSnapshot(`
".bar[test] .foo { color: red;
}"
@@ -213,7 +216,7 @@ describe('SFC scoped CSS', () => {
})
test('>>> (deprecated syntax)', () => {
const code = compile(`>>> .foo { color: red; }`)
const code = compileScoped(`>>> .foo { color: red; }`)
expect(code).toMatchInlineSnapshot(`
"[test] .foo { color: red;
}"
@@ -224,7 +227,7 @@ describe('SFC scoped CSS', () => {
})
test('/deep/ (deprecated syntax)', () => {
const code = compile(`/deep/ .foo { color: red; }`)
const code = compileScoped(`/deep/ .foo { color: red; }`)
expect(code).toMatchInlineSnapshot(`
"[test] .foo { color: red;
}"
@@ -235,3 +238,35 @@ describe('SFC scoped CSS', () => {
})
})
})
describe('SFC CSS modules', () => {
test('should include resulting classes object in result', async () => {
const result = await compileStyleAsync({
source: `.red { color: red }\n.green { color: green }\n:global(.blue) { color: blue }`,
filename: `test.css`,
id: 'test',
modules: true
})
expect(result.modules).toBeDefined()
expect(result.modules!.red).toMatch('_red_')
expect(result.modules!.green).toMatch('_green_')
expect(result.modules!.blue).toBeUndefined()
})
test('postcss-modules options', async () => {
const result = await compileStyleAsync({
source: `:local(.foo-bar) { color: red }\n.baz-qux { color: green }`,
filename: `test.css`,
id: 'test',
modules: true,
modulesOptions: {
scopeBehaviour: 'global',
generateScopedName: `[name]__[local]__[hash:base64:5]`,
localsConvention: 'camelCaseOnly'
}
})
expect(result.modules).toBeDefined()
expect(result.modules!.fooBar).toMatch('__foo-bar__')
expect(result.modules!.bazQux).toBeUndefined()
})
})