From: edison Date: Wed, 8 Jan 2025 06:35:09 +0000 (+0800) Subject: refactor(compiler-vapor): cache inline handlers passed to component (#12563) X-Git-Tag: v3.6.0-alpha.1~16^2~141 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=757b3df56ea3b39769deac8dbfc30c3eadbe6e68;p=thirdparty%2Fvuejs%2Fcore.git refactor(compiler-vapor): cache inline handlers passed to component (#12563) --- diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap index 1c37dad0f0..adb34615cd 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap @@ -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'; diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts index 030f32eea2..b26f7c776f 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts @@ -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( + ``, + ) + 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( ``, ) 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( + ``, + ) + 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' }], }, ], ], diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index 46390afadb..012ea55d58 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -34,6 +34,8 @@ export class CodegenContext { identifiers: Record = Object.create(null) + seenInlineHandlerNames: Record = Object.create(null) + block: BlockIRNode withId(fn: () => T, map: Record): T { const { identifiers } = this diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts index dcbedb4724..997414001c 100644 --- a/packages/compiler-vapor/src/generators/component.ts +++ b/packages/compiler-vapor/src/generators/component.ts @@ -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( + (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, InlineHandler[]] { + const ids: Record = 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, diff --git a/packages/compiler-vapor/src/generators/event.ts b/packages/compiler-vapor/src/generators/event.ts index 8698c722c7..c0b7e1095c 100644 --- a/packages/compiler-vapor/src/generators/event.ts +++ b/packages/compiler-vapor/src/generators/event.ts @@ -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(