vue3-yuanma/packages/compiler-core/src/transforms/vOn.ts

119 lines
3.7 KiB
TypeScript
Raw Normal View History

2019-10-19 09:51:34 +08:00
import { DirectiveTransform, DirectiveTransformResult } from '../transform'
import {
DirectiveNode,
createObjectProperty,
createSimpleExpression,
ExpressionNode,
NodeTypes,
createCompoundExpression,
SimpleExpressionNode
} from '../ast'
import { capitalize } from '@vue/shared'
2019-09-25 10:39:20 +08:00
import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression'
2019-10-19 09:51:34 +08:00
import { isMemberExpression, hasScopeRef } from '../utils'
const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/
export interface VOnDirectiveNode extends DirectiveNode {
// v-on without arg is handled directly in ./transformElements.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,
2019-10-19 09:51:34 +08:00
context,
augmentor
) => {
const { loc, modifiers, arg } = dir
if (!dir.exp && !modifiers.length) {
2019-09-25 10:39:20 +08:00
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
}
let eventName: ExpressionNode
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
if (arg.isStatic) {
eventName = createSimpleExpression(
`on${capitalize(arg.content)}`,
true,
arg.loc
)
} else {
eventName = createCompoundExpression([`"on" + (`, arg, `)`])
}
2019-09-25 10:39:20 +08:00
} else {
2019-10-05 11:12:49 +08:00
// already a compound expression.
eventName = arg
eventName.children.unshift(`"on" + (`)
eventName.children.push(`)`)
2019-09-25 10:39:20 +08:00
}
// handler processing
let exp: ExpressionNode | undefined = dir.exp
2019-10-19 09:51:34 +08:00
let isCacheable: boolean = !exp
if (exp) {
2019-10-19 09:51:34 +08:00
const isMemberExp = isMemberExpression(exp.content)
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
// process the expression since it's been skipped
if (!__BROWSER__ && context.prefixIdentifiers) {
context.addIdentifiers(`$event`)
exp = processExpression(exp, context)
context.removeIdentifiers(`$event`)
2019-10-19 09:51:34 +08:00
// with scope analysis, the function is hoistable if it has no reference
// to scope variables.
isCacheable =
context.cacheHandlers && !hasScopeRef(exp, context.identifiers)
// If the expression is optimizable and is a member expression pointing
// to a function, turn it into invocation (and wrap in an arrow function
// below) so that it always accesses the latest value when called - thus
// avoiding the need to be patched.
if (isCacheable && isMemberExp) {
if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
exp.content += `($event)`
} else {
exp.children.push(`($event)`)
}
}
}
2019-10-19 09:51:34 +08:00
if (isInlineStatement || (isCacheable && isMemberExp)) {
// wrap inline statement in a function expression
exp = createCompoundExpression([
`$event => (`,
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
`)`
])
}
}
2019-10-19 09:51:34 +08:00
let ret: DirectiveTransformResult = {
2019-10-11 06:02:51 +08:00
props: [
createObjectProperty(
eventName,
exp || createSimpleExpression(`() => {}`, false, loc)
2019-10-11 06:02:51 +08:00
)
],
needRuntime: false
}
2019-10-19 09:51:34 +08:00
// apply extended compiler augmentor
if (augmentor) {
ret = augmentor(ret)
}
if (isCacheable) {
// cache handlers so that it's always the same handler being passed down.
2019-10-30 01:51:48 +08:00
// this avoids unnecessary re-renders when users use inline handlers on
2019-10-19 09:51:34 +08:00
// components.
ret.props[0].value = context.cache(ret.props[0].value)
}
return ret
}