feat(compiler-sfc): new SFC css varaible injection implementation

ref: https://github.com/vuejs/rfcs/pull/231
This commit is contained in:
Evan You
2020-11-16 18:27:15 -05:00
parent 62372e9943
commit 41bb7fa330
16 changed files with 497 additions and 341 deletions

View File

@@ -1,62 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SFC compile <script setup> CSS vars injection <script> w/ default export 1`] = `
"const __default__ = { setup() {} }
import { useCssVars as _useCssVars } from 'vue'
const __injectCSSVars__ = () => {
_useCssVars(_ctx => ({ color: _ctx.color }))
}
const __setup__ = __default__.setup
__default__.setup = __setup__
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
: __injectCSSVars__
export default __default__"
`;
exports[`SFC compile <script setup> CSS vars injection <script> w/ default export in strings/comments 1`] = `
"
// export default {}
const __default__ = {}
import { useCssVars as _useCssVars } from 'vue'
const __injectCSSVars__ = () => {
_useCssVars(_ctx => ({ color: _ctx.color }))
}
const __setup__ = __default__.setup
__default__.setup = __setup__
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
: __injectCSSVars__
export default __default__"
`;
exports[`SFC compile <script setup> CSS vars injection <script> w/ no default export 1`] = `
"const a = 1
const __default__ = {}
import { useCssVars as _useCssVars } from 'vue'
const __injectCSSVars__ = () => {
_useCssVars(_ctx => ({ color: _ctx.color }))
}
const __setup__ = __default__.setup
__default__.setup = __setup__
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
: __injectCSSVars__
export default __default__"
`;
exports[`SFC compile <script setup> CSS vars injection w/ <script setup> 1`] = `
"import { useCssVars as _useCssVars } from 'vue'
export default {
expose: [],
setup() {
const color = 'red'
_useCssVars(_ctx => ({ color }))
return { color }
}
}"
`;
exports[`SFC compile <script setup> defineOptions() 1`] = `
"export default {
expose: [],
@@ -86,7 +29,7 @@ export default {
default: () => bar
}
},
setup() {
setup(__props) {
@@ -104,7 +47,7 @@ exports[`SFC compile <script setup> errors should allow defineOptions() referenc
default: bar => bar + 1
}
},
setup() {
setup(__props) {
const bar = 1
@@ -121,7 +64,7 @@ import { ref } from 'vue'
export default {
expose: [],
setup() {
setup(__props) {
const foo = _ref(1)
@@ -136,7 +79,7 @@ exports[`SFC compile <script setup> imports import dedupe between <script> and <
export default {
expose: [],
setup() {
setup(__props) {
x()
@@ -152,7 +95,7 @@ exports[`SFC compile <script setup> imports should extract comment for import or
export default {
expose: [],
setup() {
setup(__props) {
return { a, b }
@@ -165,7 +108,7 @@ exports[`SFC compile <script setup> imports should hoist and expose imports 1`]
"import { ref } from 'vue'
export default {
expose: [],
setup() {
setup(__props) {
return { ref }
}
@@ -182,13 +125,13 @@ import { ref } from 'vue'
export default {
expose: [],
setup() {
setup(__props) {
const count = ref(0)
const constant = {}
function fn() {}
return (_ctx, _cache, $props, $setup, $data, $options) => {
return (_ctx, _cache) => {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(Foo),
_createVNode(\\"div\\", { onClick: fn }, _toDisplayString(_unref(count)) + \\" \\" + _toDisplayString(constant) + \\" \\" + _toDisplayString(_unref(other)), 1 /* TEXT */)
@@ -208,11 +151,11 @@ import { ref } from 'vue'
export default {
expose: [],
setup() {
setup(__props) {
const count = ref(0)
return (_ctx, _cache, $props, $setup, $data, $options) => {
return (_ctx, _cache) => {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode(\\"div\\", null, _toDisplayString(_unref(count)), 1 /* TEXT */),
_hoisted_1
@@ -228,7 +171,7 @@ exports[`SFC compile <script setup> ref: syntax sugar accessing ref binding 1`]
export default {
expose: [],
setup() {
setup(__props) {
const a = _ref(1)
console.log(a.value)
@@ -247,7 +190,7 @@ exports[`SFC compile <script setup> ref: syntax sugar array destructure 1`] = `
export default {
expose: [],
setup() {
setup(__props) {
const n = _ref(1), [__a, __b = 1, ...__c] = useFoo()
const a = _ref(__a);
@@ -266,7 +209,7 @@ exports[`SFC compile <script setup> ref: syntax sugar convert ref declarations 1
export default {
expose: [],
setup() {
setup(__props) {
const foo = _ref()
const a = _ref(1)
@@ -287,7 +230,7 @@ exports[`SFC compile <script setup> ref: syntax sugar multi ref declarations 1`]
export default {
expose: [],
setup() {
setup(__props) {
const a = _ref(1), b = _ref(2), c = _ref({
count: 0
@@ -304,7 +247,7 @@ exports[`SFC compile <script setup> ref: syntax sugar mutating ref binding 1`] =
export default {
expose: [],
setup() {
setup(__props) {
const a = _ref(1)
const b = _ref({ count: 0 })
@@ -326,7 +269,7 @@ exports[`SFC compile <script setup> ref: syntax sugar nested destructure 1`] = `
export default {
expose: [],
setup() {
setup(__props) {
const [{ a: { b: __b }}] = useFoo()
const b = _ref(__b);
@@ -346,7 +289,7 @@ exports[`SFC compile <script setup> ref: syntax sugar object destructure 1`] = `
export default {
expose: [],
setup() {
setup(__props) {
const n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()
const a = _ref(__a);
@@ -365,7 +308,7 @@ return { n, a, c, d, f, g }
exports[`SFC compile <script setup> ref: syntax sugar should not convert non ref labels 1`] = `
"export default {
expose: [],
setup() {
setup(__props) {
foo: a = 1, b = 2, c = {
count: 0
@@ -382,7 +325,7 @@ exports[`SFC compile <script setup> ref: syntax sugar using ref binding in prope
export default {
expose: [],
setup() {
setup(__props) {
const a = _ref(1)
const b = { a: a.value }
@@ -401,7 +344,7 @@ exports[`SFC compile <script setup> should expose top level declarations 1`] = `
export default {
expose: [],
setup() {
setup(__props) {
let a = 1
const b = 2
@@ -509,7 +452,7 @@ export default _defineComponent({
literalUnionMixed: { type: [String, Number, Boolean], required: true },
intersection: { type: Object, required: true }
} as unknown as undefined,
setup() {
setup(__props) {
@@ -526,7 +469,7 @@ export interface Foo {}
export default _defineComponent({
expose: [],
setup() {
setup(__props) {
return { }

View File

@@ -0,0 +1,107 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CSS vars injection codegen <script> w/ default export 1`] = `
"const __default__ = { setup() {} }
import { useCssVars as _useCssVars } from 'vue'
const __injectCSSVars__ = () => {
_useCssVars(_ctx => ({
color: (_ctx.color)
}), \\"xxxxxxxx\\")}
const __setup__ = __default__.setup
__default__.setup = __setup__
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
: __injectCSSVars__
export default __default__"
`;
exports[`CSS vars injection codegen <script> w/ default export in strings/comments 1`] = `
"
// export default {}
const __default__ = {}
import { useCssVars as _useCssVars } from 'vue'
const __injectCSSVars__ = () => {
_useCssVars(_ctx => ({
color: (_ctx.color)
}), \\"xxxxxxxx\\")}
const __setup__ = __default__.setup
__default__.setup = __setup__
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
: __injectCSSVars__
export default __default__"
`;
exports[`CSS vars injection codegen <script> w/ no default export 1`] = `
"const a = 1
const __default__ = {}
import { useCssVars as _useCssVars } from 'vue'
const __injectCSSVars__ = () => {
_useCssVars(_ctx => ({
color: (_ctx.color)
}), \\"xxxxxxxx\\")}
const __setup__ = __default__.setup
__default__.setup = __setup__
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
: __injectCSSVars__
export default __default__"
`;
exports[`CSS vars injection codegen w/ <script setup> 1`] = `
"import { useCssVars as _useCssVars, unref as _unref } from 'vue'
export default {
expose: [],
setup(__props) {
_useCssVars(_ctx => ({
color: (color)
}), \\"xxxxxxxx\\")
const color = 'red'
return { color }
}
}"
`;
exports[`CSS vars injection generating correct code for nested paths 1`] = `
"const a = 1
const __default__ = {}
import { useCssVars as _useCssVars } from 'vue'
const __injectCSSVars__ = () => {
_useCssVars(_ctx => ({
color: (_ctx.color),
font_size: (_ctx.font.size)
}), \\"xxxxxxxx\\")}
const __setup__ = __default__.setup
__default__.setup = __setup__
? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }
: __injectCSSVars__
export default __default__"
`;
exports[`CSS vars injection w/ <script setup> binding analysis 1`] = `
"import { useCssVars as _useCssVars, unref as _unref } from 'vue'
import { ref } from 'vue'
export default {
expose: [],
props: {
foo: String
},
setup(__props) {
_useCssVars(_ctx => ({
color: (color),
size: (_unref(size)),
foo: (__props.foo)
}), \\"xxxxxxxx\\")
const color = 'red'
const size = ref('10px')
return { color, size, ref }
}
}"
`;

View File

@@ -1,25 +1,4 @@
import { parse, SFCScriptCompileOptions, compileScript } from '../src'
import { parse as babelParse } from '@babel/parser'
import { babelParserDefaultPlugins } from '@vue/shared'
function compile(src: string, options?: SFCScriptCompileOptions) {
const { descriptor } = parse(src)
return compileScript(descriptor, options)
}
function assertCode(code: string) {
// parse the generated code to make sure it is valid
try {
babelParse(code, {
sourceType: 'module',
plugins: [...babelParserDefaultPlugins, 'typescript']
})
} catch (e) {
console.log(code)
throw e
}
expect(code).toMatchSnapshot()
}
import { compileSFCScript as compile, assertCode } from './utils'
describe('SFC compile <script setup>', () => {
test('should expose top level declarations', () => {
@@ -323,51 +302,10 @@ const { props, emit } = defineOptions({
})
})
describe('CSS vars injection', () => {
test('<script> w/ no default export', () => {
assertCode(
compile(
`<script>const a = 1</script>\n` +
`<style vars="{ color }">div{ color: var(--color); }</style>`
).content
)
})
test('<script> w/ default export', () => {
assertCode(
compile(
`<script>export default { setup() {} }</script>\n` +
`<style vars="{ color }">div{ color: var(--color); }</style>`
).content
)
})
test('<script> w/ default export in strings/comments', () => {
assertCode(
compile(
`<script>
// export default {}
export default {}
</script>\n` +
`<style vars="{ color }">div{ color: var(--color); }</style>`
).content
)
})
test('w/ <script setup>', () => {
assertCode(
compile(
`<script setup>const color = 'red'</script>\n` +
`<style vars="{ color }">div{ color: var(--color); }</style>`
).content
)
})
})
describe('async/await detection', () => {
function assertAwaitDetection(code: string, shouldAsync = true) {
const { content } = compile(`<script setup>${code}</script>`)
expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup()`)
expect(content).toMatch(`${shouldAsync ? `async ` : ``}setup(`)
}
test('expression statement', () => {

View File

@@ -9,27 +9,27 @@ import {
} from '../src/compileStyle'
import path from 'path'
describe('SFC scoped CSS', () => {
function compileScoped(
source: string,
options?: Partial<SFCStyleCompileOptions>
): string {
const res = compileStyle({
source,
filename: 'test.css',
id: 'test',
scoped: true,
...options
export function compileScoped(
source: string,
options?: Partial<SFCStyleCompileOptions>
): string {
const res = compileStyle({
source,
filename: 'test.css',
id: 'test',
scoped: true,
...options
})
if (res.errors.length) {
res.errors.forEach(err => {
console.error(err)
})
if (res.errors.length) {
res.errors.forEach(err => {
console.error(err)
})
expect(res.errors.length).toBe(0)
}
return res.code
expect(res.errors.length).toBe(0)
}
return res.code
}
describe('SFC scoped CSS', () => {
test('simple selectors', () => {
expect(compileScoped(`h1 { color: red; }`)).toMatch(
`h1[test] { color: red;`
@@ -266,27 +266,6 @@ describe('SFC scoped CSS', () => {
).toHaveBeenWarned()
})
})
describe('<style vars>', () => {
test('should rewrite CSS vars in scoped mode', () => {
const code = compileScoped(
`.foo {
color: var(--color);
font-size: var(--global:font);
}`,
{
id: 'data-v-test',
vars: true
}
)
expect(code).toMatchInlineSnapshot(`
".foo[data-v-test] {
color: var(--test-color);
font-size: var(--font);
}"
`)
})
})
})
describe('SFC CSS modules', () => {

View File

@@ -0,0 +1,111 @@
import { compileStyle } from '../src'
import { compileSFCScript, assertCode } from './utils'
describe('CSS vars injection', () => {
describe('codegen', () => {
test('<script> w/ no default export', () => {
assertCode(
compileSFCScript(
`<script>const a = 1</script>\n` +
`<style>div{ color: var(--v-bind:color); }</style>`
).content
)
})
test('<script> w/ default export', () => {
assertCode(
compileSFCScript(
`<script>export default { setup() {} }</script>\n` +
`<style>div{ color: var(--:color); }</style>`
).content
)
})
test('<script> w/ default export in strings/comments', () => {
assertCode(
compileSFCScript(
`<script>
// export default {}
export default {}
</script>\n` + `<style>div{ color: var(--:color); }</style>`
).content
)
})
test('w/ <script setup>', () => {
assertCode(
compileSFCScript(
`<script setup>const color = 'red'</script>\n` +
`<style>div{ color: var(--:color); }</style>`
).content
)
})
})
test('generating correct code for nested paths', () => {
const { content } = compileSFCScript(
`<script>const a = 1</script>\n` +
`<style>div{
color: var(--v-bind:color);
color: var(--v-bind:font.size);
}</style>`
)
expect(content).toMatch(`_useCssVars(_ctx => ({
color: (_ctx.color),
font_size: (_ctx.font.size)
})`)
assertCode(content)
})
test('w/ <script setup> binding analysis', () => {
const { content } = compileSFCScript(
`<script setup>
import { defineOptions, ref } from 'vue'
const color = 'red'
const size = ref('10px')
defineOptions({
props: {
foo: String
}
})
</script>\n` +
`<style>
div {
color: var(--:color);
font-size: var(--:size);
border: var(--:foo);
}
</style>`
)
// should handle:
// 1. local const bindings
// 2. local potential ref bindings
// 3. props bindings (analyzed)
expect(content).toMatch(`_useCssVars(_ctx => ({
color: (color),
size: (_unref(size)),
foo: (__props.foo)
})`)
expect(content).toMatch(
`import { useCssVars as _useCssVars, unref as _unref } from 'vue'`
)
assertCode(content)
})
test('should rewrite CSS vars in scoped mode', () => {
const { code } = compileStyle({
source: `.foo {
color: var(--v-bind:color);
font-size: var(--:font.size);
}`,
filename: 'test.css',
id: 'data-v-test'
})
expect(code).toMatchInlineSnapshot(`
".foo {
color: var(--test-color);
font-size: var(--test-font_size);
}"
`)
})
})

View File

@@ -0,0 +1,28 @@
import { parse, SFCScriptCompileOptions, compileScript } from '../src'
import { parse as babelParse } from '@babel/parser'
import { babelParserDefaultPlugins } from '@vue/shared'
export function compileSFCScript(
src: string,
options?: Partial<SFCScriptCompileOptions>
) {
const { descriptor } = parse(src)
return compileScript(descriptor, {
...options,
id: 'xxxxxxxx'
})
}
export function assertCode(code: string) {
// parse the generated code to make sure it is valid
try {
babelParse(code, {
sourceType: 'module',
plugins: [...babelParserDefaultPlugins, 'typescript']
})
} catch (e) {
console.log(code)
throw e
}
expect(code).toMatchSnapshot()
}