From: Evan You Date: Wed, 25 Sep 2019 00:51:48 +0000 (-0400) Subject: feat: v-on with no argument X-Git-Tag: v3.0.0-alpha.0~704 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=9b06e04e0f2970747ca2d0e7491cf15737cc8a94;p=thirdparty%2Fvuejs%2Fcore.git feat: v-on with no argument --- diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index af39a3b8d6..00152a751a 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -11,7 +11,8 @@ import { CREATE_VNODE, MERGE_PROPS, RESOLVE_DIRECTIVE, - APPLY_DIRECTIVES + APPLY_DIRECTIVES, + TO_HANDLERS } from '../../src/runtimeConstants' import { CallExpression, @@ -198,6 +199,67 @@ describe('compiler: element transform', () => { }) }) + test('v-on="obj"', () => { + const { root, node } = parseWithElementTransform( + `
` + ) + 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( + `
` + ) + 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', () => { const onError = jest.fn() parseWithElementTransform(`
`, { onError }) diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts index 2f28f5f797..0842454277 100644 --- a/packages/compiler-core/src/errors.ts +++ b/packages/compiler-core/src/errors.ts @@ -67,6 +67,7 @@ export const enum ErrorCodes { X_FOR_NO_EXPRESSION, X_FOR_MALFORMED_EXPRESSION, X_V_BIND_NO_EXPRESSION, + X_V_ON_NO_EXPRESSION, // generic errors 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_FOR_NO_EXPRESSION]: `v-for has no 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 [ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler because it is optimized for payload size.` diff --git a/packages/compiler-core/src/runtimeConstants.ts b/packages/compiler-core/src/runtimeConstants.ts index 74178b9568..2423c7d73c 100644 --- a/packages/compiler-core/src/runtimeConstants.ts +++ b/packages/compiler-core/src/runtimeConstants.ts @@ -13,3 +13,4 @@ export const RENDER_LIST = `renderList` export const CAPITALIZE = `capitalize` export const TO_STRING = `toString` export const MERGE_PROPS = `mergeProps` +export const TO_HANDLERS = `toHandlers` diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts index fe381dedca..7b73411c74 100644 --- a/packages/compiler-core/src/transforms/transformElement.ts +++ b/packages/compiler-core/src/transforms/transformElement.ts @@ -21,7 +21,8 @@ import { APPLY_DIRECTIVES, RESOLVE_DIRECTIVE, RESOLVE_COMPONENT, - MERGE_PROPS + MERGE_PROPS, + TO_HANDLERS } from '../runtimeConstants' 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( - { loc, props }: ElementNode, + { loc: elementLoc, props }: ElementNode, context: TransformContext ): { - props: ObjectExpression | CallExpression | ExpressionNode + props: PropsExpression directives: DirectiveNode[] } { let properties: ObjectExpression['properties'] = [] - const mergeArgs: Array = [] + const mergeArgs: PropsExpression[] = [] const runtimeDirectives: DirectiveNode[] = [] for (let i = 0; i < props.length; i++) { @@ -126,24 +129,43 @@ function buildProps( ) } else { // directives - // special case for v-bind with no argument - if (prop.name === 'bind' && !prop.arg) { - if (prop.exp) { + const { name, arg, exp, loc } = prop + // special case for v-bind and v-on with no argument + const isBind = name === 'bind' + if (!arg && (isBind || name === 'on')) { + if (exp) { if (properties.length) { - mergeArgs.push(createObjectExpression(properties, loc)) + mergeArgs.push(createObjectExpression(properties, elementLoc)) 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 { 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 } - const directiveTransform = context.directiveTransforms[prop.name] + const directiveTransform = context.directiveTransforms[name] if (directiveTransform) { + // has built-in directive transform. const { props, needRuntime } = directiveTransform(prop, context) if (isArray(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 (properties.length) { - mergeArgs.push(createObjectExpression(properties, loc)) + mergeArgs.push(createObjectExpression(properties, elementLoc)) } if (mergeArgs.length > 1) { context.imports.add(MERGE_PROPS) - ret = createCallExpression(MERGE_PROPS, mergeArgs, loc) + propsExpression = createCallExpression(MERGE_PROPS, mergeArgs, elementLoc) } else { // single v-bind with nothing else - no need for a mergeProps call - ret = mergeArgs[0] + propsExpression = mergeArgs[0] } } else { - ret = createObjectExpression(properties, loc) + propsExpression = createObjectExpression(properties, elementLoc) } return { - props: ret, + props: propsExpression, directives: runtimeDirectives } } diff --git a/packages/runtime-core/src/helpers/toHandlers.ts b/packages/runtime-core/src/helpers/toHandlers.ts new file mode 100644 index 0000000000..777a4f0f84 --- /dev/null +++ b/packages/runtime-core/src/helpers/toHandlers.ts @@ -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): Record { + const ret: Record = {} + 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 +} diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index fe9b519cfe..3f0896d7d4 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -41,6 +41,7 @@ export { applyDirectives } from './directives' export { resolveComponent, resolveDirective } from './helpers/resolveAssets' export { renderList } from './helpers/renderList' export { toString } from './helpers/toString' +export { toHandlers } from './helpers/toHandlers' export { capitalize } from '@vue/shared' // Internal, for integration with runtime compiler