exports[`compiler: codegen ifNode 1`] = `
"return function render() {
with (this) {
- return (foo)
+ return foo
? \\"foo\\"
- : (bar)
+ : (a + b)
? toString(bye)
: createVNode(Comment, 0, \\"foo\\")
}
exports[`compiler: codegen ifNode with no v-else 1`] = `
"return function render() {
with (this) {
- return (foo)
+ return foo
? \\"foo\\"
- : (bar)
+ : (a + b)
? toString(bye)
: null
}
},
{
type: NodeTypes.IF_BRANCH,
- condition: createExpression('bar', false, mockLoc),
+ condition: createExpression('a + b', false, mockLoc),
loc: mockLoc,
isRoot: true,
children: [createExpression(`bye`, false, mockLoc, true)]
})
)
expect(code).toMatch(`
- return (foo)
+ return foo
? "foo"
- : (bar)
+ : (a + b)
? ${TO_STRING}(bye)
: ${CREATE_VNODE}(${COMMENT}, 0, "foo")`)
expect(code).toMatchSnapshot()
},
{
type: NodeTypes.IF_BRANCH,
- condition: createExpression('bar', false, mockLoc),
+ condition: createExpression('a + b', false, mockLoc),
loc: mockLoc,
isRoot: true,
children: [createExpression(`bye`, false, mockLoc, true)]
})
)
expect(code).toMatch(`
- return (foo)
+ return foo
? "foo"
- : (bar)
+ : (a + b)
? ${TO_STRING}(bye)
: null`)
expect(code).toMatchSnapshot()
-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')
+})
IfBranchNode
} from './ast'
import { SourceMapGenerator, RawSourceMap } from 'source-map'
-import { advancePositionWithMutation, assert } from './utils'
+import {
+ advancePositionWithMutation,
+ assert,
+ isSimpleIdentifier
+} from './utils'
import { isString, isArray } from '@vue/shared'
import {
RENDER_LIST,
push(`]`)
} else if (isStatic) {
// 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)
} else {
push(`[${content}]`, node)
if (condition) {
// v-if or v-else-if
const { push, indent, deindent, newline } = context
- push(`(`)
+ const needsQuote = !isSimpleIdentifier(condition.content)
+ needsQuote && push(`(`)
genExpression(condition, context)
- push(`)`)
+ needsQuote && push(`)`)
indent()
context.indentLevel++
push(`? `)
template: string | RootNode,
options: CompilerOptions = {}
): CodegenResult {
- const ast = isString(template) ? parse(template, options) : template
- const prefixIdentifiers = !__BROWSER__ && options.prefixIdentifiers === true
-
- if (__BROWSER__ && options.prefixIdentifiers === false) {
+ if (__BROWSER__ && options.prefixIdentifiers) {
;(options.onError || defaultOnError)(
createCompilerError(ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED)
)
}
+ const ast = isString(template) ? parse(template, options) : template
+
transform(ast, {
...options,
- prefixIdentifiers,
+ prefixIdentifiers: !__BROWSER__ && options.prefixIdentifiers === true,
nodeTransforms: [
transformIf,
transformFor,
- ...(prefixIdentifiers ? [transformExpression] : []),
+ transformExpression,
transformElement,
...(options.nodeTransforms || []) // user transforms
],
node: ExpressionNode,
context: TransformContext
) {
+ if (!context.prefixIdentifiers) {
+ return
+ }
// lazy require dependencies so that they don't end up in rollup's dep graph
// and thus can be tree-shaken in browser builds.
const parseScript =
})
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
}
}
import { DirectiveTransform } from '../transform'
-import { createObjectProperty, createExpression } from '../ast'
+import { createObjectProperty, createExpression, ExpressionNode } from '../ast'
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
// codegen for the entire props object. This transform here is only for v-on
// *with* args.
-export const transformOn: DirectiveTransform = ({ arg, exp, loc }) => {
- const eventName = arg!.isStatic
- ? createExpression(`on${capitalize(arg!.content)}`, true, arg!.loc)
- : createExpression(`'on' + (${arg!.content})`, false, arg!.loc)
+export const transformOn: DirectiveTransform = ({ arg, exp, loc }, context) => {
+ if (!exp) {
+ context.onError(createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, 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
// other modifiers are handled in compiler-dom
return {
import { SourceLocation, Position } from './ast'
+export const isSimpleIdentifier = (name: string): boolean =>
+ !/^\d|[^\w]/.test(name)
+
export function getInnerRange(
loc: SourceLocation,
offset: number,