feat: v-on with no argument

This commit is contained in:
Evan You 2019-09-24 20:51:48 -04:00
parent 0707fa5d21
commit 9b06e04e0f
6 changed files with 123 additions and 19 deletions

View File

@ -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 })

View File

@ -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.`

View File

@ -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`

View File

@ -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
} }
} }

View 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
}

View File

@ -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