Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<script setup> defineProps destructure transform #4690

Merged
merged 4 commits into from Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/compiler-core/src/options.ts
Expand Up @@ -82,6 +82,11 @@ export const enum BindingTypes {
* declared as a prop
*/
PROPS = 'props',
/**
* a local alias of a `<script setup>` destructured prop.
* the original is stored in __propsAliases of the bindingMetadata object.
*/
PROPS_ALIASED = 'props-aliased',
/**
* a let binding (may or may not be a ref)
*/
Expand Down Expand Up @@ -110,6 +115,7 @@ export type BindingMetadata = {
[key: string]: BindingTypes | undefined
} & {
__isScriptSetup?: boolean
__propsAliases?: Record<string, string>
}

interface SharedTransformCodegenOptions {
Expand Down
5 changes: 5 additions & 0 deletions packages/compiler-core/src/transforms/transformExpression.ts
Expand Up @@ -188,11 +188,16 @@ export function processExpression(
// use __props which is generated by compileScript so in ts mode
// it gets correct type
return `__props.${raw}`
} else if (type === BindingTypes.PROPS_ALIASED) {
// prop with a different local alias (from defineProps() destructure)
return `__props.${bindingMetadata.__propsAliases![raw]}`
}
} else {
if (type && type.startsWith('setup')) {
// setup bindings in non-inline mode
return `$setup.${raw}`
} else if (type === BindingTypes.PROPS_ALIASED) {
return `$props.${bindingMetadata.__propsAliases![raw]}`
} else if (type) {
return `$${type}.${raw}`
}
Expand Down
@@ -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 () => {}
}

}"
`;
191 changes: 191 additions & 0 deletions packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts
@@ -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`)
})
})
})