feat(compiler-sfc): <script setup> defineProps destructure transform (#4690)

This commit is contained in:
Evan You
2021-09-27 14:24:21 -04:00
committed by GitHub
parent d84d5ecdbd
commit 467e113b95
14 changed files with 717 additions and 124 deletions

View File

@@ -0,0 +1,128 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sfc props transform aliasing 1`] = `
"import { toDisplayString as _toDisplayString } from \\"vue\\"
export default {
props: ['foo'],
setup(__props) {
let x = foo
let y = __props.foo
return (_ctx, _cache) => {
return _toDisplayString(__props.foo + __props.foo)
}
}
}"
`;
exports[`sfc props transform basic usage 1`] = `
"import { toDisplayString as _toDisplayString } from \\"vue\\"
export default {
props: ['foo'],
setup(__props) {
console.log(__props.foo)
return (_ctx, _cache) => {
return _toDisplayString(__props.foo)
}
}
}"
`;
exports[`sfc props transform default values w/ runtime declaration 1`] = `
"import { mergeDefaults as _mergeDefaults } from 'vue'
export default {
props: _mergeDefaults(['foo', 'bar'], {
foo: 1,
bar: () => {}
}),
setup(__props) {
return () => {}
}
}"
`;
exports[`sfc props transform default values w/ type declaration 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
export default /*#__PURE__*/_defineComponent({
props: {
foo: { type: Number, required: false, default: 1 },
bar: { type: Object, required: false, default: () => {} }
},
setup(__props: any) {
return () => {}
}
})"
`;
exports[`sfc props transform default values w/ type declaration, prod mode 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
export default /*#__PURE__*/_defineComponent({
props: {
foo: { default: 1 },
bar: { default: () => {} },
baz: null
},
setup(__props: any) {
return () => {}
}
})"
`;
exports[`sfc props transform nested scope 1`] = `
"export default {
props: ['foo', 'bar'],
setup(__props) {
function test(foo) {
console.log(foo)
console.log(__props.bar)
}
return () => {}
}
}"
`;
exports[`sfc props transform rest spread 1`] = `
"import { createPropsRestProxy as _createPropsRestProxy } from 'vue'
export default {
props: ['foo', 'bar', 'baz'],
setup(__props) {
const rest = _createPropsRestProxy(__props, [\\"foo\\",\\"bar\\"])
return () => {}
}
}"
`;

View File

@@ -0,0 +1,191 @@
import { BindingTypes } from '@vue/compiler-core'
import { SFCScriptCompileOptions } from '../src'
import { compileSFCScript, assertCode } from './utils'
describe('sfc props transform', () => {
function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
return compileSFCScript(src, {
inlineTemplate: true,
propsDestructureTransform: true,
...options
})
}
test('basic usage', () => {
const { content, bindings } = compile(`
<script setup>
const { foo } = defineProps(['foo'])
console.log(foo)
</script>
<template>{{ foo }}</template>
`)
expect(content).not.toMatch(`const { foo } =`)
expect(content).toMatch(`console.log(__props.foo)`)
expect(content).toMatch(`_toDisplayString(__props.foo)`)
assertCode(content)
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS
})
})
test('nested scope', () => {
const { content, bindings } = compile(`
<script setup>
const { foo, bar } = defineProps(['foo', 'bar'])
function test(foo) {
console.log(foo)
console.log(bar)
}
</script>
`)
expect(content).not.toMatch(`const { foo, bar } =`)
expect(content).toMatch(`console.log(foo)`)
expect(content).toMatch(`console.log(__props.bar)`)
assertCode(content)
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS,
test: BindingTypes.SETUP_CONST
})
})
test('default values w/ runtime declaration', () => {
const { content } = compile(`
<script setup>
const { foo = 1, bar = {} } = defineProps(['foo', 'bar'])
</script>
`)
// literals can be used as-is, non-literals are always returned from a
// function
expect(content).toMatch(`props: _mergeDefaults(['foo', 'bar'], {
foo: 1,
bar: () => {}
})`)
assertCode(content)
})
test('default values w/ type declaration', () => {
const { content } = compile(`
<script setup lang="ts">
const { foo = 1, bar = {} } = defineProps<{ foo?: number, bar?: object }>()
</script>
`)
// literals can be used as-is, non-literals are always returned from a
// function
expect(content).toMatch(`props: {
foo: { type: Number, required: false, default: 1 },
bar: { type: Object, required: false, default: () => {} }
}`)
assertCode(content)
})
test('default values w/ type declaration, prod mode', () => {
const { content } = compile(
`
<script setup lang="ts">
const { foo = 1, bar = {} } = defineProps<{ foo?: number, bar?: object, baz?: any }>()
</script>
`,
{ isProd: true }
)
// literals can be used as-is, non-literals are always returned from a
// function
expect(content).toMatch(`props: {
foo: { default: 1 },
bar: { default: () => {} },
baz: null
}`)
assertCode(content)
})
test('aliasing', () => {
const { content, bindings } = compile(`
<script setup>
const { foo: bar } = defineProps(['foo'])
let x = foo
let y = bar
</script>
<template>{{ foo + bar }}</template>
`)
expect(content).not.toMatch(`const { foo: bar } =`)
expect(content).toMatch(`let x = foo`) // should not process
expect(content).toMatch(`let y = __props.foo`)
// should convert bar to __props.foo in template expressions
expect(content).toMatch(`_toDisplayString(__props.foo + __props.foo)`)
assertCode(content)
expect(bindings).toStrictEqual({
x: BindingTypes.SETUP_LET,
y: BindingTypes.SETUP_LET,
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS_ALIASED,
__propsAliases: {
bar: 'foo'
}
})
})
test('rest spread', () => {
const { content, bindings } = compile(`
<script setup>
const { foo, bar, ...rest } = defineProps(['foo', 'bar', 'baz'])
</script>
`)
expect(content).toMatch(
`const rest = _createPropsRestProxy(__props, ["foo","bar"])`
)
assertCode(content)
expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS,
baz: BindingTypes.PROPS,
rest: BindingTypes.SETUP_CONST
})
})
describe('errors', () => {
test('should error on deep destructure', () => {
expect(() =>
compile(
`<script setup>const { foo: [bar] } = defineProps(['foo'])</script>`
)
).toThrow(`destructure does not support nested patterns`)
expect(() =>
compile(
`<script setup>const { foo: { bar } } = defineProps(['foo'])</script>`
)
).toThrow(`destructure does not support nested patterns`)
})
test('should error on computed key', () => {
expect(() =>
compile(
`<script setup>const { [foo]: bar } = defineProps(['foo'])</script>`
)
).toThrow(`destructure cannot use computed key`)
})
test('should error when used with withDefaults', () => {
expect(() =>
compile(
`<script setup lang="ts">
const { foo } = withDefaults(defineProps<{ foo: string }>(), { foo: 'foo' })
</script>`
)
).toThrow(`withDefaults() is unnecessary when using destructure`)
})
test('should error if destructure reference local vars', () => {
expect(() =>
compile(
`<script setup>
const x = 1
const {
foo = () => x
} = defineProps(['foo'])
</script>`
)
).toThrow(`cannot reference locally declared variables`)
})
})
})