Skip to content

Commit ba9c2ae

Browse files
authored
feat(compiler-sfc): enable reactive props destructure by default and deprecate withDefaults() (#7986)
1 parent e10a89e commit ba9c2ae

File tree

6 files changed

+593
-68
lines changed

6 files changed

+593
-68
lines changed

packages/compiler-core/src/babelUtils.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import type {
99
Program,
1010
ImportDefaultSpecifier,
1111
ImportNamespaceSpecifier,
12-
ImportSpecifier
12+
ImportSpecifier,
13+
CallExpression
1314
} from '@babel/types'
1415
import { walk } from 'estree-walker'
1516

@@ -449,3 +450,18 @@ export function unwrapTSNode(node: Node): Node {
449450
return node
450451
}
451452
}
453+
454+
export function isCallOf(
455+
node: Node | null | undefined,
456+
test: string | ((id: string) => boolean) | null | undefined
457+
): node is CallExpression {
458+
return !!(
459+
node &&
460+
test &&
461+
node.type === 'CallExpression' &&
462+
node.callee.type === 'Identifier' &&
463+
(typeof test === 'string'
464+
? node.callee.name === test
465+
: test(node.callee.name))
466+
)
467+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`sfc props transform > aliasing 1`] = `
4+
"import { toDisplayString as _toDisplayString } from \\"vue\\"
5+
6+
7+
export default {
8+
props: ['foo'],
9+
setup(__props) {
10+
11+
12+
let x = foo
13+
let y = __props.foo
14+
15+
return (_ctx, _cache) => {
16+
return _toDisplayString(__props.foo + __props.foo)
17+
}
18+
}
19+
20+
}"
21+
`;
22+
23+
exports[`sfc props transform > basic usage 1`] = `
24+
"import { toDisplayString as _toDisplayString } from \\"vue\\"
25+
26+
27+
export default {
28+
props: ['foo'],
29+
setup(__props) {
30+
31+
32+
console.log(__props.foo)
33+
34+
return (_ctx, _cache) => {
35+
return _toDisplayString(__props.foo)
36+
}
37+
}
38+
39+
}"
40+
`;
41+
42+
exports[`sfc props transform > computed static key 1`] = `
43+
"import { toDisplayString as _toDisplayString } from \\"vue\\"
44+
45+
46+
export default {
47+
props: ['foo'],
48+
setup(__props) {
49+
50+
51+
console.log(__props.foo)
52+
53+
return (_ctx, _cache) => {
54+
return _toDisplayString(__props.foo)
55+
}
56+
}
57+
58+
}"
59+
`;
60+
61+
exports[`sfc props transform > default values w/ array runtime declaration 1`] = `
62+
"import { mergeDefaults as _mergeDefaults } from 'vue'
63+
64+
export default {
65+
props: _mergeDefaults(['foo', 'bar', 'baz'], {
66+
foo: 1,
67+
bar: () => ({}),
68+
func: () => {}, __skip_func: true
69+
}),
70+
setup(__props) {
71+
72+
73+
74+
return () => {}
75+
}
76+
77+
}"
78+
`;
79+
80+
exports[`sfc props transform > default values w/ object runtime declaration 1`] = `
81+
"import { mergeDefaults as _mergeDefaults } from 'vue'
82+
83+
export default {
84+
props: _mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, {
85+
foo: 1,
86+
bar: () => ({}),
87+
func: () => {}, __skip_func: true,
88+
ext: x, __skip_ext: true
89+
}),
90+
setup(__props) {
91+
92+
93+
94+
return () => {}
95+
}
96+
97+
}"
98+
`;
99+
100+
exports[`sfc props transform > default values w/ type declaration 1`] = `
101+
"import { defineComponent as _defineComponent } from 'vue'
102+
103+
export default /*#__PURE__*/_defineComponent({
104+
props: {
105+
foo: { type: Number, required: false, default: 1 },
106+
bar: { type: Object, required: false, default: () => ({}) },
107+
func: { type: Function, required: false, default: () => {} }
108+
},
109+
setup(__props: any) {
110+
111+
112+
113+
return () => {}
114+
}
115+
116+
})"
117+
`;
118+
119+
exports[`sfc props transform > default values w/ type declaration, prod mode 1`] = `
120+
"import { defineComponent as _defineComponent } from 'vue'
121+
122+
export default /*#__PURE__*/_defineComponent({
123+
props: {
124+
foo: { default: 1 },
125+
bar: { default: () => ({}) },
126+
baz: null,
127+
boola: { type: Boolean },
128+
boolb: { type: [Boolean, Number] },
129+
func: { type: Function, default: () => {} }
130+
},
131+
setup(__props: any) {
132+
133+
134+
135+
return () => {}
136+
}
137+
138+
})"
139+
`;
140+
141+
exports[`sfc props transform > multiple variable declarations 1`] = `
142+
"import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
143+
144+
145+
export default {
146+
props: ['foo'],
147+
setup(__props) {
148+
149+
const bar = 'fish', hello = 'world'
150+
151+
return (_ctx, _cache) => {
152+
return (_openBlock(), _createElementBlock(\\"div\\", null, _toDisplayString(__props.foo) + \\" \\" + _toDisplayString(hello) + \\" \\" + _toDisplayString(bar), 1 /* TEXT */))
153+
}
154+
}
155+
156+
}"
157+
`;
158+
159+
exports[`sfc props transform > nested scope 1`] = `
160+
"export default {
161+
props: ['foo', 'bar'],
162+
setup(__props) {
163+
164+
165+
function test(foo) {
166+
console.log(foo)
167+
console.log(__props.bar)
168+
}
169+
170+
return () => {}
171+
}
172+
173+
}"
174+
`;
175+
176+
exports[`sfc props transform > non-identifier prop names 1`] = `
177+
"import { toDisplayString as _toDisplayString } from \\"vue\\"
178+
179+
180+
export default {
181+
props: { 'foo.bar': Function },
182+
setup(__props) {
183+
184+
185+
let x = __props[\\"foo.bar\\"]
186+
187+
return (_ctx, _cache) => {
188+
return _toDisplayString(__props[\\"foo.bar\\"])
189+
}
190+
}
191+
192+
}"
193+
`;
194+
195+
exports[`sfc props transform > rest spread 1`] = `
196+
"import { createPropsRestProxy as _createPropsRestProxy } from 'vue'
197+
198+
export default {
199+
props: ['foo', 'bar', 'baz'],
200+
setup(__props) {
201+
202+
const rest = _createPropsRestProxy(__props, [\\"foo\\",\\"bar\\"]);
203+
204+
205+
206+
return () => {}
207+
}
208+
209+
}"
210+
`;

packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts renamed to packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts

+44-20
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ describe('sfc props transform', () => {
66
function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
77
return compileSFCScript(src, {
88
inlineTemplate: true,
9-
reactivityTransform: true,
109
...options
1110
})
1211
}
@@ -211,23 +210,6 @@ describe('sfc props transform', () => {
211210
})
212211
})
213212

214-
test('$$() escape', () => {
215-
const { content } = compile(`
216-
<script setup>
217-
const { foo, bar: baz } = defineProps(['foo'])
218-
console.log($$(foo))
219-
console.log($$(baz))
220-
$$({ foo, baz })
221-
</script>
222-
`)
223-
expect(content).toMatch(`const __props_foo = _toRef(__props, 'foo')`)
224-
expect(content).toMatch(`const __props_bar = _toRef(__props, 'bar')`)
225-
expect(content).toMatch(`console.log((__props_foo))`)
226-
expect(content).toMatch(`console.log((__props_bar))`)
227-
expect(content).toMatch(`({ foo: __props_foo, baz: __props_bar })`)
228-
assertCode(content)
229-
})
230-
231213
// #6960
232214
test('computed static key', () => {
233215
const { content, bindings } = compile(`
@@ -292,15 +274,57 @@ describe('sfc props transform', () => {
292274
).toThrow(`cannot reference locally declared variables`)
293275
})
294276

295-
test('should error if assignment to constant variable', () => {
277+
test('should error if assignment to destructured prop binding', () => {
296278
expect(() =>
297279
compile(
298280
`<script setup>
299281
const { foo } = defineProps(['foo'])
300282
foo = 'bar'
301283
</script>`
302284
)
303-
).toThrow(`Assignment to constant variable.`)
285+
).toThrow(`Cannot assign to destructured props`)
286+
287+
expect(() =>
288+
compile(
289+
`<script setup>
290+
let { foo } = defineProps(['foo'])
291+
foo = 'bar'
292+
</script>`
293+
)
294+
).toThrow(`Cannot assign to destructured props`)
295+
})
296+
297+
test('should error when watching destructured prop', () => {
298+
expect(() =>
299+
compile(
300+
`<script setup>
301+
import { watch } from 'vue'
302+
const { foo } = defineProps(['foo'])
303+
watch(foo, () => {})
304+
</script>`
305+
)
306+
).toThrow(`"foo" is a destructured prop and cannot be directly watched.`)
307+
308+
expect(() =>
309+
compile(
310+
`<script setup>
311+
import { watch as w } from 'vue'
312+
const { foo } = defineProps(['foo'])
313+
w(foo, () => {})
314+
</script>`
315+
)
316+
).toThrow(`"foo" is a destructured prop and cannot be directly watched.`)
317+
})
318+
319+
// not comprehensive, but should help for most common cases
320+
test('should error if default value type does not match declared type', () => {
321+
expect(() =>
322+
compile(
323+
`<script setup lang="ts">
324+
const { foo = 'hello' } = defineProps<{ foo?: number }>()
325+
</script>`
326+
)
327+
).toThrow(`Default value of prop "foo" does not match declared type.`)
304328
})
305329
})
306330
})

0 commit comments

Comments
 (0)