feat(reactivity-transform): $$() escape for destructured prop bindings

This commit is contained in:
Evan You 2021-12-11 17:50:09 +08:00
parent 179fc05a84
commit 198ca14f19
4 changed files with 96 additions and 15 deletions

View File

@ -1,5 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`sfc props transform $$() escape 1`] = `
"import { toRef as _toRef } from 'vue'
export default {
props: ['foo'],
setup(__props) {
const __props_bar = _toRef(__props, 'bar')
const __props_foo = _toRef(__props, 'foo')
console.log((__props_foo))
console.log((__props_bar))
({ foo: __props_foo, baz: __props_bar })
return () => {}
}
}"
`;
exports[`sfc props transform aliasing 1`] = ` exports[`sfc props transform aliasing 1`] = `
"import { toDisplayString as _toDisplayString } from \\"vue\\" "import { toDisplayString as _toDisplayString } from \\"vue\\"

View File

@ -36,7 +36,7 @@ describe('SFC compile <script setup>', () => {
assertCode(content) assertCode(content)
}) })
test('binding analysis for destructur', () => { test('binding analysis for destructure', () => {
const { content, bindings } = compile(` const { content, bindings } = compile(`
<script setup> <script setup>
const { foo, b: bar, ['x' + 'y']: baz, x: { y, zz: { z }}} = {} const { foo, b: bar, ['x' + 'y']: baz, x: { y, zz: { z }}} = {}

View File

@ -145,6 +145,23 @@ 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)
})
describe('errors', () => { describe('errors', () => {
test('should error on deep destructure', () => { test('should error on deep destructure', () => {
expect(() => expect(() =>

View File

@ -23,7 +23,7 @@ import { parse, ParserPlugin } from '@babel/parser'
import { hasOwn, isArray, isString } from '@vue/shared' import { hasOwn, isArray, isString } from '@vue/shared'
const TO_VAR_SYMBOL = '$' const TO_VAR_SYMBOL = '$'
const TO_REF_SYMBOL = '$$' const ESCAPE_SYMBOL = '$$'
const shorthands = ['ref', 'computed', 'shallowRef', 'toRef', 'customRef'] const shorthands = ['ref', 'computed', 'shallowRef', 'toRef', 'customRef']
const transformCheckRE = /[^\w]\$(?:\$|ref|computed|shallowRef)?\s*(\(|\<)/ const transformCheckRE = /[^\w]\$(?:\$|ref|computed|shallowRef)?\s*(\(|\<)/
@ -420,30 +420,52 @@ export function transformAST(
const isProp = bindingType === 'prop' const isProp = bindingType === 'prop'
if (isStaticProperty(parent) && parent.shorthand) { if (isStaticProperty(parent) && parent.shorthand) {
// let binding used in a property shorthand // let binding used in a property shorthand
// { foo } -> { foo: foo.value }
// { prop } -> { prop: __prop.prop }
// skip for destructure patterns // skip for destructure patterns
if ( if (
!(parent as any).inPattern || !(parent as any).inPattern ||
isInDestructureAssignment(parent, parentStack) isInDestructureAssignment(parent, parentStack)
) { ) {
if (isProp) { if (isProp) {
if (escapeScope) {
// prop binding in $$()
// { prop } -> { prop: __prop_prop }
registerEscapedPropBinding(id)
s.appendLeft(
id.end! + offset,
`: __props_${propsLocalToPublicMap[id.name]}`
)
} else {
// { prop } -> { prop: __prop.prop }
s.appendLeft( s.appendLeft(
id.end! + offset, id.end! + offset,
`: __props.${propsLocalToPublicMap[id.name]}` `: __props.${propsLocalToPublicMap[id.name]}`
) )
}
} else { } else {
// { foo } -> { foo: foo.value }
s.appendLeft(id.end! + offset, `: ${id.name}.value`) s.appendLeft(id.end! + offset, `: ${id.name}.value`)
} }
} }
} else { } else {
if (isProp) { if (isProp) {
if (escapeScope) {
// x --> __props_x
registerEscapedPropBinding(id)
s.overwrite(
id.start! + offset,
id.end! + offset,
`__props_${propsLocalToPublicMap[id.name]}`
)
} else {
// x --> __props.x
s.overwrite( s.overwrite(
id.start! + offset, id.start! + offset,
id.end! + offset, id.end! + offset,
`__props.${propsLocalToPublicMap[id.name]}` `__props.${propsLocalToPublicMap[id.name]}`
) )
}
} else { } else {
// x --> x.value
s.appendLeft(id.end! + offset, '.value') s.appendLeft(id.end! + offset, '.value')
} }
} }
@ -453,8 +475,25 @@ export function transformAST(
return false return false
} }
const propBindingRefs: Record<string, true> = {}
function registerEscapedPropBinding(id: Identifier) {
if (!propBindingRefs.hasOwnProperty(id.name)) {
propBindingRefs[id.name] = true
const publicKey = propsLocalToPublicMap[id.name]
s.prependRight(
offset,
`const __props_${publicKey} = ${helper(
`toRef`
)}(__props, '${publicKey}')\n`
)
}
}
// check root scope first // check root scope first
walkScope(ast, true) walkScope(ast, true)
// inside $$()
let escapeScope: CallExpression | undefined
;(walk as any)(ast, { ;(walk as any)(ast, {
enter(node: Node, parent?: Node) { enter(node: Node, parent?: Node) {
parent && parentStack.push(parent) parent && parentStack.push(parent)
@ -488,6 +527,8 @@ export function transformAST(
if ( if (
node.type === 'Identifier' && node.type === 'Identifier' &&
// if inside $$(), skip unless this is a destructured prop binding
!(escapeScope && rootScope[node.name] !== 'prop') &&
isReferencedIdentifier(node, parent!, parentStack) && isReferencedIdentifier(node, parent!, parentStack) &&
!excludedIds.has(node) !excludedIds.has(node)
) { ) {
@ -512,9 +553,9 @@ export function transformAST(
) )
} }
if (callee === TO_REF_SYMBOL) { if (callee === ESCAPE_SYMBOL) {
s.remove(node.callee.start! + offset, node.callee.end! + offset) s.remove(node.callee.start! + offset, node.callee.end! + offset)
return this.skip() escapeScope = node
} }
// TODO remove when out of experimental // TODO remove when out of experimental
@ -543,6 +584,9 @@ export function transformAST(
scopeStack.pop() scopeStack.pop()
currentScope = scopeStack[scopeStack.length - 1] || null currentScope = scopeStack[scopeStack.length - 1] || null
} }
if (node === escapeScope) {
escapeScope = undefined
}
} }
}) })