mirror of https://github.com/vuejs/core.git
refactor(compiler-vapor): cache inline handlers passed to component (#12563)
This commit is contained in:
parent
ef6986fbc3
commit
757b3df56e
|
@ -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';
|
||||
|
||||
|
|
|
@ -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' }],
|
||||
},
|
||||
],
|
||||
],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue