feat(compiler): v-on inline statement handling
This commit is contained in:
parent
3354837ce1
commit
2e2b6924da
@ -121,6 +121,73 @@ describe('compiler: transform v-on', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('should wrap as function if expression is inline statement', () => {
|
||||
const node = parseWithVOn(`<div @click="i++"/>`)
|
||||
const props = node.codegenNode!.arguments[1] as ObjectExpression
|
||||
expect(props.properties[0]).toMatchObject({
|
||||
key: { content: `onClick` },
|
||||
value: {
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [`$event => (`, { content: `i++` }, `)`]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('inline statement w/ prefixIdentifiers: true', () => {
|
||||
const node = parseWithVOn(`<div @click="foo($event)"/>`, {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
const props = node.codegenNode!.arguments[1] as ObjectExpression
|
||||
expect(props.properties[0]).toMatchObject({
|
||||
key: { content: `onClick` },
|
||||
value: {
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [
|
||||
`$event => (`,
|
||||
{ content: `_ctx.foo` },
|
||||
`(`,
|
||||
// should NOT prefix $event
|
||||
{ content: `$event` },
|
||||
`)`,
|
||||
`)`
|
||||
]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('should NOT wrap as function if expression is already function expression', () => {
|
||||
const node = parseWithVOn(`<div @click="$event => foo($event)"/>`)
|
||||
const props = node.codegenNode!.arguments[1] as ObjectExpression
|
||||
expect(props.properties[0]).toMatchObject({
|
||||
key: { content: `onClick` },
|
||||
value: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `$event => foo($event)`
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('function expression w/ prefixIdentifiers: true', () => {
|
||||
const node = parseWithVOn(`<div @click="e => foo(e)"/>`, {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
const props = node.codegenNode!.arguments[1] as ObjectExpression
|
||||
expect(props.properties[0]).toMatchObject({
|
||||
key: { content: `onClick` },
|
||||
value: {
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
children: [
|
||||
{ content: `e` },
|
||||
` => `,
|
||||
{ content: `_ctx.foo` },
|
||||
`(`,
|
||||
{ content: `e` },
|
||||
`)`
|
||||
]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test('should error if no expression AND no modifier', () => {
|
||||
const onError = jest.fn()
|
||||
parseWithVOn(`<div v-on:click />`, { onError })
|
||||
|
@ -72,8 +72,8 @@ export interface TransformContext extends Required<TransformOptions> {
|
||||
replaceNode(node: TemplateChildNode): void
|
||||
removeNode(node?: TemplateChildNode): void
|
||||
onNodeRemoved: () => void
|
||||
addIdentifiers(exp: ExpressionNode): void
|
||||
removeIdentifiers(exp: ExpressionNode): void
|
||||
addIdentifiers(exp: ExpressionNode | string): void
|
||||
removeIdentifiers(exp: ExpressionNode | string): void
|
||||
hoist(exp: JSChildNode): SimpleExpressionNode
|
||||
}
|
||||
|
||||
@ -152,7 +152,9 @@ function createTransformContext(
|
||||
addIdentifiers(exp) {
|
||||
// identifier tracking only happens in non-browser builds.
|
||||
if (!__BROWSER__) {
|
||||
if (exp.identifiers) {
|
||||
if (isString(exp)) {
|
||||
addId(exp)
|
||||
} else if (exp.identifiers) {
|
||||
exp.identifiers.forEach(addId)
|
||||
} else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
addId(exp.content)
|
||||
@ -161,7 +163,9 @@ function createTransformContext(
|
||||
},
|
||||
removeIdentifiers(exp) {
|
||||
if (!__BROWSER__) {
|
||||
if (exp.identifiers) {
|
||||
if (isString(exp)) {
|
||||
removeId(exp)
|
||||
} else if (exp.identifiers) {
|
||||
exp.identifiers.forEach(removeId)
|
||||
} else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
removeId(exp.content)
|
||||
|
@ -35,11 +35,13 @@ export const transformExpression: NodeTransform = (node, context) => {
|
||||
// handle directives on element
|
||||
for (let i = 0; i < node.props.length; i++) {
|
||||
const dir = node.props[i]
|
||||
// do not process for v-for since it's special handled
|
||||
// do not process for v-on & v-for since they are special handled
|
||||
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
|
||||
const exp = dir.exp as SimpleExpressionNode | undefined
|
||||
const arg = dir.arg as SimpleExpressionNode | undefined
|
||||
if (exp) {
|
||||
// do not process exp if this is v-on:arg - we need special handling
|
||||
// for wrapping inline statements.
|
||||
if (exp && !(dir.name === 'on' && arg)) {
|
||||
dir.exp = processExpression(
|
||||
exp,
|
||||
context,
|
||||
|
@ -4,18 +4,23 @@ import {
|
||||
createSimpleExpression,
|
||||
ExpressionNode,
|
||||
NodeTypes,
|
||||
createCompoundExpression
|
||||
createCompoundExpression,
|
||||
SimpleExpressionNode
|
||||
} from '../ast'
|
||||
import { capitalize } from '@vue/shared'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import { processExpression } from './transformExpression'
|
||||
|
||||
const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/
|
||||
const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/
|
||||
|
||||
// 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.
|
||||
export const transformOn: DirectiveTransform = (dir, context) => {
|
||||
const { exp, loc, modifiers } = dir
|
||||
const { loc, modifiers } = dir
|
||||
const arg = dir.arg!
|
||||
if (!exp && !modifiers.length) {
|
||||
if (!dir.exp && !modifiers.length) {
|
||||
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
|
||||
}
|
||||
let eventName: ExpressionNode
|
||||
@ -37,10 +42,36 @@ export const transformOn: DirectiveTransform = (dir, context) => {
|
||||
}
|
||||
// TODO .once modifier handling since it is platform agnostic
|
||||
// other modifiers are handled in compiler-dom
|
||||
|
||||
// handler processing
|
||||
if (dir.exp) {
|
||||
// exp is guarunteed to be a simple expression here because v-on w/ arg is
|
||||
// skipped by transformExpression as a special case.
|
||||
let exp: ExpressionNode = dir.exp as SimpleExpressionNode
|
||||
const isInlineStatement = !(
|
||||
simplePathRE.test(exp.content) || 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`)
|
||||
}
|
||||
if (isInlineStatement) {
|
||||
// wrap inline statement in a function expression
|
||||
exp = createCompoundExpression([
|
||||
`$event => (`,
|
||||
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
|
||||
`)`
|
||||
])
|
||||
}
|
||||
dir.exp = exp
|
||||
}
|
||||
|
||||
return {
|
||||
props: createObjectProperty(
|
||||
eventName,
|
||||
exp || createSimpleExpression(`() => {}`, false, loc)
|
||||
dir.exp || createSimpleExpression(`() => {}`, false, loc)
|
||||
),
|
||||
needRuntime: false
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user