test: tests for v-on transform

This commit is contained in:
Evan You 2019-09-24 22:39:20 -04:00
parent 597ada36ed
commit 84909648e7
8 changed files with 182 additions and 26 deletions

View File

@ -76,9 +76,9 @@ return function render() {
exports[`compiler: codegen ifNode 1`] = ` exports[`compiler: codegen ifNode 1`] = `
"return function render() { "return function render() {
with (this) { with (this) {
return (foo) return foo
? \\"foo\\" ? \\"foo\\"
: (bar) : (a + b)
? toString(bye) ? toString(bye)
: createVNode(Comment, 0, \\"foo\\") : createVNode(Comment, 0, \\"foo\\")
} }
@ -88,9 +88,9 @@ exports[`compiler: codegen ifNode 1`] = `
exports[`compiler: codegen ifNode with no v-else 1`] = ` exports[`compiler: codegen ifNode with no v-else 1`] = `
"return function render() { "return function render() {
with (this) { with (this) {
return (foo) return foo
? \\"foo\\" ? \\"foo\\"
: (bar) : (a + b)
? toString(bye) ? toString(bye)
: null : null
} }

View File

@ -191,7 +191,7 @@ describe('compiler: codegen', () => {
}, },
{ {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
condition: createExpression('bar', false, mockLoc), condition: createExpression('a + b', false, mockLoc),
loc: mockLoc, loc: mockLoc,
isRoot: true, isRoot: true,
children: [createExpression(`bye`, false, mockLoc, true)] children: [createExpression(`bye`, false, mockLoc, true)]
@ -215,9 +215,9 @@ describe('compiler: codegen', () => {
}) })
) )
expect(code).toMatch(` expect(code).toMatch(`
return (foo) return foo
? "foo" ? "foo"
: (bar) : (a + b)
? ${TO_STRING}(bye) ? ${TO_STRING}(bye)
: ${CREATE_VNODE}(${COMMENT}, 0, "foo")`) : ${CREATE_VNODE}(${COMMENT}, 0, "foo")`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
@ -248,7 +248,7 @@ describe('compiler: codegen', () => {
}, },
{ {
type: NodeTypes.IF_BRANCH, type: NodeTypes.IF_BRANCH,
condition: createExpression('bar', false, mockLoc), condition: createExpression('a + b', false, mockLoc),
loc: mockLoc, loc: mockLoc,
isRoot: true, isRoot: true,
children: [createExpression(`bye`, false, mockLoc, true)] children: [createExpression(`bye`, false, mockLoc, true)]
@ -259,9 +259,9 @@ describe('compiler: codegen', () => {
}) })
) )
expect(code).toMatch(` expect(code).toMatch(`
return (foo) return foo
? "foo" ? "foo"
: (bar) : (a + b)
? ${TO_STRING}(bye) ? ${TO_STRING}(bye)
: null`) : null`)
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()

View File

@ -1 +1,125 @@
test.todo('v-on') import {
parse,
transform,
ElementNode,
ObjectExpression,
CompilerOptions,
ErrorCodes
} from '../../src'
import { transformOn } from '../../src/transforms/vOn'
import { transformElement } from '../../src/transforms/transformElement'
import { transformExpression } from '../../src/transforms/transformExpression'
function parseWithVOn(
template: string,
options: CompilerOptions = {}
): ElementNode {
const ast = parse(template)
transform(ast, {
nodeTransforms: [transformExpression, transformElement],
directiveTransforms: {
on: transformOn
},
...options
})
return ast.children[0] as ElementNode
}
describe('compiler: transform v-bind', () => {
test('basic', () => {
const node = parseWithVOn(`<div v-on:click="onClick"/>`)
const props = node.codegenNode!.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `onClick`,
isStatic: true,
loc: {
start: {
line: 1,
column: 11
},
end: {
line: 1,
column: 16
}
}
},
value: {
content: `onClick`,
isStatic: false,
loc: {
start: {
line: 1,
column: 17
},
end: {
line: 1,
column: 26
}
}
},
loc: {
start: {
line: 1,
column: 6
},
end: {
line: 1,
column: 26
}
}
})
})
test('dynamic arg', () => {
const node = parseWithVOn(`<div v-on:[event]="handler"/>`)
const props = node.codegenNode!.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
content: `"on" + event`,
isStatic: false
},
value: {
content: `handler`,
isStatic: false
}
})
})
test('dynamic arg with prefixing', () => {
const node = parseWithVOn(`<div v-on:[event]="handler"/>`, {
prefixIdentifiers: true
})
const props = node.codegenNode!.arguments[1] as ObjectExpression
expect(props.properties[0]).toMatchObject({
key: {
isStatic: false,
children: [`"on" + `, `_ctx.`, { content: `event` }]
},
value: {
content: `handler`,
isStatic: false
}
})
})
test('should error if no expression', () => {
const onError = jest.fn()
parseWithVOn(`<div v-on />`, { onError })
expect(onError.mock.calls[0][0]).toMatchObject({
code: ErrorCodes.X_V_ON_NO_EXPRESSION,
loc: {
start: {
line: 1,
column: 6
},
end: {
line: 1,
column: 10
}
}
})
})
test.todo('.once modifier')
})

View File

@ -15,7 +15,11 @@ import {
IfBranchNode IfBranchNode
} from './ast' } from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map' import { SourceMapGenerator, RawSourceMap } from 'source-map'
import { advancePositionWithMutation, assert } from './utils' import {
advancePositionWithMutation,
assert,
isSimpleIdentifier
} from './utils'
import { isString, isArray } from '@vue/shared' import { isString, isArray } from '@vue/shared'
import { import {
RENDER_LIST, RENDER_LIST,
@ -325,7 +329,7 @@ function genExpressionAsPropertyKey(
push(`]`) push(`]`)
} else if (isStatic) { } else if (isStatic) {
// only quote keys if necessary // only quote keys if necessary
const text = /^\d|[^\w]/.test(content) ? JSON.stringify(content) : content const text = isSimpleIdentifier(content) ? content : JSON.stringify(content)
push(text, node) push(text, node)
} else { } else {
push(`[${content}]`, node) push(`[${content}]`, node)
@ -366,9 +370,10 @@ function genIfBranch(
if (condition) { if (condition) {
// v-if or v-else-if // v-if or v-else-if
const { push, indent, deindent, newline } = context const { push, indent, deindent, newline } = context
push(`(`) const needsQuote = !isSimpleIdentifier(condition.content)
needsQuote && push(`(`)
genExpression(condition, context) genExpression(condition, context)
push(`)`) needsQuote && push(`)`)
indent() indent()
context.indentLevel++ context.indentLevel++
push(`? `) push(`? `)

View File

@ -17,22 +17,21 @@ export function compile(
template: string | RootNode, template: string | RootNode,
options: CompilerOptions = {} options: CompilerOptions = {}
): CodegenResult { ): CodegenResult {
const ast = isString(template) ? parse(template, options) : template if (__BROWSER__ && options.prefixIdentifiers) {
const prefixIdentifiers = !__BROWSER__ && options.prefixIdentifiers === true
if (__BROWSER__ && options.prefixIdentifiers === false) {
;(options.onError || defaultOnError)( ;(options.onError || defaultOnError)(
createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED) createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED)
) )
} }
const ast = isString(template) ? parse(template, options) : template
transform(ast, { transform(ast, {
...options, ...options,
prefixIdentifiers, prefixIdentifiers: !__BROWSER__ && options.prefixIdentifiers === true,
nodeTransforms: [ nodeTransforms: [
transformIf, transformIf,
transformFor, transformFor,
...(prefixIdentifiers ? [transformExpression] : []), transformExpression,
transformElement, transformElement,
...(options.nodeTransforms || []) // user transforms ...(options.nodeTransforms || []) // user transforms
], ],

View File

@ -56,6 +56,9 @@ export function processExpression(
node: ExpressionNode, node: ExpressionNode,
context: TransformContext context: TransformContext
) { ) {
if (!context.prefixIdentifiers) {
return
}
// lazy require dependencies so that they don't end up in rollup's dep graph // lazy require dependencies so that they don't end up in rollup's dep graph
// and thus can be tree-shaken in browser builds. // and thus can be tree-shaken in browser builds.
const parseScript = const parseScript =
@ -155,6 +158,9 @@ export function processExpression(
}) })
if (children.length) { if (children.length) {
// mark it empty so that it's more noticeable in case another transform or
// codegen forget to handle `.children` first.
node.content = __DEV__ ? `[[REMOVED]]` : ``
node.children = children node.children = children
} }
} }

View File

@ -1,14 +1,33 @@
import { DirectiveTransform } from '../transform' import { DirectiveTransform } from '../transform'
import { createObjectProperty, createExpression } from '../ast' import { createObjectProperty, createExpression, ExpressionNode } from '../ast'
import { capitalize } from '@vue/shared' import { capitalize } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
import { isSimpleIdentifier } from '../utils'
// 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 = ({ arg, exp, loc }) => { export const transformOn: DirectiveTransform = ({ arg, exp, loc }, context) => {
const eventName = arg!.isStatic if (!exp) {
? createExpression(`on${capitalize(arg!.content)}`, true, arg!.loc) context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc))
: createExpression(`'on' + (${arg!.content})`, false, arg!.loc) }
const { content, children, isStatic, loc: argLoc } = arg!
let eventName: ExpressionNode
if (isStatic) {
// static arg
eventName = createExpression(`on${capitalize(content)}`, true, argLoc)
} else if (!children) {
// dynamic arg with no rewrite
eventName = createExpression(
`"on" + ${isSimpleIdentifier(content) ? content : `(${content})`}`,
false,
argLoc
)
} else {
// dynamic arg with ctx prefixing
eventName = arg!
children.unshift(`"on" + `)
}
// 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
return { return {

View File

@ -1,5 +1,8 @@
import { SourceLocation, Position } from './ast' import { SourceLocation, Position } from './ast'
export const isSimpleIdentifier = (name: string): boolean =>
!/^\d|[^\w]/.test(name)
export function getInnerRange( export function getInnerRange(
loc: SourceLocation, loc: SourceLocation,
offset: number, offset: number,