// 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';
}"
`;
-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';
}"
`;
+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';
])
})
- 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,
{
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' }],
},
],
],
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
import { genExpression } from './expression'
import { genPropKey, genPropValue } from './prop'
import {
+ type SimpleExpressionNode,
createSimpleExpression,
+ isMemberExpression,
toValidAssetId,
walkIdentifiers,
} from '@vue/compiler-core'
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
}
}
+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,
nonKeys: string[]
keys: string[]
} = { nonKeys: [], keys: [] },
+ needWrap: boolean = true,
): CodeFragment[] {
let handlerExp: CodeFragment[] = [`() => {}`]
if (value && value.content.trim()) {
handlerExp = genWithModifiers(context, handlerExp, nonKeys)
if (keys.length) handlerExp = genWithKeys(context, handlerExp, keys)
- return [`() => `, ...handlerExp]
+ if (needWrap) handlerExp.unshift(`() => `)
+ return handlerExp
}
function genWithModifiers(