refactor(compiler-vapor): cache inline handlers passed to component (#12563)

This commit is contained in:
edison 2025-01-08 14:35:09 +08:00 committed by GitHub
parent ef6986fbc3
commit 757b3df56e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 164 additions and 16 deletions

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

View File

@ -383,12 +383,13 @@ 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="handleBar($event)" />`,
`<Foo v-on:bar="() => handler" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains(`onBar: () => $event => (_ctx.handleBar($event))`)
expect(code).contains(`onBar: () => _on_bar`)
expect(code).contains(`const _on_bar = () => _ctx.handler`)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
@ -398,7 +399,74 @@ describe('compiler: element transform', () => {
{
key: { content: 'bar' },
handler: true,
values: [{ content: 'handleBar($event)' }],
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: () => _on_bar`)
expect(code).contains(
`const _on_bar = $event => (_ctx.handleBar($event))`,
)
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[
{
key: { content: 'bar' },
handler: true,
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' }],
},
],
],

View 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

View 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,

View 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(