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', () => {
|
test('should error if no expression AND no modifier', () => {
|
||||||
const onError = jest.fn()
|
const onError = jest.fn()
|
||||||
parseWithVOn(`<div v-on:click />`, { onError })
|
parseWithVOn(`<div v-on:click />`, { onError })
|
||||||
|
@ -72,8 +72,8 @@ export interface TransformContext extends Required<TransformOptions> {
|
|||||||
replaceNode(node: TemplateChildNode): void
|
replaceNode(node: TemplateChildNode): void
|
||||||
removeNode(node?: TemplateChildNode): void
|
removeNode(node?: TemplateChildNode): void
|
||||||
onNodeRemoved: () => void
|
onNodeRemoved: () => void
|
||||||
addIdentifiers(exp: ExpressionNode): void
|
addIdentifiers(exp: ExpressionNode | string): void
|
||||||
removeIdentifiers(exp: ExpressionNode): void
|
removeIdentifiers(exp: ExpressionNode | string): void
|
||||||
hoist(exp: JSChildNode): SimpleExpressionNode
|
hoist(exp: JSChildNode): SimpleExpressionNode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +152,9 @@ function createTransformContext(
|
|||||||
addIdentifiers(exp) {
|
addIdentifiers(exp) {
|
||||||
// identifier tracking only happens in non-browser builds.
|
// identifier tracking only happens in non-browser builds.
|
||||||
if (!__BROWSER__) {
|
if (!__BROWSER__) {
|
||||||
if (exp.identifiers) {
|
if (isString(exp)) {
|
||||||
|
addId(exp)
|
||||||
|
} else if (exp.identifiers) {
|
||||||
exp.identifiers.forEach(addId)
|
exp.identifiers.forEach(addId)
|
||||||
} else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
} else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||||
addId(exp.content)
|
addId(exp.content)
|
||||||
@ -161,7 +163,9 @@ function createTransformContext(
|
|||||||
},
|
},
|
||||||
removeIdentifiers(exp) {
|
removeIdentifiers(exp) {
|
||||||
if (!__BROWSER__) {
|
if (!__BROWSER__) {
|
||||||
if (exp.identifiers) {
|
if (isString(exp)) {
|
||||||
|
removeId(exp)
|
||||||
|
} else if (exp.identifiers) {
|
||||||
exp.identifiers.forEach(removeId)
|
exp.identifiers.forEach(removeId)
|
||||||
} else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
} else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||||
removeId(exp.content)
|
removeId(exp.content)
|
||||||
|
@ -35,11 +35,13 @@ export const transformExpression: NodeTransform = (node, context) => {
|
|||||||
// handle directives on element
|
// handle directives on element
|
||||||
for (let i = 0; i < node.props.length; i++) {
|
for (let i = 0; i < node.props.length; i++) {
|
||||||
const dir = node.props[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') {
|
if (dir.type === NodeTypes.DIRECTIVE && dir.name !== 'for') {
|
||||||
const exp = dir.exp as SimpleExpressionNode | undefined
|
const exp = dir.exp as SimpleExpressionNode | undefined
|
||||||
const arg = dir.arg 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(
|
dir.exp = processExpression(
|
||||||
exp,
|
exp,
|
||||||
context,
|
context,
|
||||||
|
@ -4,18 +4,23 @@ import {
|
|||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
createCompoundExpression
|
createCompoundExpression,
|
||||||
|
SimpleExpressionNode
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { capitalize } from '@vue/shared'
|
import { capitalize } from '@vue/shared'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
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
|
// 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
|
// codegen for the entire props object. This transform here is only for v-on
|
||||||
// *with* args.
|
// *with* args.
|
||||||
export const transformOn: DirectiveTransform = (dir, context) => {
|
export const transformOn: DirectiveTransform = (dir, context) => {
|
||||||
const { exp, loc, modifiers } = dir
|
const { loc, modifiers } = dir
|
||||||
const arg = dir.arg!
|
const arg = dir.arg!
|
||||||
if (!exp && !modifiers.length) {
|
if (!dir.exp && !modifiers.length) {
|
||||||
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
|
context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
|
||||||
}
|
}
|
||||||
let eventName: ExpressionNode
|
let eventName: ExpressionNode
|
||||||
@ -37,10 +42,36 @@ export const transformOn: DirectiveTransform = (dir, context) => {
|
|||||||
}
|
}
|
||||||
// TODO .once modifier handling since it is platform agnostic
|
// TODO .once modifier handling since it is platform agnostic
|
||||||
// other modifiers are handled in compiler-dom
|
// 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 {
|
return {
|
||||||
props: createObjectProperty(
|
props: createObjectProperty(
|
||||||
eventName,
|
eventName,
|
||||||
exp || createSimpleExpression(`() => {}`, false, loc)
|
dir.exp || createSimpleExpression(`() => {}`, false, loc)
|
||||||
),
|
),
|
||||||
needRuntime: false
|
needRuntime: false
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user