Skip to content

Commit

Permalink
feat(v-on): use runtime helper, wip
Browse files Browse the repository at this point in the history
  • Loading branch information
fnlctrl committed Oct 11, 2019
1 parent 06c4236 commit b48fb93
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 177 deletions.
10 changes: 1 addition & 9 deletions packages/compiler-core/src/index.ts
Expand Up @@ -103,12 +103,4 @@ export { registerRuntimeHelpers } from './runtimeHelpers'

// expose transforms so higher-order compilers can import and extend them
export { transformModel } from './transforms/vModel'
export {
transformOn,
// Probably not ok to export like this...
// Need better ways to let compiler-dom import things from compiler-core
createVOnEventName,
validateVOnArguments,
processVOnHanlder,
VOnDirectiveNode
} from './transforms/vOn'
export { transformOn } from './transforms/vOn'
86 changes: 35 additions & 51 deletions packages/compiler-core/src/transforms/vOn.ts
@@ -1,19 +1,39 @@
import { DirectiveTransform, TransformContext } from '../transform'
import { DirectiveTransform } from '../transform'
import {
DirectiveNode,
createObjectProperty,
createSimpleExpression,
ExpressionNode,
NodeTypes,
createCompoundExpression,
SimpleExpressionNode,
DirectiveNode
SimpleExpressionNode
} from '../ast'
import { capitalize, fnExpRE } from '@vue/shared'
import { capitalize } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
import { isMemberExpression } from '../utils'

export const createVOnEventName = (arg: ExpressionNode): ExpressionNode => {
const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/

export type VOnDirectiveNode = Omit<DirectiveNode, 'arg' | 'exp'> & {
// v-on without arg is handled directly in ./element.ts due to it affecting
// codegen for the entire props object. This transform here is only for v-on
// *with* args.
arg: ExpressionNode
// exp is guaranteed to be a simple expression here because v-on w/ arg is
// skipped by transformExpression as a special case.
exp: SimpleExpressionNode | undefined
}

export const transformOn: DirectiveTransform = (
dir: VOnDirectiveNode,
node,
context
) => {
const { loc, modifiers, arg } = dir
if (!dir.exp && !modifiers.length) {
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
}
let eventName: ExpressionNode
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
if (arg.isStatic) {
Expand All @@ -31,72 +51,36 @@ export const createVOnEventName = (arg: ExpressionNode): ExpressionNode => {
eventName.children.unshift(`"on" + (`)
eventName.children.push(`)`)
}
return eventName
}

export const validateVOnArguments = (
dir: DirectiveNode,
context: TransformContext
) => {
const { loc, modifiers } = dir
if (!dir.exp && !modifiers.length) {
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
}
}

// handler processing
export const processVOnHanlder = (
exp: ExpressionNode,
context: TransformContext
): ExpressionNode => {
if (!__BROWSER__ && context.prefixIdentifiers) {
context.addIdentifiers(`$event`)
let result = processExpression(exp as SimpleExpressionNode, context)
context.removeIdentifiers(`$event`)
return result
}
return exp
}

export type VOnDirectiveNode = Omit<DirectiveNode, 'arg' | 'exp'> & {
// v-on without arg is handled directly in ./element.ts due to it affecting
// codegen for the entire props object. This transform here is only for v-on
// *with* args.
arg: ExpressionNode
// exp is guaranteed to be a simple expression here because v-on w/ arg is
// skipped by transformExpression as a special case.
exp: SimpleExpressionNode | undefined
}
export const transformOn: DirectiveTransform = (
dir: VOnDirectiveNode,
node,
context
) => {
validateVOnArguments(dir, context)
let eventName = createVOnEventName(dir.arg)
// TODO .once modifier handling since it is platform agnostic
// other modifiers are handled in compiler-dom

// handler processing
let exp: ExpressionNode | undefined = dir.exp
if (exp) {
const isInlineStatement = !(
isMemberExpression(exp.content) || fnExpRE.test(exp.content)
)
exp = processVOnHanlder(exp, context)
// wrap inline statement in a function expression
// process the expression since it's been skipped
if (!__BROWSER__ && context.prefixIdentifiers) {
context.addIdentifiers(`$event`)
exp = processExpression(exp, context)
context.removeIdentifiers(`$event`)
}
if (isInlineStatement) {
// wrap inline statement in a function expression
exp = createCompoundExpression([
`$event => (`,
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
`)`
])
}
}

return {
props: [
createObjectProperty(
eventName,
exp || createSimpleExpression(`() => {}`, false, dir.loc)
exp || createSimpleExpression(`() => {}`, false, loc)
)
],
needRuntime: false
Expand Down
52 changes: 7 additions & 45 deletions packages/compiler-dom/__tests__/transforms/vOn.spec.ts
Expand Up @@ -27,55 +27,17 @@ function parseWithVOn(
}

describe('compiler-dom: transform v-on', () => {
it('should support .stop modifier', () => {
const node = parseWithVOn(`<div @click.stop="test"/>`, {
it('should support muliple modifiers w/ prefixIdentifiers: true', () => {
const node = parseWithVOn(`<div @click.stop.prevent="test"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0].value).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`$event => {`,
`$event.stopPropagation();`,
'(',
{
content: '_ctx.test',
isStatic: false,
type: NodeTypes.SIMPLE_EXPRESSION,
loc: expect.anything()
},
')',
'($event)',
'}'
]
})
})

it('should support muliple modifiers, and ignore unknown modifier', () => {
const node = parseWithVOn(`<div @click.stop.prevent.gibberish="test"/>`, {
prefixIdentifiers: true
})
const props = (node.codegenNode as CallExpression)
.arguments[1] as ObjectExpression
expect(props.properties[0].value).toMatchObject({
type: NodeTypes.COMPOUND_EXPRESSION,
children: [
`$event => {`,
`$event.stopPropagation();`,
`$event.preventDefault();`,
'', // ignored modifier "gibberish"
'(',
{
content: '_ctx.test',
isStatic: false,
type: NodeTypes.SIMPLE_EXPRESSION,
loc: expect.anything()
},
')',
'($event)',
'}'
]
expect(props.properties[0]).toMatchObject({
type: NodeTypes.JS_PROPERTY,
value: {
arguments: [{ content: '_ctx.test' }, '["stop","prevent"]']
}
})
})
})
5 changes: 4 additions & 1 deletion packages/compiler-dom/src/runtimeHelpers.ts
Expand Up @@ -6,10 +6,13 @@ export const V_MODEL_TEXT = Symbol(__DEV__ ? `vModelText` : ``)
export const V_MODEL_SELECT = Symbol(__DEV__ ? `vModelSelect` : ``)
export const V_MODEL_DYNAMIC = Symbol(__DEV__ ? `vModelDynamic` : ``)

export const V_ON_MODIFIERS_GUARD = Symbol(__DEV__ ? `vOnModifiersGuard` : ``)

registerRuntimeHelpers({
[V_MODEL_RADIO]: `vModelRadio`,
[V_MODEL_CHECKBOX]: `vModelCheckbox`,
[V_MODEL_TEXT]: `vModelText`,
[V_MODEL_SELECT]: `vModelSelect`,
[V_MODEL_DYNAMIC]: `vModelDynamic`
[V_MODEL_DYNAMIC]: `vModelDynamic`,
[V_ON_MODIFIERS_GUARD]: `vOnModifiersGuard`
})
88 changes: 17 additions & 71 deletions packages/compiler-dom/src/transforms/vOn.ts
@@ -1,80 +1,26 @@
import {
transformOn as baseTransform,
createVOnEventName,
validateVOnArguments,
processVOnHanlder,
VOnDirectiveNode,
DirectiveTransform,
createObjectProperty,
ExpressionNode,
createCompoundExpression,
NodeTypes
createCallExpression
} from '@vue/compiler-core'
import { EMPTY_ARR, fnExpRE } from '@vue/shared'
import { V_ON_MODIFIERS_GUARD } from '../runtimeHelpers'

// const keyCodes: { [key: string]: number | Array<number> } = {
// esc: 27,
// tab: 9,
// enter: 13,
// space: 32,
// up: 38,
// left: 37,
// right: 39,
// down: 40,
// delete: [8, 46]
// }

const genGuard = (condition: string) => `if(${condition})return null;`
const modifierCode = {
stop: '$event.stopPropagation();',
prevent: '$event.preventDefault();',
self: genGuard(`$event.target !== $event.currentTarget`),
ctrl: genGuard(`!$event.ctrlKey`),
shift: genGuard(`!$event.shiftKey`),
alt: genGuard(`!$event.altKey`),
meta: genGuard(`!$event.metaKey`),
left: genGuard(`'button' in $event && $event.button !== 0`),
middle: genGuard(`'button' in $event && $event.button !== 1`),
right: genGuard(`'button' in $event && $event.button !== 2`)
} as const

const isValidDomModifier = (key: string): key is keyof typeof modifierCode =>
key in modifierCode

export const transformOn: DirectiveTransform = (
dir: VOnDirectiveNode,
node,
context
) => {
validateVOnArguments(dir, context)

const { modifiers, arg } = dir
let hasDomModifiers = modifiers.length && modifiers.some(isValidDomModifier)
if (!hasDomModifiers) return baseTransform(dir, node, context)

let eventName = createVOnEventName(arg)
let guards = modifiers.map(
m => (isValidDomModifier(m) ? modifierCode[m] : '')
)
let exp: ExpressionNode | undefined = dir.exp
if (exp) {
if (fnExpRE.test(exp.content)) exp.content = `(${exp.content})($event)`
exp = processVOnHanlder(exp, context)
}
exp = createCompoundExpression([
`$event => {`,
...guards,
...(exp
? exp.type === NodeTypes.SIMPLE_EXPRESSION
? ['(', exp, ')', '($event)']
: exp.children
: EMPTY_ARR),
`}`
])
let res = {
props: [createObjectProperty(eventName, exp)],
export const transformOn: DirectiveTransform = (dir, node, context) => {
const { modifiers } = dir
let baseResult = baseTransform(dir, node, context)
if (!modifiers.length) return baseResult
let exp = baseResult.props[0].value
return {
props: [
createObjectProperty(
baseResult.props[0].key,
createCallExpression(context.helper(V_ON_MODIFIERS_GUARD), [
exp,
JSON.stringify(dir.modifiers)
])
)
],
needRuntime: false
}
console.log(`result`, JSON.stringify(res.props[0].value, null, 2))
return res
}
6 changes: 6 additions & 0 deletions packages/runtime-dom/src/directives/vOn.ts
@@ -0,0 +1,6 @@
// todo
export const vOnModifiersGuard = (fn: Function, modifiers: string[]) => {
return (event: Event) => {
return fn(event)
}
}

0 comments on commit b48fb93

Please sign in to comment.