Skip to content

Commit 198ca14

Browse files
committed
feat(reactivity-transform): $$() escape for destructured prop bindings
1 parent 179fc05 commit 198ca14

File tree

4 files changed

+96
-15
lines changed

4 files changed

+96
-15
lines changed

packages/compiler-sfc/__tests__/__snapshots__/compileScriptPropsTransform.spec.ts.snap

+20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`sfc props transform $$() escape 1`] = `
4+
"import { toRef as _toRef } from 'vue'
5+
6+
export default {
7+
props: ['foo'],
8+
setup(__props) {
9+
const __props_bar = _toRef(__props, 'bar')
10+
const __props_foo = _toRef(__props, 'foo')
11+
12+
13+
console.log((__props_foo))
14+
console.log((__props_bar))
15+
({ foo: __props_foo, baz: __props_bar })
16+
17+
return () => {}
18+
}
19+
20+
}"
21+
`;
22+
323
exports[`sfc props transform aliasing 1`] = `
424
"import { toDisplayString as _toDisplayString } from \\"vue\\"
525

packages/compiler-sfc/__tests__/compileScript.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe('SFC compile <script setup>', () => {
3636
assertCode(content)
3737
})
3838

39-
test('binding analysis for destructur', () => {
39+
test('binding analysis for destructure', () => {
4040
const { content, bindings } = compile(`
4141
<script setup>
4242
const { foo, b: bar, ['x' + 'y']: baz, x: { y, zz: { z }}} = {}

packages/compiler-sfc/__tests__/compileScriptPropsTransform.spec.ts

+17
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,23 @@ describe('sfc props transform', () => {
145145
})
146146
})
147147

148+
test('$$() escape', () => {
149+
const { content } = compile(`
150+
<script setup>
151+
const { foo, bar: baz } = defineProps(['foo'])
152+
console.log($$(foo))
153+
console.log($$(baz))
154+
$$({ foo, baz })
155+
</script>
156+
`)
157+
expect(content).toMatch(`const __props_foo = _toRef(__props, 'foo')`)
158+
expect(content).toMatch(`const __props_bar = _toRef(__props, 'bar')`)
159+
expect(content).toMatch(`console.log((__props_foo))`)
160+
expect(content).toMatch(`console.log((__props_bar))`)
161+
expect(content).toMatch(`({ foo: __props_foo, baz: __props_bar })`)
162+
assertCode(content)
163+
})
164+
148165
describe('errors', () => {
149166
test('should error on deep destructure', () => {
150167
expect(() =>

packages/ref-transform/src/refTransform.ts

+58-14
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { parse, ParserPlugin } from '@babel/parser'
2323
import { hasOwn, isArray, isString } from '@vue/shared'
2424

2525
const TO_VAR_SYMBOL = '$'
26-
const TO_REF_SYMBOL = '$$'
26+
const ESCAPE_SYMBOL = '$$'
2727
const shorthands = ['ref', 'computed', 'shallowRef', 'toRef', 'customRef']
2828
const transformCheckRE = /[^\w]\$(?:\$|ref|computed|shallowRef)?\s*(\(|\<)/
2929

@@ -420,30 +420,52 @@ export function transformAST(
420420
const isProp = bindingType === 'prop'
421421
if (isStaticProperty(parent) && parent.shorthand) {
422422
// let binding used in a property shorthand
423-
// { foo } -> { foo: foo.value }
424-
// { prop } -> { prop: __prop.prop }
425423
// skip for destructure patterns
426424
if (
427425
!(parent as any).inPattern ||
428426
isInDestructureAssignment(parent, parentStack)
429427
) {
430428
if (isProp) {
431-
s.appendLeft(
432-
id.end! + offset,
433-
`: __props.${propsLocalToPublicMap[id.name]}`
434-
)
429+
if (escapeScope) {
430+
// prop binding in $$()
431+
// { prop } -> { prop: __prop_prop }
432+
registerEscapedPropBinding(id)
433+
s.appendLeft(
434+
id.end! + offset,
435+
`: __props_${propsLocalToPublicMap[id.name]}`
436+
)
437+
} else {
438+
// { prop } -> { prop: __prop.prop }
439+
s.appendLeft(
440+
id.end! + offset,
441+
`: __props.${propsLocalToPublicMap[id.name]}`
442+
)
443+
}
435444
} else {
445+
// { foo } -> { foo: foo.value }
436446
s.appendLeft(id.end! + offset, `: ${id.name}.value`)
437447
}
438448
}
439449
} else {
440450
if (isProp) {
441-
s.overwrite(
442-
id.start! + offset,
443-
id.end! + offset,
444-
`__props.${propsLocalToPublicMap[id.name]}`
445-
)
451+
if (escapeScope) {
452+
// x --> __props_x
453+
registerEscapedPropBinding(id)
454+
s.overwrite(
455+
id.start! + offset,
456+
id.end! + offset,
457+
`__props_${propsLocalToPublicMap[id.name]}`
458+
)
459+
} else {
460+
// x --> __props.x
461+
s.overwrite(
462+
id.start! + offset,
463+
id.end! + offset,
464+
`__props.${propsLocalToPublicMap[id.name]}`
465+
)
466+
}
446467
} else {
468+
// x --> x.value
447469
s.appendLeft(id.end! + offset, '.value')
448470
}
449471
}
@@ -453,8 +475,25 @@ export function transformAST(
453475
return false
454476
}
455477

478+
const propBindingRefs: Record<string, true> = {}
479+
function registerEscapedPropBinding(id: Identifier) {
480+
if (!propBindingRefs.hasOwnProperty(id.name)) {
481+
propBindingRefs[id.name] = true
482+
const publicKey = propsLocalToPublicMap[id.name]
483+
s.prependRight(
484+
offset,
485+
`const __props_${publicKey} = ${helper(
486+
`toRef`
487+
)}(__props, '${publicKey}')\n`
488+
)
489+
}
490+
}
491+
456492
// check root scope first
457493
walkScope(ast, true)
494+
495+
// inside $$()
496+
let escapeScope: CallExpression | undefined
458497
;(walk as any)(ast, {
459498
enter(node: Node, parent?: Node) {
460499
parent && parentStack.push(parent)
@@ -488,6 +527,8 @@ export function transformAST(
488527

489528
if (
490529
node.type === 'Identifier' &&
530+
// if inside $$(), skip unless this is a destructured prop binding
531+
!(escapeScope && rootScope[node.name] !== 'prop') &&
491532
isReferencedIdentifier(node, parent!, parentStack) &&
492533
!excludedIds.has(node)
493534
) {
@@ -512,9 +553,9 @@ export function transformAST(
512553
)
513554
}
514555

515-
if (callee === TO_REF_SYMBOL) {
556+
if (callee === ESCAPE_SYMBOL) {
516557
s.remove(node.callee.start! + offset, node.callee.end! + offset)
517-
return this.skip()
558+
escapeScope = node
518559
}
519560

520561
// TODO remove when out of experimental
@@ -543,6 +584,9 @@ export function transformAST(
543584
scopeStack.pop()
544585
currentScope = scopeStack[scopeStack.length - 1] || null
545586
}
587+
if (node === escapeScope) {
588+
escapeScope = undefined
589+
}
546590
}
547591
})
548592

0 commit comments

Comments
 (0)