Преглед изворни кода

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

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
Rizumu Ayaka пре 2 година
родитељ
комит
75b0937d31

+ 12 - 0
packages/compiler-vapor/__tests__/transforms/__snapshots__/vOn.spec.ts.snap

@@ -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';
 

+ 23 - 0
packages/compiler-vapor/__tests__/transforms/vOn.spec.ts

@@ -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()"/>`)
 

+ 63 - 17
packages/compiler-vapor/src/generators/expression.ts

@@ -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]]
 }