diff --git a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap index 2bd766517..359a881dd 100644 --- a/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap @@ -5,7 +5,7 @@ exports[`compiler: integration tests function mode 1`] = ` return function render() { with (this) { - const { createVNode: _createVNode, toString: _toString, openBlock: _openBlock, applyDirectives: _applyDirectives, createBlock: _createBlock, Empty: _Empty, Fragment: _Fragment, renderList: _renderList } = _Vue + const { createVNode: _createVNode, toString: _toString, openBlock: _openBlock, createBlock: _createBlock, Empty: _Empty, Fragment: _Fragment, renderList: _renderList } = _Vue return _createVNode(\\"div\\", { id: \\"foo\\", @@ -13,16 +13,8 @@ return function render() { }, [ _toString(world.burn()), (_openBlock(), ok - ? _createBlock( - \\"div\\", - { key: 0 }, - \\"yes\\" - ) - : _createBlock( - _Fragment, - { key: 1 }, - \\"no\\" - )), + ? _createBlock(\\"div\\", { key: 0 }, \\"yes\\") + : _createBlock(_Fragment, { key: 1 }, \\"no\\")), _renderList(list, (value, index) => { return _createVNode(\\"div\\", null, [ _createVNode(\\"span\\", null, _toString(value + index)) @@ -34,7 +26,7 @@ return function render() { `; exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = ` -"const { createVNode, toString, openBlock, applyDirectives, createBlock, Empty, Fragment, renderList } = Vue +"const { createVNode, toString, openBlock, createBlock, Empty, Fragment, renderList } = Vue return function render() { const _ctx = this @@ -44,16 +36,8 @@ return function render() { }, [ toString(_ctx.world.burn()), (openBlock(), (_ctx.ok) - ? createBlock( - \\"div\\", - { key: 0 }, - \\"yes\\" - ) - : createBlock( - Fragment, - { key: 1 }, - \\"no\\" - )), + ? createBlock(\\"div\\", { key: 0 }, \\"yes\\") + : createBlock(Fragment, { key: 1 }, \\"no\\")), renderList(_ctx.list, (value, index) => { return createVNode(\\"div\\", null, [ createVNode(\\"span\\", null, toString(value + index)) @@ -64,7 +48,7 @@ return function render() { `; exports[`compiler: integration tests module mode 1`] = ` -"import { createVNode, toString, openBlock, applyDirectives, createBlock, Empty, Fragment, renderList } from \\"vue\\" +"import { createVNode, toString, openBlock, createBlock, Empty, Fragment, renderList } from \\"vue\\" export default function render() { const _ctx = this @@ -74,16 +58,8 @@ export default function render() { }, [ _toString(_ctx.world.burn()), (openBlock(), (_ctx.ok) - ? createBlock( - \\"div\\", - { key: 0 }, - \\"yes\\" - ) - : createBlock( - Fragment, - { key: 1 }, - \\"no\\" - )), + ? createBlock(\\"div\\", { key: 0 }, \\"yes\\") + : createBlock(Fragment, { key: 1 }, \\"no\\")), _renderList(_ctx.list, (value, index) => { return createVNode(\\"div\\", null, [ createVNode(\\"span\\", null, _toString(value + index)) diff --git a/packages/compiler-core/__tests__/testUtils.ts b/packages/compiler-core/__tests__/testUtils.ts new file mode 100644 index 000000000..8fd62227a --- /dev/null +++ b/packages/compiler-core/__tests__/testUtils.ts @@ -0,0 +1,28 @@ +import { NodeTypes } from '../src' + +const leadingBracketRE = /^\[/ +const bracketsRE = /^\[|\]$/g + +// Create a matcher for an object +// where non-static expressions should be wrapped in [] +// e.g. +// - createObjectMatcher({ 'foo': '[bar]' }) matches { foo: bar } +// - createObjectMatcher({ '[foo]': 'bar' }) matches { [foo]: "bar" } +export function createObjectMatcher(obj: any) { + return { + type: NodeTypes.JS_OBJECT_EXPRESSION, + properties: Object.keys(obj).map(key => ({ + type: NodeTypes.JS_PROPERTY, + key: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: key.replace(bracketsRE, ''), + isStatic: !leadingBracketRE.test(key) + }, + value: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: obj[key].replace(bracketsRE, ''), + isStatic: !leadingBracketRE.test(obj[key]) + } + })) + } +} diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap new file mode 100644 index 000000000..a6b34c8e5 --- /dev/null +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vIf.spec.ts.snap @@ -0,0 +1,79 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`compiler: transform v-if codegen basic v-if 1`] = ` +"const _Vue = Vue + +return function render() { + with (this) { + const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Empty: _Empty } = _Vue + + return (_openBlock(), ok + ? _createBlock(\\"div\\", { key: 0 }) + : _createBlock(_Empty)) + } +}" +`; + +exports[`compiler: transform v-if codegen template v-if 1`] = ` +"const _Vue = Vue + +return function render() { + with (this) { + const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Fragment: _Fragment, Empty: _Empty } = _Vue + + return (_openBlock(), ok + ? _createBlock(_Fragment, { key: 0 }, [ + _createVNode(\\"div\\"), + \\"hello\\", + _createVNode(\\"p\\") + ]) + : _createBlock(_Empty)) + } +}" +`; + +exports[`compiler: transform v-if codegen v-if + v-else 1`] = ` +"const _Vue = Vue + +return function render() { + with (this) { + const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Empty: _Empty } = _Vue + + return (_openBlock(), ok + ? _createBlock(\\"div\\", { key: 0 }) + : _createBlock(\\"p\\", { key: 1 })) + } +}" +`; + +exports[`compiler: transform v-if codegen v-if + v-else-if + v-else 1`] = ` +"const _Vue = Vue + +return function render() { + with (this) { + const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Empty: _Empty, Fragment: _Fragment } = _Vue + + return (_openBlock(), ok + ? _createBlock(\\"div\\", { key: 0 }) + : orNot + ? _createBlock(\\"p\\", { key: 1 }) + : _createBlock(_Fragment, { key: 2 }, \\"fine\\")) + } +}" +`; + +exports[`compiler: transform v-if codegen v-if + v-else-if 1`] = ` +"const _Vue = Vue + +return function render() { + with (this) { + const { openBlock: _openBlock, createVNode: _createVNode, createBlock: _createBlock, Empty: _Empty } = _Vue + + return (_openBlock(), ok + ? _createBlock(\\"div\\", { key: 0 }) + : orNot + ? _createBlock(\\"p\\", { key: 1 }) + : _createBlock(_Empty)) + } +}" +`; diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index 8f2cce9ee..2ca90b952 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -25,6 +25,7 @@ import { transformOn } from '../../src/transforms/vOn' import { transformStyle } from '../../src/transforms/transformStyle' import { transformBind } from '../../src/transforms/vBind' import { PatchFlags } from '@vue/shared' +import { createObjectMatcher } from '../testUtils' function parseWithElementTransform( template: string, @@ -47,25 +48,6 @@ function parseWithElementTransform( } } -function createStaticObjectMatcher(obj: any) { - return { - type: NodeTypes.JS_OBJECT_EXPRESSION, - properties: Object.keys(obj).map(key => ({ - type: NodeTypes.JS_PROPERTY, - key: { - type: NodeTypes.SIMPLE_EXPRESSION, - content: key, - isStatic: true - }, - value: { - type: NodeTypes.SIMPLE_EXPRESSION, - content: obj[key], - isStatic: true - } - })) - } -} - describe('compiler: element transform', () => { test('import + resovle component', () => { const { root } = parseWithElementTransform(``) @@ -78,7 +60,7 @@ describe('compiler: element transform', () => { expect(node.callee).toBe(`_${CREATE_VNODE}`) expect(node.arguments).toMatchObject([ `"div"`, - createStaticObjectMatcher({ + createObjectMatcher({ id: 'foo', class: 'bar' }) @@ -90,7 +72,7 @@ describe('compiler: element transform', () => { expect(node.callee).toBe(`_${CREATE_VNODE}`) expect(node.arguments).toMatchObject([ `"div"`, - createStaticObjectMatcher({ + createObjectMatcher({ id: 'foo' }), [ @@ -147,7 +129,7 @@ describe('compiler: element transform', () => { type: NodeTypes.JS_CALL_EXPRESSION, callee: `_${MERGE_PROPS}`, arguments: [ - createStaticObjectMatcher({ + createObjectMatcher({ id: 'foo' }), { @@ -172,7 +154,7 @@ describe('compiler: element transform', () => { type: NodeTypes.SIMPLE_EXPRESSION, content: `obj` }, - createStaticObjectMatcher({ + createObjectMatcher({ id: 'foo' }) ] @@ -189,14 +171,14 @@ describe('compiler: element transform', () => { type: NodeTypes.JS_CALL_EXPRESSION, callee: `_${MERGE_PROPS}`, arguments: [ - createStaticObjectMatcher({ + createObjectMatcher({ id: 'foo' }), { type: NodeTypes.SIMPLE_EXPRESSION, content: `obj` }, - createStaticObjectMatcher({ + createObjectMatcher({ class: 'bar' }) ] @@ -213,7 +195,7 @@ describe('compiler: element transform', () => { type: NodeTypes.JS_CALL_EXPRESSION, callee: `_${MERGE_PROPS}`, arguments: [ - createStaticObjectMatcher({ + createObjectMatcher({ id: 'foo' }), { @@ -226,7 +208,7 @@ describe('compiler: element transform', () => { } ] }, - createStaticObjectMatcher({ + createObjectMatcher({ class: 'bar' }) ] @@ -243,7 +225,7 @@ describe('compiler: element transform', () => { type: NodeTypes.JS_CALL_EXPRESSION, callee: `_${MERGE_PROPS}`, arguments: [ - createStaticObjectMatcher({ + createObjectMatcher({ id: 'foo' }), { diff --git a/packages/compiler-core/__tests__/transforms/vIf.spec.ts b/packages/compiler-core/__tests__/transforms/vIf.spec.ts index 5871476fd..283a07cd2 100644 --- a/packages/compiler-core/__tests__/transforms/vIf.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vIf.spec.ts @@ -8,31 +8,46 @@ import { ElementNode, TextNode, CommentNode, - SimpleExpressionNode + SimpleExpressionNode, + SequenceExpression, + ConditionalExpression, + CallExpression } from '../../src/ast' import { ErrorCodes } from '../../src/errors' -import { CompilerOptions } from '../../src' +import { CompilerOptions, generate } from '../../src' +import { + OPEN_BLOCK, + CREATE_BLOCK, + EMPTY, + FRAGMENT, + MERGE_PROPS, + APPLY_DIRECTIVES +} from '../../src/runtimeConstants' +import { createObjectMatcher } from '../testUtils' function parseWithIfTransform( template: string, options: CompilerOptions = {}, returnIndex: number = 0 -): IfNode { - const node = parse(template, options) - transform(node, { +) { + const ast = parse(template, options) + transform(ast, { nodeTransforms: [transformIf, transformElement], ...options }) if (!options.onError) { - expect(node.children.length).toBe(1) - expect(node.children[0].type).toBe(NodeTypes.IF) + expect(ast.children.length).toBe(1) + expect(ast.children[0].type).toBe(NodeTypes.IF) + } + return { + root: ast, + node: ast.children[returnIndex] as IfNode } - return node.children[returnIndex] as IfNode } describe('compiler: transform v-if', () => { test('basic v-if', () => { - const node = parseWithIfTransform(`
`) + const { node } = parseWithIfTransform(`
`) expect(node.type).toBe(NodeTypes.IF) expect(node.branches.length).toBe(1) expect((node.branches[0].condition as SimpleExpressionNode).content).toBe( @@ -44,7 +59,7 @@ describe('compiler: transform v-if', () => { }) test('template v-if', () => { - const node = parseWithIfTransform( + const { node } = parseWithIfTransform( `` ) expect(node.type).toBe(NodeTypes.IF) @@ -62,7 +77,7 @@ describe('compiler: transform v-if', () => { }) test('v-if + v-else', () => { - const node = parseWithIfTransform(`

`) + const { node } = parseWithIfTransform(`

`) expect(node.type).toBe(NodeTypes.IF) expect(node.branches.length).toBe(2) @@ -80,7 +95,9 @@ describe('compiler: transform v-if', () => { }) test('v-if + v-else-if', () => { - const node = parseWithIfTransform(`

`) + const { node } = parseWithIfTransform( + `

` + ) expect(node.type).toBe(NodeTypes.IF) expect(node.branches.length).toBe(2) @@ -98,7 +115,7 @@ describe('compiler: transform v-if', () => { }) test('v-if + v-else-if + v-else', () => { - const node = parseWithIfTransform( + const { node } = parseWithIfTransform( `

` ) expect(node.type).toBe(NodeTypes.IF) @@ -124,7 +141,7 @@ describe('compiler: transform v-if', () => { }) test('comment between branches', () => { - const node = parseWithIfTransform(` + const { node } = parseWithIfTransform(`

@@ -158,24 +175,20 @@ describe('compiler: transform v-if', () => { }) test('should prefix v-if condition', () => { - const node = parseWithIfTransform(`

`, { + const { node } = parseWithIfTransform(`
`, { prefixIdentifiers: true - }) as IfNode + }) expect(node.branches[0].condition).toMatchObject({ type: NodeTypes.SIMPLE_EXPRESSION, content: `_ctx.ok` }) }) - describe('codegen', () => { - // TODO - }) - describe('errors', () => { test('error on v-else missing adjacent v-if', () => { const onError = jest.fn() - const node1 = parseWithIfTransform(`
`, { onError }) + const { node: node1 } = parseWithIfTransform(`
`, { onError }) expect(onError.mock.calls[0]).toMatchObject([ { code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, @@ -183,7 +196,11 @@ describe('compiler: transform v-if', () => { } ]) - const node2 = parseWithIfTransform(`
`, { onError }, 1) + const { node: node2 } = parseWithIfTransform( + `
`, + { onError }, + 1 + ) expect(onError.mock.calls[1]).toMatchObject([ { code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, @@ -191,7 +208,7 @@ describe('compiler: transform v-if', () => { } ]) - const node3 = parseWithIfTransform( + const { node: node3 } = parseWithIfTransform( `
foo
`, { onError }, 2 @@ -207,7 +224,9 @@ describe('compiler: transform v-if', () => { test('error on v-else-if missing adjacent v-if', () => { const onError = jest.fn() - const node1 = parseWithIfTransform(`
`, { onError }) + const { node: node1 } = parseWithIfTransform(`
`, { + onError + }) expect(onError.mock.calls[0]).toMatchObject([ { code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, @@ -215,7 +234,7 @@ describe('compiler: transform v-if', () => { } ]) - const node2 = parseWithIfTransform( + const { node: node2 } = parseWithIfTransform( `
`, { onError }, 1 @@ -227,7 +246,7 @@ describe('compiler: transform v-if', () => { } ]) - const node3 = parseWithIfTransform( + const { node: node3 } = parseWithIfTransform( `
foo
`, { onError }, 2 @@ -240,4 +259,214 @@ describe('compiler: transform v-if', () => { ]) }) }) + + describe('codegen', () => { + function assertSharedCodegen(node: SequenceExpression, depth: number = 0) { + expect(node).toMatchObject({ + type: NodeTypes.JS_SEQUENCE_EXPRESSION, + expressions: [ + { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: `_${OPEN_BLOCK}`, + arguments: [] + }, + { + type: NodeTypes.JS_CONDITIONAL_EXPRESSION, + test: { + content: `ok` + }, + consequent: { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: `_${CREATE_BLOCK}` + }, + alternate: + depth < 1 + ? { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: `_${CREATE_BLOCK}` + } + : { + type: NodeTypes.JS_CONDITIONAL_EXPRESSION, + test: { + content: `orNot` + }, + consequent: { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: `_${CREATE_BLOCK}` + }, + alternate: { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: `_${CREATE_BLOCK}` + } + } + } + ] + }) + } + + test('basic v-if', () => { + const { + root, + node: { codegenNode } + } = parseWithIfTransform(`
`) + assertSharedCodegen(codegenNode) + const branch1 = (codegenNode.expressions[1] as ConditionalExpression) + .consequent as CallExpression + expect(branch1.arguments).toMatchObject([`"div"`, `{ key: 0 }`]) + const branch2 = (codegenNode.expressions[1] as ConditionalExpression) + .alternate as CallExpression + expect(branch2.arguments).toMatchObject([`_${EMPTY}`]) + expect(generate(root).code).toMatchSnapshot() + }) + + test('template v-if', () => { + const { + root, + node: { codegenNode } + } = parseWithIfTransform(``) + assertSharedCodegen(codegenNode) + const branch1 = (codegenNode.expressions[1] as ConditionalExpression) + .consequent as CallExpression + expect(branch1.arguments).toMatchObject([ + `_${FRAGMENT}`, + `{ key: 0 }`, + [ + { type: NodeTypes.ELEMENT, tag: 'div' }, + { type: NodeTypes.TEXT, content: `hello` }, + { type: NodeTypes.ELEMENT, tag: 'p' } + ] + ]) + const branch2 = (codegenNode.expressions[1] as ConditionalExpression) + .alternate as CallExpression + expect(branch2.arguments).toMatchObject([`_${EMPTY}`]) + expect(generate(root).code).toMatchSnapshot() + }) + + test('v-if + v-else', () => { + const { + root, + node: { codegenNode } + } = parseWithIfTransform(`

`) + assertSharedCodegen(codegenNode) + const branch1 = (codegenNode.expressions[1] as ConditionalExpression) + .consequent as CallExpression + expect(branch1.arguments).toMatchObject([`"div"`, `{ key: 0 }`]) + const branch2 = (codegenNode.expressions[1] as ConditionalExpression) + .alternate as CallExpression + expect(branch2.arguments).toMatchObject([`"p"`, `{ key: 1 }`]) + expect(generate(root).code).toMatchSnapshot() + }) + + test('v-if + v-else-if', () => { + const { + root, + node: { codegenNode } + } = parseWithIfTransform(`

`) + assertSharedCodegen(codegenNode, 1) + const branch1 = (codegenNode.expressions[1] as ConditionalExpression) + .consequent as CallExpression + expect(branch1.arguments).toMatchObject([`"div"`, `{ key: 0 }`]) + const branch2 = (codegenNode.expressions[1] as ConditionalExpression) + .alternate as ConditionalExpression + expect((branch2.consequent as CallExpression).arguments).toMatchObject([ + `"p"`, + `{ key: 1 }` + ]) + expect(generate(root).code).toMatchSnapshot() + }) + + test('v-if + v-else-if + v-else', () => { + const { + root, + node: { codegenNode } + } = parseWithIfTransform( + `

` + ) + assertSharedCodegen(codegenNode, 1) + const branch1 = (codegenNode.expressions[1] as ConditionalExpression) + .consequent as CallExpression + expect(branch1.arguments).toMatchObject([`"div"`, `{ key: 0 }`]) + const branch2 = (codegenNode.expressions[1] as ConditionalExpression) + .alternate as ConditionalExpression + expect((branch2.consequent as CallExpression).arguments).toMatchObject([ + `"p"`, + `{ key: 1 }` + ]) + expect((branch2.alternate as CallExpression).arguments).toMatchObject([ + `_${FRAGMENT}`, + `{ key: 2 }`, + [ + { + type: NodeTypes.TEXT, + content: `fine` + } + ] + ]) + expect(generate(root).code).toMatchSnapshot() + }) + + test('key injection (only v-bind)', () => { + const { + node: { codegenNode } + } = parseWithIfTransform(`

`) + const branch1 = (codegenNode.expressions[1] as ConditionalExpression) + .consequent as CallExpression + expect(branch1.arguments[1]).toMatchObject({ + type: NodeTypes.JS_CALL_EXPRESSION, + callee: `_${MERGE_PROPS}`, + arguments: [`{ key: 0 }`, { content: `obj` }] + }) + }) + + test('key injection (before v-bind)', () => { + const { + node: { codegenNode } + } = parseWithIfTransform(`
`) + const branch1 = (codegenNode.expressions[1] as ConditionalExpression) + .consequent as CallExpression + expect(branch1.arguments[1]).toMatchObject({ + type: NodeTypes.JS_CALL_EXPRESSION, + callee: `_${MERGE_PROPS}`, + arguments: [ + createObjectMatcher({ + key: '[0]', + id: 'foo' + }), + { content: `obj` } + ] + }) + }) + + test('key injection (after v-bind)', () => { + const { + node: { codegenNode } + } = parseWithIfTransform(`
`) + const branch1 = (codegenNode.expressions[1] as ConditionalExpression) + .consequent as CallExpression + expect(branch1.arguments[1]).toMatchObject({ + type: NodeTypes.JS_CALL_EXPRESSION, + callee: `_${MERGE_PROPS}`, + arguments: [ + `{ key: 0 }`, + { content: `obj` }, + createObjectMatcher({ + id: 'foo' + }) + ] + }) + }) + + test('key injection (w/ custom directive)', () => { + const { + node: { codegenNode } + } = parseWithIfTransform(`
`) + const branch1 = (codegenNode.expressions[1] as ConditionalExpression) + .consequent as CallExpression + expect(branch1.callee).toBe(`_${APPLY_DIRECTIVES}`) + const realBranch = branch1.arguments[0] as CallExpression + expect(realBranch.arguments[1]).toBe(`{ key: 0 }`) + }) + + test('with comments', () => {}) + }) }) diff --git a/packages/compiler-core/src/codegen.ts b/packages/compiler-core/src/codegen.ts index 5f80c074a..0875573e0 100644 --- a/packages/compiler-core/src/codegen.ts +++ b/packages/compiler-core/src/codegen.ts @@ -300,7 +300,7 @@ function genNodeListAsArray( context: CodegenContext ) { const multilines = - nodes.length > 1 || + nodes.length > 3 || ((!__BROWSER__ || __DEV__) && nodes.some( n => @@ -513,7 +513,7 @@ function genFor(node: ForNode, context: CodegenContext) { function genCallExpression( node: CallExpression, context: CodegenContext, - multilines = node.arguments.length > 2 + multilines = false ) { context.push(node.callee + `(`, node, true) multilines && context.indent() diff --git a/packages/compiler-core/src/transforms/vIf.ts b/packages/compiler-core/src/transforms/vIf.ts index 9c415fd6c..456cda0da 100644 --- a/packages/compiler-core/src/transforms/vIf.ts +++ b/packages/compiler-core/src/transforms/vIf.ts @@ -19,7 +19,8 @@ import { JSChildNode, ObjectExpression, createObjectProperty, - Property + Property, + ExpressionNode } from '../ast' import { createCompilerError, ErrorCodes } from '../errors' import { processExpression } from './transformExpression' @@ -28,7 +29,8 @@ import { CREATE_BLOCK, EMPTY, FRAGMENT, - APPLY_DIRECTIVES + APPLY_DIRECTIVES, + MERGE_PROPS } from '../runtimeConstants' import { isString } from '@vue/shared' @@ -51,14 +53,14 @@ export const transformIf = createStructuralDirectiveTransform( } if (dir.name === 'if') { + const branch = createIfBranch(node, dir) const codegenNode = createSequenceExpression([ createCallExpression(context.helper(OPEN_BLOCK)) ]) - context.replaceNode({ type: NodeTypes.IF, loc: node.loc, - branches: [createIfBranch(node, dir)], + branches: [branch], codegenNode }) @@ -66,7 +68,7 @@ export const transformIf = createStructuralDirectiveTransform( // transformed. return () => { codegenNode.expressions.push( - createCodegenNodeForBranch(node, dir, 0, context) + createCodegenNodeForBranch(node, branch, 0, context) ) } } else { @@ -104,7 +106,7 @@ export const transformIf = createStructuralDirectiveTransform( } else { parentCondition.alternate = createCodegenNodeForBranch( node, - dir, + branch, sibling.branches.length - 1, context ) @@ -138,25 +140,26 @@ function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode { function createCodegenNodeForBranch( node: ElementNode, - dir: DirectiveNode, + branch: IfBranchNode, index: number, context: TransformContext ): ConditionalExpression | CallExpression { - if (dir.exp) { + if (branch.condition) { return createConditionalExpression( - dir.exp, - createChildrenCodegenNode(node, index, context), + branch.condition, + createChildrenCodegenNode(node, branch, index, context), createCallExpression(context.helper(CREATE_BLOCK), [ context.helper(EMPTY) ]) ) } else { - return createChildrenCodegenNode(node, index, context) + return createChildrenCodegenNode(node, branch, index, context) } } function createChildrenCodegenNode( node: ElementNode, + branch: IfBranchNode, index: number, { helper }: TransformContext ): CallExpression { @@ -166,11 +169,11 @@ function createChildrenCodegenNode( return createCallExpression(helper(CREATE_BLOCK), [ helper(FRAGMENT), keyExp, - node.children + branch.children ]) } else { let childCodegen = node.codegenNode! - if (childCodegen.callee === helper(APPLY_DIRECTIVES)) { + if (childCodegen.callee.includes(APPLY_DIRECTIVES)) { childCodegen = childCodegen.arguments[0] as CallExpression } // change child to a block @@ -181,7 +184,10 @@ function createChildrenCodegenNode( childCodegen.arguments[1] = keyExp } else { // inject branch key if not already have a key - const props = existingProps as CallExpression | ObjectExpression + const props = existingProps as + | CallExpression + | ObjectExpression + | ExpressionNode if (props.type === NodeTypes.JS_CALL_EXPRESSION) { // merged props... add ours // only inject key to object literal if it's the first argument so that @@ -192,11 +198,17 @@ function createChildrenCodegenNode( } else { props.arguments.unshift(keyExp) } - } else { + } else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) { props.properties.unshift(createKeyProperty(index)) + } else { + // single v-bind with expression + childCodegen.arguments[1] = createCallExpression(helper(MERGE_PROPS), [ + keyExp, + props + ]) } } - return childCodegen + return node.codegenNode! } }