]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(compiler-vapor): cache inline handlers passed to component (#12563)
authoredison <daiwei521@126.com>
Wed, 8 Jan 2025 06:35:09 +0000 (14:35 +0800)
committerGitHub <noreply@github.com>
Wed, 8 Jan 2025 06:35:09 +0000 (14:35 +0800)
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
packages/compiler-vapor/src/generate.ts
packages/compiler-vapor/src/generators/component.ts
packages/compiler-vapor/src/generators/event.ts

index 1c37dad0f0de1d49b1102eb9ff14efe0f5c0ae63..adb34615cdff0ea0cece6dd5d71404e08a42196d 100644 (file)
@@ -1,5 +1,19 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
+exports[`compiler: element transform > component > cache v-on expression with unique handler name 1`] = `
+"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
+
+export function render(_ctx) {
+  const _component_Foo = _resolveComponent("Foo")
+  const _component_Bar = _resolveComponent("Bar")
+  const _on_bar = $event => (_ctx.handleBar($event))
+  const n0 = _createComponentWithFallback(_component_Foo, { onBar: () => _on_bar })
+  const _on_bar1 = () => _ctx.handler
+  const n1 = _createComponentWithFallback(_component_Bar, { onBar: () => _on_bar1 })
+  return [n0, n1]
+}"
+`;
+
 exports[`compiler: element transform > component > do not resolve component from non-script-setup bindings 1`] = `
 "import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
 
@@ -95,16 +109,6 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
 }"
 `;
 
-exports[`compiler: element transform > component > should wrap as function if v-on expression is inline statement 1`] = `
-"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
-
-export function render(_ctx) {
-  const _component_Foo = _resolveComponent("Foo")
-  const n0 = _createComponentWithFallback(_component_Foo, { onBar: () => $event => (_ctx.handleBar($event)) }, null, true)
-  return n0
-}"
-`;
-
 exports[`compiler: element transform > component > static props 1`] = `
 "import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
 
@@ -174,6 +178,28 @@ export function render(_ctx) {
 }"
 `;
 
+exports[`compiler: element transform > component > v-on expression is a function call 1`] = `
+"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
+
+export function render(_ctx) {
+  const _component_Foo = _resolveComponent("Foo")
+  const _on_bar = $event => (_ctx.handleBar($event))
+  const n0 = _createComponentWithFallback(_component_Foo, { onBar: () => _on_bar }, null, true)
+  return n0
+}"
+`;
+
+exports[`compiler: element transform > component > v-on expression is inline statement 1`] = `
+"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
+
+export function render(_ctx) {
+  const _component_Foo = _resolveComponent("Foo")
+  const _on_bar = () => _ctx.handler
+  const n0 = _createComponentWithFallback(_component_Foo, { onBar: () => _on_bar }, null, true)
+  return n0
+}"
+`;
+
 exports[`compiler: element transform > component > v-on="obj" 1`] = `
 "import { resolveComponent as _resolveComponent, toHandlers as _toHandlers, createComponentWithFallback as _createComponentWithFallback } from 'vue';
 
index 030f32eea2b6e4bc0761f8dae82c1d019286d4bf..b26f7c776f3a24e35526482041bc4da148545385 100644 (file)
@@ -383,12 +383,39 @@ describe('compiler: element transform', () => {
       ])
     })
 
-    test('should wrap as function if v-on expression is inline statement', () => {
+    test('v-on expression is inline statement', () => {
+      const { code, ir } = compileWithElementTransform(
+        `<Foo v-on:bar="() => handler" />`,
+      )
+      expect(code).toMatchSnapshot()
+      expect(code).contains(`onBar: () => _on_bar`)
+      expect(code).contains(`const _on_bar = () => _ctx.handler`)
+      expect(ir.block.operation).toMatchObject([
+        {
+          type: IRNodeTypes.CREATE_COMPONENT_NODE,
+          tag: 'Foo',
+          props: [
+            [
+              {
+                key: { content: 'bar' },
+                handler: true,
+                values: [{ content: '_on_bar' }],
+              },
+            ],
+          ],
+        },
+      ])
+    })
+
+    test('v-on expression is a function call', () => {
       const { code, ir } = compileWithElementTransform(
         `<Foo v-on:bar="handleBar($event)" />`,
       )
       expect(code).toMatchSnapshot()
-      expect(code).contains(`onBar: () => $event => (_ctx.handleBar($event))`)
+      expect(code).contains(`onBar: () => _on_bar`)
+      expect(code).contains(
+        `const _on_bar = $event => (_ctx.handleBar($event))`,
+      )
       expect(ir.block.operation).toMatchObject([
         {
           type: IRNodeTypes.CREATE_COMPONENT_NODE,
@@ -398,7 +425,48 @@ describe('compiler: element transform', () => {
               {
                 key: { content: 'bar' },
                 handler: true,
-                values: [{ content: 'handleBar($event)' }],
+                values: [{ content: '_on_bar' }],
+              },
+            ],
+          ],
+        },
+      ])
+    })
+
+    test('cache v-on expression with unique handler name', () => {
+      const { code, ir } = compileWithElementTransform(
+        `<Foo v-on:bar="handleBar($event)" /><Bar v-on:bar="() => handler" />`,
+      )
+      expect(code).toMatchSnapshot()
+      expect(code).contains(`onBar: () => _on_bar`)
+      expect(code).contains(
+        `const _on_bar = $event => (_ctx.handleBar($event))`,
+      )
+      expect(code).contains(`onBar: () => _on_bar1`)
+      expect(code).contains(`const _on_bar1 = () => _ctx.handler`)
+      expect(ir.block.operation).toMatchObject([
+        {
+          type: IRNodeTypes.CREATE_COMPONENT_NODE,
+          tag: 'Foo',
+          props: [
+            [
+              {
+                key: { content: 'bar' },
+                handler: true,
+                values: [{ content: '_on_bar' }],
+              },
+            ],
+          ],
+        },
+        {
+          type: IRNodeTypes.CREATE_COMPONENT_NODE,
+          tag: 'Bar',
+          props: [
+            [
+              {
+                key: { content: 'bar' },
+                handler: true,
+                values: [{ content: '_on_bar1' }],
               },
             ],
           ],
index 46390afadbb3a4b018acd263f858b4116d95e54a..012ea55d5866f3511fcb1c8c65531757d426f750 100644 (file)
@@ -34,6 +34,8 @@ export class CodegenContext {
 
   identifiers: Record<string, string[]> = Object.create(null)
 
+  seenInlineHandlerNames: Record<string, number> = Object.create(null)
+
   block: BlockIRNode
   withId<T>(fn: () => T, map: Record<string, string | null>): T {
     const { identifiers } = this
index dcbedb4724c00cc7943bcd1d025b388d144ea47e..997414001c882a2791bd5d3fafd19ca81eee8902 100644 (file)
@@ -29,7 +29,9 @@ import {
 import { genExpression } from './expression'
 import { genPropKey, genPropValue } from './prop'
 import {
+  type SimpleExpressionNode,
   createSimpleExpression,
+  isMemberExpression,
   toValidAssetId,
   walkIdentifiers,
 } from '@vue/compiler-core'
@@ -46,11 +48,20 @@ export function genCreateComponent(
 
   const tag = genTag()
   const { root, props, slots, once } = operation
-  const rawProps = genRawProps(props, context)
   const rawSlots = genRawSlots(slots, context)
+  const [ids, handlers] = processInlineHandlers(props, context)
+  const rawProps = context.withId(() => genRawProps(props, context), ids)
+  const inlineHandlers: CodeFragment[] = handlers.reduce<CodeFragment[]>(
+    (acc, { name, value }) => {
+      const handler = genEventHandler(context, value, undefined, false)
+      return [...acc, `const ${name} = `, ...handler, NEWLINE]
+    },
+    [],
+  )
 
   return [
     NEWLINE,
+    ...inlineHandlers,
     `const n${operation.id} = `,
     ...genCall(
       operation.asset
@@ -82,6 +93,45 @@ export function genCreateComponent(
   }
 }
 
+function getUniqueHandlerName(context: CodegenContext, name: string): string {
+  const { seenInlineHandlerNames } = context
+  const count = seenInlineHandlerNames[name] || 0
+  seenInlineHandlerNames[name] = count + 1
+  return count === 0 ? name : `${name}${count}`
+}
+
+type InlineHandler = {
+  name: string
+  value: SimpleExpressionNode
+}
+
+function processInlineHandlers(
+  props: IRProps[],
+  context: CodegenContext,
+): [Record<string, null>, InlineHandler[]] {
+  const ids: Record<string, null> = Object.create(null)
+  const handlers: InlineHandler[] = []
+  const staticProps = props[0]
+  if (isArray(staticProps)) {
+    for (let i = 0; i < staticProps.length; i++) {
+      const prop = staticProps[i]
+      if (!prop.handler) continue
+      prop.values.forEach((value, i) => {
+        const isMemberExp = isMemberExpression(value, context.options)
+        // cache inline handlers (fn expression or inline statement)
+        if (!isMemberExp) {
+          const name = getUniqueHandlerName(context, `_on_${prop.key.content}`)
+          handlers.push({ name, value })
+          ids[name] = null
+          // replace the original prop value with the handler name
+          prop.values[i] = extend({ ast: null }, createSimpleExpression(name))
+        }
+      })
+    }
+  }
+  return [ids, handlers]
+}
+
 export function genRawProps(
   props: IRProps[],
   context: CodegenContext,
index 8698c722c7d3fbbb17a1cfb721ad4f4eed147923..c0b7e1095ce8dce4204fb07e2f104c7d945128ec 100644 (file)
@@ -88,6 +88,7 @@ export function genEventHandler(
     nonKeys: string[]
     keys: string[]
   } = { nonKeys: [], keys: [] },
+  needWrap: boolean = true,
 ): CodeFragment[] {
   let handlerExp: CodeFragment[] = [`() => {}`]
   if (value && value.content.trim()) {
@@ -117,7 +118,8 @@ export function genEventHandler(
     handlerExp = genWithModifiers(context, handlerExp, nonKeys)
   if (keys.length) handlerExp = genWithKeys(context, handlerExp, keys)
 
-  return [`() => `, ...handlerExp]
+  if (needWrap) handlerExp.unshift(`() => `)
+  return handlerExp
 }
 
 function genWithModifiers(