feat(reactivity-transform): $$() escape for destructured prop bindings
This commit is contained in:
parent
179fc05a84
commit
198ca14f19
@ -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\\"
|
||||||
|
|
||||||
|
@ -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 }}} = {}
|
||||||
|
@ -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(() =>
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user