feat(v-on): cache handlers
This commit is contained in:
@@ -5,8 +5,7 @@ import {
|
||||
ElementNode,
|
||||
ObjectExpression,
|
||||
CallExpression,
|
||||
NodeTypes,
|
||||
Property
|
||||
NodeTypes
|
||||
} from '@vue/compiler-core'
|
||||
import { transformOn } from '../../src/transforms/vOn'
|
||||
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../../src/runtimeHelpers'
|
||||
@@ -14,10 +13,7 @@ import { transformElement } from '../../../compiler-core/src/transforms/transfor
|
||||
import { transformExpression } from '../../../compiler-core/src/transforms/transformExpression'
|
||||
import { createObjectMatcher } from '../../../compiler-core/__tests__/testUtils'
|
||||
|
||||
function parseVOnProperties(
|
||||
template: string,
|
||||
options: CompilerOptions = {}
|
||||
): Property[] {
|
||||
function parseWithVOn(template: string, options: CompilerOptions = {}) {
|
||||
const ast = parse(template)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformExpression, transformElement],
|
||||
@@ -26,13 +22,18 @@ function parseVOnProperties(
|
||||
},
|
||||
...options
|
||||
})
|
||||
return (((ast.children[0] as ElementNode).codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression).properties
|
||||
return {
|
||||
root: ast,
|
||||
props: (((ast.children[0] as ElementNode).codegenNode as CallExpression)
|
||||
.arguments[1] as ObjectExpression).properties
|
||||
}
|
||||
}
|
||||
|
||||
describe('compiler-dom: transform v-on', () => {
|
||||
it('should support multiple modifiers w/ prefixIdentifiers: true', () => {
|
||||
const [prop] = parseVOnProperties(`<div @click.stop.prevent="test"/>`, {
|
||||
const {
|
||||
props: [prop]
|
||||
} = parseWithVOn(`<div @click.stop.prevent="test"/>`, {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
expect(prop).toMatchObject({
|
||||
@@ -45,10 +46,11 @@ describe('compiler-dom: transform v-on', () => {
|
||||
})
|
||||
|
||||
it('should support multiple modifiers and event options w/ prefixIdentifiers: true', () => {
|
||||
const [prop] = parseVOnProperties(
|
||||
`<div @click.stop.capture.passive="test"/>`,
|
||||
{ prefixIdentifiers: true }
|
||||
)
|
||||
const {
|
||||
props: [prop]
|
||||
} = parseWithVOn(`<div @click.stop.capture.passive="test"/>`, {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
expect(prop).toMatchObject({
|
||||
type: NodeTypes.JS_PROPERTY,
|
||||
value: createObjectMatcher({
|
||||
@@ -59,17 +61,17 @@ describe('compiler-dom: transform v-on', () => {
|
||||
options: createObjectMatcher({
|
||||
capture: { content: 'true', isStatic: false },
|
||||
passive: { content: 'true', isStatic: false }
|
||||
}),
|
||||
persistent: { content: 'true', isStatic: false }
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should wrap keys guard for keyboard events or dynamic events', () => {
|
||||
const [prop] = parseVOnProperties(
|
||||
`<div @keyDown.stop.capture.ctrl.a="test"/>`,
|
||||
{ prefixIdentifiers: true }
|
||||
)
|
||||
const {
|
||||
props: [prop]
|
||||
} = parseWithVOn(`<div @keyDown.stop.capture.ctrl.a="test"/>`, {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
expect(prop).toMatchObject({
|
||||
type: NodeTypes.JS_PROPERTY,
|
||||
value: createObjectMatcher({
|
||||
@@ -85,14 +87,15 @@ describe('compiler-dom: transform v-on', () => {
|
||||
},
|
||||
options: createObjectMatcher({
|
||||
capture: { content: 'true', isStatic: false }
|
||||
}),
|
||||
persistent: { content: 'true', isStatic: false }
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should not wrap keys guard if no key modifier is present', () => {
|
||||
const [prop] = parseVOnProperties(`<div @keyup.exact="test"/>`, {
|
||||
const {
|
||||
props: [prop]
|
||||
} = parseWithVOn(`<div @keyup.exact="test"/>`, {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
expect(prop).toMatchObject({
|
||||
@@ -105,7 +108,9 @@ describe('compiler-dom: transform v-on', () => {
|
||||
})
|
||||
|
||||
it('should not wrap normal guard if there is only keys guard', () => {
|
||||
const [prop] = parseVOnProperties(`<div @keyup.enter="test"/>`, {
|
||||
const {
|
||||
props: [prop]
|
||||
} = parseWithVOn(`<div @keyup.enter="test"/>`, {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
expect(prop).toMatchObject({
|
||||
@@ -116,4 +121,37 @@ describe('compiler-dom: transform v-on', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('cache handler w/ modifiers', () => {
|
||||
const {
|
||||
root,
|
||||
props: [prop]
|
||||
} = parseWithVOn(`<div @keyup.enter.capture="foo" />`, {
|
||||
prefixIdentifiers: true,
|
||||
cacheHandlers: true
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect((root as any).children[0].codegenNode.arguments.length).toBe(2)
|
||||
expect(prop.value).toMatchObject({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
index: 1,
|
||||
value: {
|
||||
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||
properties: [
|
||||
{
|
||||
key: { content: 'handler' },
|
||||
value: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: V_ON_WITH_KEYS
|
||||
}
|
||||
},
|
||||
{
|
||||
key: { content: 'options' },
|
||||
value: { type: NodeTypes.JS_OBJECT_EXPRESSION }
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -25,61 +25,60 @@ const isKeyboardEvent = /*#__PURE__*/ makeMap(
|
||||
)
|
||||
|
||||
export const transformOn: DirectiveTransform = (dir, node, context) => {
|
||||
const { modifiers } = dir
|
||||
const baseResult = baseTransform(dir, node, context)
|
||||
if (!modifiers.length) return baseResult
|
||||
return baseTransform(dir, node, context, baseResult => {
|
||||
const { modifiers } = dir
|
||||
if (!modifiers.length) return baseResult
|
||||
|
||||
let { key, value: handlerExp } = baseResult.props[0]
|
||||
let { key, value: handlerExp } = baseResult.props[0]
|
||||
|
||||
// modifiers for addEventListener() options, e.g. .passive & .capture
|
||||
const eventOptionModifiers = modifiers.filter(isEventOptionModifier)
|
||||
// modifiers that needs runtime guards
|
||||
const runtimeModifiers = modifiers.filter(m => !isEventOptionModifier(m))
|
||||
// modifiers for addEventListener() options, e.g. .passive & .capture
|
||||
const eventOptionModifiers = modifiers.filter(isEventOptionModifier)
|
||||
// modifiers that needs runtime guards
|
||||
const runtimeModifiers = modifiers.filter(m => !isEventOptionModifier(m))
|
||||
|
||||
// built-in modifiers that are not keys
|
||||
const nonKeyModifiers = runtimeModifiers.filter(isNonKeyModifier)
|
||||
if (nonKeyModifiers.length) {
|
||||
handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [
|
||||
handlerExp,
|
||||
JSON.stringify(nonKeyModifiers)
|
||||
])
|
||||
}
|
||||
// built-in modifiers that are not keys
|
||||
const nonKeyModifiers = runtimeModifiers.filter(isNonKeyModifier)
|
||||
if (nonKeyModifiers.length) {
|
||||
handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [
|
||||
handlerExp,
|
||||
JSON.stringify(nonKeyModifiers)
|
||||
])
|
||||
}
|
||||
|
||||
const keyModifiers = runtimeModifiers.filter(m => !isNonKeyModifier(m))
|
||||
if (
|
||||
keyModifiers.length &&
|
||||
// if event name is dynamic, always wrap with keys guard
|
||||
(key.type === NodeTypes.COMPOUND_EXPRESSION ||
|
||||
!key.isStatic ||
|
||||
isKeyboardEvent(key.content))
|
||||
) {
|
||||
handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [
|
||||
handlerExp,
|
||||
JSON.stringify(keyModifiers)
|
||||
])
|
||||
}
|
||||
const keyModifiers = runtimeModifiers.filter(m => !isNonKeyModifier(m))
|
||||
if (
|
||||
keyModifiers.length &&
|
||||
// if event name is dynamic, always wrap with keys guard
|
||||
(key.type === NodeTypes.COMPOUND_EXPRESSION ||
|
||||
!key.isStatic ||
|
||||
isKeyboardEvent(key.content))
|
||||
) {
|
||||
handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [
|
||||
handlerExp,
|
||||
JSON.stringify(keyModifiers)
|
||||
])
|
||||
}
|
||||
|
||||
if (eventOptionModifiers.length) {
|
||||
handlerExp = createObjectExpression([
|
||||
createObjectProperty('handler', handlerExp),
|
||||
createObjectProperty(
|
||||
'options',
|
||||
createObjectExpression(
|
||||
eventOptionModifiers.map(modifier =>
|
||||
createObjectProperty(
|
||||
modifier,
|
||||
createSimpleExpression('true', false)
|
||||
if (eventOptionModifiers.length) {
|
||||
handlerExp = createObjectExpression([
|
||||
createObjectProperty('handler', handlerExp),
|
||||
createObjectProperty(
|
||||
'options',
|
||||
createObjectExpression(
|
||||
eventOptionModifiers.map(modifier =>
|
||||
createObjectProperty(
|
||||
modifier,
|
||||
createSimpleExpression('true', false)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
// so the runtime knows the options never change
|
||||
createObjectProperty('persistent', createSimpleExpression('true', false))
|
||||
])
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
return {
|
||||
props: [createObjectProperty(key, handlerExp)],
|
||||
needRuntime: false
|
||||
}
|
||||
return {
|
||||
props: [createObjectProperty(key, handlerExp)],
|
||||
needRuntime: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user