feat(compiler-vapor): complex identifier rewriting with vOn (#113)

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
Rizumu Ayaka 2024-02-05 22:37:09 +08:00 committed by GitHub
parent e9e7fe414b
commit 75b0937d31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 98 additions and 17 deletions

View File

@ -353,6 +353,18 @@ export function render(_ctx) {
}"
`;
exports[`v-on > should wrap in unref if identifier is setup-maybe-ref w/ inline: true 1`] = `
"(() => {
const t0 = _template("<div></div><div></div><div></div>")
const n0 = t0()
const { 0: [n1], 1: [n2], 2: [n3],} = _children(n0)
_on(n1, "click", $event => (x.value=_unref(y)))
_on(n2, "click", $event => (x.value++))
_on(n3, "click", $event => ({ x: x.value } = _unref(y)))
return n0
})()"
`;
exports[`v-on > should wrap keys guard for keyboard events or dynamic events 1`] = `
"import { template as _template, children as _children, withModifiers as _withModifiers, withKeys as _withKeys, on as _on } from 'vue/vapor';

View File

@ -170,6 +170,29 @@ describe('v-on', () => {
expect(code).contains('_on(n1, "click", $event => (_ctx.i++))')
})
test('should wrap in unref if identifier is setup-maybe-ref w/ inline: true', () => {
const { code, helpers, vaporHelpers } = compileWithVOn(
`<div @click="x=y"/><div @click="x++"/><div @click="{ x } = y"/>`,
{
mode: 'module',
inline: true,
bindingMetadata: {
x: BindingTypes.SETUP_MAYBE_REF,
y: BindingTypes.SETUP_MAYBE_REF,
},
},
)
expect(code).matchSnapshot()
expect(vaporHelpers).contains('unref')
expect(helpers.size).toBe(0)
expect(code).contains('_on(n1, "click", $event => (x.value=_unref(y)))')
expect(code).contains('_on(n2, "click", $event => (x.value++))')
expect(code).contains(
'_on(n3, "click", $event => ({ x: x.value } = _unref(y)))',
)
})
test('should handle multiple inline statement', () => {
const { ir, code } = compileWithVOn(`<div @click="foo();bar()"/>`)

View File

@ -3,6 +3,8 @@ import {
NewlineType,
type SourceLocation,
advancePositionWithClone,
isInDestructureAssignment,
isStaticProperty,
walkIdentifiers,
} from '@vue/compiler-dom'
import { isGloballyAllowed, isString, makeMap } from '@vue/shared'
@ -13,6 +15,7 @@ import {
type CodegenContext,
buildCodeFragment,
} from '../generate'
import type { Node } from '@babel/types'
export function genExpression(
node: IRExpression,
@ -42,11 +45,21 @@ export function genExpression(
// the expression is a simple identifier
if (ast === null) {
return [genIdentifier(rawExpr, context, loc)]
return genIdentifier(rawExpr, context, loc)
}
const ids: Identifier[] = []
walkIdentifiers(ast!, id => ids.push(id))
const parentStackMap = new WeakMap<Identifier, Node[]>()
const parentStack: Node[] = []
walkIdentifiers(
ast!,
id => {
ids.push(id)
parentStackMap.set(id, parentStack.slice())
},
false,
parentStack,
)
if (ids.length) {
ids.sort((a, b) => a.start! - b.start!)
const [frag, push] = buildCodeFragment()
@ -60,12 +73,20 @@ export function genExpression(
if (leadingText.length) push([leadingText, NewlineType.Unknown])
const source = rawExpr.slice(start, end)
const parentStack = parentStackMap.get(id)!
push(
genIdentifier(source, context, {
start: advancePositionWithClone(node.loc.start, source, start),
end: advancePositionWithClone(node.loc.start, source, end),
...genIdentifier(
source,
}),
context,
{
start: advancePositionWithClone(node.loc.start, source, start),
end: advancePositionWithClone(node.loc.start, source, end),
source,
},
id,
parentStack[parentStack.length - 1],
parentStack,
),
)
if (i === ids.length - 1 && end < rawExpr.length) {
@ -81,30 +102,55 @@ export function genExpression(
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
function genIdentifier(
id: string,
raw: string,
{ options, vaporHelper, identifiers }: CodegenContext,
loc?: SourceLocation,
): CodeFragment {
id?: Identifier,
parent?: Node,
parentStack?: Node[],
): CodeFragment[] {
const { inline, bindingMetadata } = options
let name: string | undefined = id
let name: string | undefined = raw
const idMap = identifiers[id]
const idMap = identifiers[raw]
if (idMap && idMap.length) {
return [idMap[0], NewlineType.None, loc]
return [[idMap[0], NewlineType.None, loc]]
}
let prefix: string | undefined
if (isStaticProperty(parent!) && parent.shorthand) {
// property shorthand like { foo }, we need to add the key since
// we rewrite the value
prefix = `${raw}: `
}
if (inline) {
switch (bindingMetadata[id]) {
switch (bindingMetadata[raw]) {
case BindingTypes.SETUP_REF:
name = id += '.value'
name = raw = `${raw}.value`
break
case BindingTypes.SETUP_MAYBE_REF:
id = `${vaporHelper('unref')}(${id})`
name = undefined
// ({ x } = y)
const isDestructureAssignment =
parent && isInDestructureAssignment(parent, parentStack || [])
// x = y
const isAssignmentLVal =
parent && parent.type === 'AssignmentExpression' && parent.left === id
// x++
const isUpdateArg =
parent && parent.type === 'UpdateExpression' && parent.argument === id
// const binding that may or may not be ref
// if it's not a ref, then assignments don't make sense -
// so we ignore the non-ref assignment case and generate code
// that assumes the value to be a ref for more efficiency
raw =
isAssignmentLVal || isUpdateArg || isDestructureAssignment
? (name = `${raw}.value`)
: `${vaporHelper('unref')}(${raw})`
break
}
} else {
id = `_ctx.${id}`
raw = `_ctx.${raw}`
}
return [id, NewlineType.None, loc, name]
return [prefix, [raw, NewlineType.None, loc, name]]
}