Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 10 commits into
base: vapor
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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';

Expand Down Expand Up @@ -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';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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' }],
},
],
],
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler-vapor/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 51 additions & 1 deletion packages/compiler-vapor/src/generators/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 3 additions & 1 deletion packages/compiler-vapor/src/generators/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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(
Expand Down
Loading