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
|
// 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`] = `
|
exports[`compiler: element transform > component > do not resolve component from non-script-setup bindings 1`] = `
|
||||||
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
|
"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`] = `
|
exports[`compiler: element transform > component > static props 1`] = `
|
||||||
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
|
"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`] = `
|
exports[`compiler: element transform > component > v-on="obj" 1`] = `
|
||||||
"import { resolveComponent as _resolveComponent, toHandlers as _toHandlers, createComponentWithFallback as _createComponentWithFallback } from 'vue';
|
"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(
|
const { code, ir } = compileWithElementTransform(
|
||||||
`<Foo v-on:bar="handleBar($event)" />`,
|
`<Foo v-on:bar="() => handler" />`,
|
||||||
)
|
)
|
||||||
expect(code).toMatchSnapshot()
|
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([
|
expect(ir.block.operation).toMatchObject([
|
||||||
{
|
{
|
||||||
type: IRNodeTypes.CREATE_COMPONENT_NODE,
|
type: IRNodeTypes.CREATE_COMPONENT_NODE,
|
||||||
|
@ -398,7 +399,74 @@ describe('compiler: element transform', () => {
|
||||||
{
|
{
|
||||||
key: { content: 'bar' },
|
key: { content: 'bar' },
|
||||||
handler: true,
|
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)
|
identifiers: Record<string, string[]> = Object.create(null)
|
||||||
|
|
||||||
|
seenInlineHandlerNames: Record<string, number> = Object.create(null)
|
||||||
|
|
||||||
block: BlockIRNode
|
block: BlockIRNode
|
||||||
withId<T>(fn: () => T, map: Record<string, string | null>): T {
|
withId<T>(fn: () => T, map: Record<string, string | null>): T {
|
||||||
const { identifiers } = this
|
const { identifiers } = this
|
||||||
|
|
|
@ -29,7 +29,9 @@ import {
|
||||||
import { genExpression } from './expression'
|
import { genExpression } from './expression'
|
||||||
import { genPropKey, genPropValue } from './prop'
|
import { genPropKey, genPropValue } from './prop'
|
||||||
import {
|
import {
|
||||||
|
type SimpleExpressionNode,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
|
isMemberExpression,
|
||||||
toValidAssetId,
|
toValidAssetId,
|
||||||
walkIdentifiers,
|
walkIdentifiers,
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
|
@ -46,11 +48,20 @@ export function genCreateComponent(
|
||||||
|
|
||||||
const tag = genTag()
|
const tag = genTag()
|
||||||
const { root, props, slots, once } = operation
|
const { root, props, slots, once } = operation
|
||||||
const rawProps = genRawProps(props, context)
|
|
||||||
const rawSlots = genRawSlots(slots, 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 [
|
return [
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
|
...inlineHandlers,
|
||||||
`const n${operation.id} = `,
|
`const n${operation.id} = `,
|
||||||
...genCall(
|
...genCall(
|
||||||
operation.asset
|
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(
|
export function genRawProps(
|
||||||
props: IRProps[],
|
props: IRProps[],
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
|
|
|
@ -88,6 +88,7 @@ export function genEventHandler(
|
||||||
nonKeys: string[]
|
nonKeys: string[]
|
||||||
keys: string[]
|
keys: string[]
|
||||||
} = { nonKeys: [], keys: [] },
|
} = { nonKeys: [], keys: [] },
|
||||||
|
needWrap: boolean = true,
|
||||||
): CodeFragment[] {
|
): CodeFragment[] {
|
||||||
let handlerExp: CodeFragment[] = [`() => {}`]
|
let handlerExp: CodeFragment[] = [`() => {}`]
|
||||||
if (value && value.content.trim()) {
|
if (value && value.content.trim()) {
|
||||||
|
@ -117,7 +118,8 @@ export function genEventHandler(
|
||||||
handlerExp = genWithModifiers(context, handlerExp, nonKeys)
|
handlerExp = genWithModifiers(context, handlerExp, nonKeys)
|
||||||
if (keys.length) handlerExp = genWithKeys(context, handlerExp, keys)
|
if (keys.length) handlerExp = genWithKeys(context, handlerExp, keys)
|
||||||
|
|
||||||
return [`() => `, ...handlerExp]
|
if (needWrap) handlerExp.unshift(`() => `)
|
||||||
|
return handlerExp
|
||||||
}
|
}
|
||||||
|
|
||||||
function genWithModifiers(
|
function genWithModifiers(
|
||||||
|
|
Loading…
Reference in New Issue