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

feat(compiler-sfc): enable reactive props destructure by default #7986

Merged
merged 6 commits into from
Mar 30, 2023
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
18 changes: 17 additions & 1 deletion packages/compiler-core/src/babelUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import type {
Program,
ImportDefaultSpecifier,
ImportNamespaceSpecifier,
ImportSpecifier
ImportSpecifier,
CallExpression
} from '@babel/types'
import { walk } from 'estree-walker'

Expand Down Expand Up @@ -449,3 +450,18 @@ export function unwrapTSNode(node: Node): Node {
return node
}
}

export function isCallOf(
node: Node | null | undefined,
test: string | ((id: string) => boolean) | null | undefined
): node is CallExpression {
return !!(
node &&
test &&
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
(typeof test === 'string'
? node.callee.name === test
: test(node.callee.name))
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

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 > computed static key 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/ array runtime declaration 1`] = `
"import { mergeDefaults as _mergeDefaults } from 'vue'

export default {
props: _mergeDefaults(['foo', 'bar', 'baz'], {
foo: 1,
bar: () => ({}),
func: () => {}, __skip_func: true
}),
setup(__props) {



return () => {}
}

}"
`;

exports[`sfc props transform > default values w/ object runtime declaration 1`] = `
"import { mergeDefaults as _mergeDefaults } from 'vue'

export default {
props: _mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, {
foo: 1,
bar: () => ({}),
func: () => {}, __skip_func: true,
ext: x, __skip_ext: true
}),
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: () => ({}) },
func: { type: Function, 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,
boola: { type: Boolean },
boolb: { type: [Boolean, Number] },
func: { type: Function, default: () => {} }
},
setup(__props: any) {



return () => {}
}

})"
`;

exports[`sfc props transform > multiple variable declarations 1`] = `
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"


export default {
props: ['foo'],
setup(__props) {

const bar = 'fish', hello = 'world'

return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString(__props.foo) + \\" \\" + _toDisplayString(hello) + \\" \\" + _toDisplayString(bar), 1 /* TEXT */))
}
}

}"
`;

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 > non-identifier prop names 1`] = `
"import { toDisplayString as _toDisplayString } from \\"vue\\"


export default {
props: { 'foo.bar': Function },
setup(__props) {


let x = __props[\\"foo.bar\\"]

return (_ctx, _cache) => {
return _toDisplayString(__props[\\"foo.bar\\"])
}
}

}"
`;

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 () => {}
}

}"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ describe('sfc props transform', () => {
function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
return compileSFCScript(src, {
inlineTemplate: true,
reactivityTransform: true,
...options
})
}
Expand Down Expand Up @@ -211,23 +210,6 @@ describe('sfc props transform', () => {
})
})

test('$$() escape', () => {
const { content } = compile(`
<script setup>
const { foo, bar: baz } = defineProps(['foo'])
console.log($$(foo))
console.log($$(baz))
$$({ foo, baz })
</script>
`)
expect(content).toMatch(`const __props_foo = _toRef(__props, 'foo')`)
expect(content).toMatch(`const __props_bar = _toRef(__props, 'bar')`)
expect(content).toMatch(`console.log((__props_foo))`)
expect(content).toMatch(`console.log((__props_bar))`)
expect(content).toMatch(`({ foo: __props_foo, baz: __props_bar })`)
assertCode(content)
})

// #6960
test('computed static key', () => {
const { content, bindings } = compile(`
Expand Down Expand Up @@ -292,15 +274,57 @@ describe('sfc props transform', () => {
).toThrow(`cannot reference locally declared variables`)
})

test('should error if assignment to constant variable', () => {
test('should error if assignment to destructured prop binding', () => {
expect(() =>
compile(
`<script setup>
const { foo } = defineProps(['foo'])
foo = 'bar'
</script>`
)
).toThrow(`Assignment to constant variable.`)
).toThrow(`Cannot assign to destructured props`)

expect(() =>
compile(
`<script setup>
let { foo } = defineProps(['foo'])
foo = 'bar'
</script>`
)
).toThrow(`Cannot assign to destructured props`)
})

test('should error when watching destructured prop', () => {
expect(() =>
compile(
`<script setup>
import { watch } from 'vue'
const { foo } = defineProps(['foo'])
watch(foo, () => {})
</script>`
)
).toThrow(`"foo" is a destructured prop and cannot be directly watched.`)

expect(() =>
compile(
`<script setup>
import { watch as w } from 'vue'
const { foo } = defineProps(['foo'])
w(foo, () => {})
</script>`
)
).toThrow(`"foo" is a destructured prop and cannot be directly watched.`)
})

// not comprehensive, but should help for most common cases
test('should error if default value type does not match declared type', () => {
expect(() =>
compile(
`<script setup lang="ts">
const { foo = 'hello' } = defineProps<{ foo?: number }>()
</script>`
)
).toThrow(`Default value of prop "foo" does not match declared type.`)
})
})
})