feat: v-on with no argument
This commit is contained in:
parent
0707fa5d21
commit
9b06e04e0f
@ -11,7 +11,8 @@ import {
|
|||||||
CREATE_VNODE,
|
CREATE_VNODE,
|
||||||
MERGE_PROPS,
|
MERGE_PROPS,
|
||||||
RESOLVE_DIRECTIVE,
|
RESOLVE_DIRECTIVE,
|
||||||
APPLY_DIRECTIVES
|
APPLY_DIRECTIVES,
|
||||||
|
TO_HANDLERS
|
||||||
} from '../../src/runtimeConstants'
|
} from '../../src/runtimeConstants'
|
||||||
import {
|
import {
|
||||||
CallExpression,
|
CallExpression,
|
||||||
@ -198,6 +199,67 @@ describe('compiler: element transform', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('v-on="obj"', () => {
|
||||||
|
const { root, node } = parseWithElementTransform(
|
||||||
|
`<div id="foo" v-on="obj" class="bar" />`
|
||||||
|
)
|
||||||
|
expect(root.imports).toContain(MERGE_PROPS)
|
||||||
|
expect(node.callee).toBe(CREATE_VNODE)
|
||||||
|
expect(node.arguments[1]).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: MERGE_PROPS,
|
||||||
|
arguments: [
|
||||||
|
createStaticObjectMatcher({
|
||||||
|
id: 'foo'
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: TO_HANDLERS,
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.EXPRESSION,
|
||||||
|
content: `obj`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
createStaticObjectMatcher({
|
||||||
|
class: 'bar'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('v-on="obj" + v-bind="obj"', () => {
|
||||||
|
const { root, node } = parseWithElementTransform(
|
||||||
|
`<div id="foo" v-on="handlers" v-bind="obj" />`
|
||||||
|
)
|
||||||
|
expect(root.imports).toContain(MERGE_PROPS)
|
||||||
|
expect(node.callee).toBe(CREATE_VNODE)
|
||||||
|
expect(node.arguments[1]).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: MERGE_PROPS,
|
||||||
|
arguments: [
|
||||||
|
createStaticObjectMatcher({
|
||||||
|
id: 'foo'
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: TO_HANDLERS,
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
type: NodeTypes.EXPRESSION,
|
||||||
|
content: `handlers`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.EXPRESSION,
|
||||||
|
content: `obj`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('error on v-bind with no argument', () => {
|
test('error on v-bind with no argument', () => {
|
||||||
const onError = jest.fn()
|
const onError = jest.fn()
|
||||||
parseWithElementTransform(`<div v-bind/>`, { onError })
|
parseWithElementTransform(`<div v-bind/>`, { onError })
|
||||||
|
@ -67,6 +67,7 @@ export const enum ErrorCodes {
|
|||||||
X_FOR_NO_EXPRESSION,
|
X_FOR_NO_EXPRESSION,
|
||||||
X_FOR_MALFORMED_EXPRESSION,
|
X_FOR_MALFORMED_EXPRESSION,
|
||||||
X_V_BIND_NO_EXPRESSION,
|
X_V_BIND_NO_EXPRESSION,
|
||||||
|
X_V_ON_NO_EXPRESSION,
|
||||||
|
|
||||||
// generic errors
|
// generic errors
|
||||||
X_PREFIX_ID_NOT_SUPPORTED
|
X_PREFIX_ID_NOT_SUPPORTED
|
||||||
@ -133,6 +134,8 @@ export const errorMessages: { [code: number]: string } = {
|
|||||||
[ErrorCodes.X_ELSE_NO_ADJACENT_IF]: `v-else has no adjacent v-if`,
|
[ErrorCodes.X_ELSE_NO_ADJACENT_IF]: `v-else has no adjacent v-if`,
|
||||||
[ErrorCodes.X_FOR_NO_EXPRESSION]: `v-for has no expression`,
|
[ErrorCodes.X_FOR_NO_EXPRESSION]: `v-for has no expression`,
|
||||||
[ErrorCodes.X_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression`,
|
[ErrorCodes.X_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression`,
|
||||||
|
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression`,
|
||||||
|
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression`,
|
||||||
|
|
||||||
// generic errors
|
// generic errors
|
||||||
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler because it is optimized for payload size.`
|
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler because it is optimized for payload size.`
|
||||||
|
@ -13,3 +13,4 @@ export const RENDER_LIST = `renderList`
|
|||||||
export const CAPITALIZE = `capitalize`
|
export const CAPITALIZE = `capitalize`
|
||||||
export const TO_STRING = `toString`
|
export const TO_STRING = `toString`
|
||||||
export const MERGE_PROPS = `mergeProps`
|
export const MERGE_PROPS = `mergeProps`
|
||||||
|
export const TO_HANDLERS = `toHandlers`
|
||||||
|
@ -21,7 +21,8 @@ import {
|
|||||||
APPLY_DIRECTIVES,
|
APPLY_DIRECTIVES,
|
||||||
RESOLVE_DIRECTIVE,
|
RESOLVE_DIRECTIVE,
|
||||||
RESOLVE_COMPONENT,
|
RESOLVE_COMPONENT,
|
||||||
MERGE_PROPS
|
MERGE_PROPS,
|
||||||
|
TO_HANDLERS
|
||||||
} from '../runtimeConstants'
|
} from '../runtimeConstants'
|
||||||
|
|
||||||
const toValidId = (str: string): string => str.replace(/[^\w]/g, '')
|
const toValidId = (str: string): string => str.replace(/[^\w]/g, '')
|
||||||
@ -97,15 +98,17 @@ export const transformElement: NodeTransform = (node, context) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
|
||||||
|
|
||||||
function buildProps(
|
function buildProps(
|
||||||
{ loc, props }: ElementNode,
|
{ loc: elementLoc, props }: ElementNode,
|
||||||
context: TransformContext
|
context: TransformContext
|
||||||
): {
|
): {
|
||||||
props: ObjectExpression | CallExpression | ExpressionNode
|
props: PropsExpression
|
||||||
directives: DirectiveNode[]
|
directives: DirectiveNode[]
|
||||||
} {
|
} {
|
||||||
let properties: ObjectExpression['properties'] = []
|
let properties: ObjectExpression['properties'] = []
|
||||||
const mergeArgs: Array<ObjectExpression | ExpressionNode> = []
|
const mergeArgs: PropsExpression[] = []
|
||||||
const runtimeDirectives: DirectiveNode[] = []
|
const runtimeDirectives: DirectiveNode[] = []
|
||||||
|
|
||||||
for (let i = 0; i < props.length; i++) {
|
for (let i = 0; i < props.length; i++) {
|
||||||
@ -126,24 +129,43 @@ function buildProps(
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// directives
|
// directives
|
||||||
// special case for v-bind with no argument
|
const { name, arg, exp, loc } = prop
|
||||||
if (prop.name === 'bind' && !prop.arg) {
|
// special case for v-bind and v-on with no argument
|
||||||
if (prop.exp) {
|
const isBind = name === 'bind'
|
||||||
|
if (!arg && (isBind || name === 'on')) {
|
||||||
|
if (exp) {
|
||||||
if (properties.length) {
|
if (properties.length) {
|
||||||
mergeArgs.push(createObjectExpression(properties, loc))
|
mergeArgs.push(createObjectExpression(properties, elementLoc))
|
||||||
properties = []
|
properties = []
|
||||||
}
|
}
|
||||||
mergeArgs.push(prop.exp)
|
if (isBind) {
|
||||||
|
mergeArgs.push(exp)
|
||||||
|
} else {
|
||||||
|
// v-on="obj" -> toHandlers(obj)
|
||||||
|
context.imports.add(TO_HANDLERS)
|
||||||
|
mergeArgs.push({
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
loc,
|
||||||
|
callee: TO_HANDLERS,
|
||||||
|
arguments: [exp]
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
context.onError(
|
context.onError(
|
||||||
createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, prop.loc)
|
createCompilerError(
|
||||||
|
isBind
|
||||||
|
? ErrorCodes.X_V_BIND_NO_EXPRESSION
|
||||||
|
: ErrorCodes.X_V_ON_NO_EXPRESSION,
|
||||||
|
loc
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const directiveTransform = context.directiveTransforms[prop.name]
|
const directiveTransform = context.directiveTransforms[name]
|
||||||
if (directiveTransform) {
|
if (directiveTransform) {
|
||||||
|
// has built-in directive transform.
|
||||||
const { props, needRuntime } = directiveTransform(prop, context)
|
const { props, needRuntime } = directiveTransform(prop, context)
|
||||||
if (isArray(props)) {
|
if (isArray(props)) {
|
||||||
properties.push(...props)
|
properties.push(...props)
|
||||||
@ -160,26 +182,26 @@ function buildProps(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret: ObjectExpression | CallExpression | ExpressionNode
|
let propsExpression: PropsExpression
|
||||||
|
|
||||||
// has v-bind="object", wrap with mergeProps
|
// has v-bind="object" or v-on="object", wrap with mergeProps
|
||||||
if (mergeArgs.length) {
|
if (mergeArgs.length) {
|
||||||
if (properties.length) {
|
if (properties.length) {
|
||||||
mergeArgs.push(createObjectExpression(properties, loc))
|
mergeArgs.push(createObjectExpression(properties, elementLoc))
|
||||||
}
|
}
|
||||||
if (mergeArgs.length > 1) {
|
if (mergeArgs.length > 1) {
|
||||||
context.imports.add(MERGE_PROPS)
|
context.imports.add(MERGE_PROPS)
|
||||||
ret = createCallExpression(MERGE_PROPS, mergeArgs, loc)
|
propsExpression = createCallExpression(MERGE_PROPS, mergeArgs, elementLoc)
|
||||||
} else {
|
} else {
|
||||||
// single v-bind with nothing else - no need for a mergeProps call
|
// single v-bind with nothing else - no need for a mergeProps call
|
||||||
ret = mergeArgs[0]
|
propsExpression = mergeArgs[0]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ret = createObjectExpression(properties, loc)
|
propsExpression = createObjectExpression(properties, elementLoc)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: ret,
|
props: propsExpression,
|
||||||
directives: runtimeDirectives
|
directives: runtimeDirectives
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
packages/runtime-core/src/helpers/toHandlers.ts
Normal file
15
packages/runtime-core/src/helpers/toHandlers.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { isObject } from '@vue/shared'
|
||||||
|
import { warn } from '../warning'
|
||||||
|
|
||||||
|
// For prefixing keys in v-on="obj" with "on"
|
||||||
|
export function toHandlers(obj: Record<string, any>): Record<string, any> {
|
||||||
|
const ret: Record<string, any> = {}
|
||||||
|
if (__DEV__ && !isObject(obj)) {
|
||||||
|
warn(`v-on with no argument expects an object value.`)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
for (const key in obj) {
|
||||||
|
ret[`on${key}`] = obj[key]
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
@ -41,6 +41,7 @@ export { applyDirectives } from './directives'
|
|||||||
export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
|
export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
|
||||||
export { renderList } from './helpers/renderList'
|
export { renderList } from './helpers/renderList'
|
||||||
export { toString } from './helpers/toString'
|
export { toString } from './helpers/toString'
|
||||||
|
export { toHandlers } from './helpers/toHandlers'
|
||||||
export { capitalize } from '@vue/shared'
|
export { capitalize } from '@vue/shared'
|
||||||
|
|
||||||
// Internal, for integration with runtime compiler
|
// Internal, for integration with runtime compiler
|
||||||
|
Loading…
x
Reference in New Issue
Block a user