test(compiler): tests for vIf codegen w/ blocks optimization

This commit is contained in:
Evan You 2019-10-01 15:04:58 -04:00
parent 5de744d4e1
commit ed111cd37b
7 changed files with 411 additions and 105 deletions

View File

@ -5,7 +5,7 @@ exports[`compiler: integration tests function mode 1`] = `
return function render() { return function render() {
with (this) { 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\\", { return _createVNode(\\"div\\", {
id: \\"foo\\", id: \\"foo\\",
@ -13,16 +13,8 @@ return function render() {
}, [ }, [
_toString(world.burn()), _toString(world.burn()),
(_openBlock(), ok (_openBlock(), ok
? _createBlock( ? _createBlock(\\"div\\", { key: 0 }, \\"yes\\")
\\"div\\", : _createBlock(_Fragment, { key: 1 }, \\"no\\")),
{ key: 0 },
\\"yes\\"
)
: _createBlock(
_Fragment,
{ key: 1 },
\\"no\\"
)),
_renderList(list, (value, index) => { _renderList(list, (value, index) => {
return _createVNode(\\"div\\", null, [ return _createVNode(\\"div\\", null, [
_createVNode(\\"span\\", null, _toString(value + index)) _createVNode(\\"span\\", null, _toString(value + index))
@ -34,7 +26,7 @@ return function render() {
`; `;
exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = ` 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() { return function render() {
const _ctx = this const _ctx = this
@ -44,16 +36,8 @@ return function render() {
}, [ }, [
toString(_ctx.world.burn()), toString(_ctx.world.burn()),
(openBlock(), (_ctx.ok) (openBlock(), (_ctx.ok)
? createBlock( ? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
\\"div\\", : createBlock(Fragment, { key: 1 }, \\"no\\")),
{ key: 0 },
\\"yes\\"
)
: createBlock(
Fragment,
{ key: 1 },
\\"no\\"
)),
renderList(_ctx.list, (value, index) => { renderList(_ctx.list, (value, index) => {
return createVNode(\\"div\\", null, [ return createVNode(\\"div\\", null, [
createVNode(\\"span\\", null, toString(value + index)) createVNode(\\"span\\", null, toString(value + index))
@ -64,7 +48,7 @@ return function render() {
`; `;
exports[`compiler: integration tests module mode 1`] = ` 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() { export default function render() {
const _ctx = this const _ctx = this
@ -74,16 +58,8 @@ export default function render() {
}, [ }, [
_toString(_ctx.world.burn()), _toString(_ctx.world.burn()),
(openBlock(), (_ctx.ok) (openBlock(), (_ctx.ok)
? createBlock( ? createBlock(\\"div\\", { key: 0 }, \\"yes\\")
\\"div\\", : createBlock(Fragment, { key: 1 }, \\"no\\")),
{ key: 0 },
\\"yes\\"
)
: createBlock(
Fragment,
{ key: 1 },
\\"no\\"
)),
_renderList(_ctx.list, (value, index) => { _renderList(_ctx.list, (value, index) => {
return createVNode(\\"div\\", null, [ return createVNode(\\"div\\", null, [
createVNode(\\"span\\", null, _toString(value + index)) createVNode(\\"span\\", null, _toString(value + index))

View File

@ -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])
}
}))
}
}

View File

@ -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))
}
}"
`;

View File

@ -25,6 +25,7 @@ import { transformOn } from '../../src/transforms/vOn'
import { transformStyle } from '../../src/transforms/transformStyle' import { transformStyle } from '../../src/transforms/transformStyle'
import { transformBind } from '../../src/transforms/vBind' import { transformBind } from '../../src/transforms/vBind'
import { PatchFlags } from '@vue/shared' import { PatchFlags } from '@vue/shared'
import { createObjectMatcher } from '../testUtils'
function parseWithElementTransform( function parseWithElementTransform(
template: string, 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', () => { describe('compiler: element transform', () => {
test('import + resovle component', () => { test('import + resovle component', () => {
const { root } = parseWithElementTransform(`<Foo/>`) const { root } = parseWithElementTransform(`<Foo/>`)
@ -78,7 +60,7 @@ describe('compiler: element transform', () => {
expect(node.callee).toBe(`_${CREATE_VNODE}`) expect(node.callee).toBe(`_${CREATE_VNODE}`)
expect(node.arguments).toMatchObject([ expect(node.arguments).toMatchObject([
`"div"`, `"div"`,
createStaticObjectMatcher({ createObjectMatcher({
id: 'foo', id: 'foo',
class: 'bar' class: 'bar'
}) })
@ -90,7 +72,7 @@ describe('compiler: element transform', () => {
expect(node.callee).toBe(`_${CREATE_VNODE}`) expect(node.callee).toBe(`_${CREATE_VNODE}`)
expect(node.arguments).toMatchObject([ expect(node.arguments).toMatchObject([
`"div"`, `"div"`,
createStaticObjectMatcher({ createObjectMatcher({
id: 'foo' id: 'foo'
}), }),
[ [
@ -147,7 +129,7 @@ describe('compiler: element transform', () => {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${MERGE_PROPS}`, callee: `_${MERGE_PROPS}`,
arguments: [ arguments: [
createStaticObjectMatcher({ createObjectMatcher({
id: 'foo' id: 'foo'
}), }),
{ {
@ -172,7 +154,7 @@ describe('compiler: element transform', () => {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj` content: `obj`
}, },
createStaticObjectMatcher({ createObjectMatcher({
id: 'foo' id: 'foo'
}) })
] ]
@ -189,14 +171,14 @@ describe('compiler: element transform', () => {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${MERGE_PROPS}`, callee: `_${MERGE_PROPS}`,
arguments: [ arguments: [
createStaticObjectMatcher({ createObjectMatcher({
id: 'foo' id: 'foo'
}), }),
{ {
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `obj` content: `obj`
}, },
createStaticObjectMatcher({ createObjectMatcher({
class: 'bar' class: 'bar'
}) })
] ]
@ -213,7 +195,7 @@ describe('compiler: element transform', () => {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${MERGE_PROPS}`, callee: `_${MERGE_PROPS}`,
arguments: [ arguments: [
createStaticObjectMatcher({ createObjectMatcher({
id: 'foo' id: 'foo'
}), }),
{ {
@ -226,7 +208,7 @@ describe('compiler: element transform', () => {
} }
] ]
}, },
createStaticObjectMatcher({ createObjectMatcher({
class: 'bar' class: 'bar'
}) })
] ]
@ -243,7 +225,7 @@ describe('compiler: element transform', () => {
type: NodeTypes.JS_CALL_EXPRESSION, type: NodeTypes.JS_CALL_EXPRESSION,
callee: `_${MERGE_PROPS}`, callee: `_${MERGE_PROPS}`,
arguments: [ arguments: [
createStaticObjectMatcher({ createObjectMatcher({
id: 'foo' id: 'foo'
}), }),
{ {

View File

@ -8,31 +8,46 @@ import {
ElementNode, ElementNode,
TextNode, TextNode,
CommentNode, CommentNode,
SimpleExpressionNode SimpleExpressionNode,
SequenceExpression,
ConditionalExpression,
CallExpression
} from '../../src/ast' } from '../../src/ast'
import { ErrorCodes } from '../../src/errors' 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( function parseWithIfTransform(
template: string, template: string,
options: CompilerOptions = {}, options: CompilerOptions = {},
returnIndex: number = 0 returnIndex: number = 0
): IfNode { ) {
const node = parse(template, options) const ast = parse(template, options)
transform(node, { transform(ast, {
nodeTransforms: [transformIf, transformElement], nodeTransforms: [transformIf, transformElement],
...options ...options
}) })
if (!options.onError) { if (!options.onError) {
expect(node.children.length).toBe(1) expect(ast.children.length).toBe(1)
expect(node.children[0].type).toBe(NodeTypes.IF) 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', () => { describe('compiler: transform v-if', () => {
test('basic v-if', () => { test('basic v-if', () => {
const node = parseWithIfTransform(`<div v-if="ok"/>`) const { node } = parseWithIfTransform(`<div v-if="ok"/>`)
expect(node.type).toBe(NodeTypes.IF) expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(1) expect(node.branches.length).toBe(1)
expect((node.branches[0].condition as SimpleExpressionNode).content).toBe( expect((node.branches[0].condition as SimpleExpressionNode).content).toBe(
@ -44,7 +59,7 @@ describe('compiler: transform v-if', () => {
}) })
test('template v-if', () => { test('template v-if', () => {
const node = parseWithIfTransform( const { node } = parseWithIfTransform(
`<template v-if="ok"><div/>hello<p/></template>` `<template v-if="ok"><div/>hello<p/></template>`
) )
expect(node.type).toBe(NodeTypes.IF) expect(node.type).toBe(NodeTypes.IF)
@ -62,7 +77,7 @@ describe('compiler: transform v-if', () => {
}) })
test('v-if + v-else', () => { test('v-if + v-else', () => {
const node = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`) const { node } = parseWithIfTransform(`<div v-if="ok"/><p v-else/>`)
expect(node.type).toBe(NodeTypes.IF) expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(2) expect(node.branches.length).toBe(2)
@ -80,7 +95,9 @@ describe('compiler: transform v-if', () => {
}) })
test('v-if + v-else-if', () => { test('v-if + v-else-if', () => {
const node = parseWithIfTransform(`<div v-if="ok"/><p v-else-if="orNot"/>`) const { node } = parseWithIfTransform(
`<div v-if="ok"/><p v-else-if="orNot"/>`
)
expect(node.type).toBe(NodeTypes.IF) expect(node.type).toBe(NodeTypes.IF)
expect(node.branches.length).toBe(2) expect(node.branches.length).toBe(2)
@ -98,7 +115,7 @@ describe('compiler: transform v-if', () => {
}) })
test('v-if + v-else-if + v-else', () => { test('v-if + v-else-if + v-else', () => {
const node = parseWithIfTransform( const { node } = parseWithIfTransform(
`<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>` `<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`
) )
expect(node.type).toBe(NodeTypes.IF) expect(node.type).toBe(NodeTypes.IF)
@ -124,7 +141,7 @@ describe('compiler: transform v-if', () => {
}) })
test('comment between branches', () => { test('comment between branches', () => {
const node = parseWithIfTransform(` const { node } = parseWithIfTransform(`
<div v-if="ok"/> <div v-if="ok"/>
<!--foo--> <!--foo-->
<p v-else-if="orNot"/> <p v-else-if="orNot"/>
@ -158,24 +175,20 @@ describe('compiler: transform v-if', () => {
}) })
test('should prefix v-if condition', () => { test('should prefix v-if condition', () => {
const node = parseWithIfTransform(`<div v-if="ok"/>`, { const { node } = parseWithIfTransform(`<div v-if="ok"/>`, {
prefixIdentifiers: true prefixIdentifiers: true
}) as IfNode })
expect(node.branches[0].condition).toMatchObject({ expect(node.branches[0].condition).toMatchObject({
type: NodeTypes.SIMPLE_EXPRESSION, type: NodeTypes.SIMPLE_EXPRESSION,
content: `_ctx.ok` content: `_ctx.ok`
}) })
}) })
describe('codegen', () => {
// TODO
})
describe('errors', () => { describe('errors', () => {
test('error on v-else missing adjacent v-if', () => { test('error on v-else missing adjacent v-if', () => {
const onError = jest.fn() const onError = jest.fn()
const node1 = parseWithIfTransform(`<div v-else/>`, { onError }) const { node: node1 } = parseWithIfTransform(`<div v-else/>`, { onError })
expect(onError.mock.calls[0]).toMatchObject([ expect(onError.mock.calls[0]).toMatchObject([
{ {
code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
@ -183,7 +196,11 @@ describe('compiler: transform v-if', () => {
} }
]) ])
const node2 = parseWithIfTransform(`<div/><div v-else/>`, { onError }, 1) const { node: node2 } = parseWithIfTransform(
`<div/><div v-else/>`,
{ onError },
1
)
expect(onError.mock.calls[1]).toMatchObject([ expect(onError.mock.calls[1]).toMatchObject([
{ {
code: ErrorCodes.X_ELSE_NO_ADJACENT_IF, code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
@ -191,7 +208,7 @@ describe('compiler: transform v-if', () => {
} }
]) ])
const node3 = parseWithIfTransform( const { node: node3 } = parseWithIfTransform(
`<div/>foo<div v-else/>`, `<div/>foo<div v-else/>`,
{ onError }, { onError },
2 2
@ -207,7 +224,9 @@ describe('compiler: transform v-if', () => {
test('error on v-else-if missing adjacent v-if', () => { test('error on v-else-if missing adjacent v-if', () => {
const onError = jest.fn() const onError = jest.fn()
const node1 = parseWithIfTransform(`<div v-else-if="foo"/>`, { onError }) const { node: node1 } = parseWithIfTransform(`<div v-else-if="foo"/>`, {
onError
})
expect(onError.mock.calls[0]).toMatchObject([ expect(onError.mock.calls[0]).toMatchObject([
{ {
code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF, code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
@ -215,7 +234,7 @@ describe('compiler: transform v-if', () => {
} }
]) ])
const node2 = parseWithIfTransform( const { node: node2 } = parseWithIfTransform(
`<div/><div v-else-if="foo"/>`, `<div/><div v-else-if="foo"/>`,
{ onError }, { onError },
1 1
@ -227,7 +246,7 @@ describe('compiler: transform v-if', () => {
} }
]) ])
const node3 = parseWithIfTransform( const { node: node3 } = parseWithIfTransform(
`<div/>foo<div v-else-if="foo"/>`, `<div/>foo<div v-else-if="foo"/>`,
{ onError }, { onError },
2 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(`<div v-if="ok"/>`)
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(`<template v-if="ok"><div/>hello<p/></template>`)
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(`<div v-if="ok"/><p v-else/>`)
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(`<div v-if="ok"/><p v-else-if="orNot" />`)
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(
`<div v-if="ok"/><p v-else-if="orNot"/><template v-else>fine</template>`
)
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(`<div v-if="ok" v-bind="obj"/>`)
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(`<div v-if="ok" id="foo" v-bind="obj"/>`)
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(`<div v-if="ok" v-bind="obj" id="foo"/>`)
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(`<div v-if="ok" v-foo />`)
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', () => {})
})
}) })

View File

@ -300,7 +300,7 @@ function genNodeListAsArray(
context: CodegenContext context: CodegenContext
) { ) {
const multilines = const multilines =
nodes.length > 1 || nodes.length > 3 ||
((!__BROWSER__ || __DEV__) && ((!__BROWSER__ || __DEV__) &&
nodes.some( nodes.some(
n => n =>
@ -513,7 +513,7 @@ function genFor(node: ForNode, context: CodegenContext) {
function genCallExpression( function genCallExpression(
node: CallExpression, node: CallExpression,
context: CodegenContext, context: CodegenContext,
multilines = node.arguments.length > 2 multilines = false
) { ) {
context.push(node.callee + `(`, node, true) context.push(node.callee + `(`, node, true)
multilines && context.indent() multilines && context.indent()

View File

@ -19,7 +19,8 @@ import {
JSChildNode, JSChildNode,
ObjectExpression, ObjectExpression,
createObjectProperty, createObjectProperty,
Property Property,
ExpressionNode
} from '../ast' } from '../ast'
import { createCompilerError, ErrorCodes } from '../errors' import { createCompilerError, ErrorCodes } from '../errors'
import { processExpression } from './transformExpression' import { processExpression } from './transformExpression'
@ -28,7 +29,8 @@ import {
CREATE_BLOCK, CREATE_BLOCK,
EMPTY, EMPTY,
FRAGMENT, FRAGMENT,
APPLY_DIRECTIVES APPLY_DIRECTIVES,
MERGE_PROPS
} from '../runtimeConstants' } from '../runtimeConstants'
import { isString } from '@vue/shared' import { isString } from '@vue/shared'
@ -51,14 +53,14 @@ export const transformIf = createStructuralDirectiveTransform(
} }
if (dir.name === 'if') { if (dir.name === 'if') {
const branch = createIfBranch(node, dir)
const codegenNode = createSequenceExpression([ const codegenNode = createSequenceExpression([
createCallExpression(context.helper(OPEN_BLOCK)) createCallExpression(context.helper(OPEN_BLOCK))
]) ])
context.replaceNode({ context.replaceNode({
type: NodeTypes.IF, type: NodeTypes.IF,
loc: node.loc, loc: node.loc,
branches: [createIfBranch(node, dir)], branches: [branch],
codegenNode codegenNode
}) })
@ -66,7 +68,7 @@ export const transformIf = createStructuralDirectiveTransform(
// transformed. // transformed.
return () => { return () => {
codegenNode.expressions.push( codegenNode.expressions.push(
createCodegenNodeForBranch(node, dir, 0, context) createCodegenNodeForBranch(node, branch, 0, context)
) )
} }
} else { } else {
@ -104,7 +106,7 @@ export const transformIf = createStructuralDirectiveTransform(
} else { } else {
parentCondition.alternate = createCodegenNodeForBranch( parentCondition.alternate = createCodegenNodeForBranch(
node, node,
dir, branch,
sibling.branches.length - 1, sibling.branches.length - 1,
context context
) )
@ -138,25 +140,26 @@ function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
function createCodegenNodeForBranch( function createCodegenNodeForBranch(
node: ElementNode, node: ElementNode,
dir: DirectiveNode, branch: IfBranchNode,
index: number, index: number,
context: TransformContext context: TransformContext
): ConditionalExpression | CallExpression { ): ConditionalExpression | CallExpression {
if (dir.exp) { if (branch.condition) {
return createConditionalExpression( return createConditionalExpression(
dir.exp, branch.condition,
createChildrenCodegenNode(node, index, context), createChildrenCodegenNode(node, branch, index, context),
createCallExpression(context.helper(CREATE_BLOCK), [ createCallExpression(context.helper(CREATE_BLOCK), [
context.helper(EMPTY) context.helper(EMPTY)
]) ])
) )
} else { } else {
return createChildrenCodegenNode(node, index, context) return createChildrenCodegenNode(node, branch, index, context)
} }
} }
function createChildrenCodegenNode( function createChildrenCodegenNode(
node: ElementNode, node: ElementNode,
branch: IfBranchNode,
index: number, index: number,
{ helper }: TransformContext { helper }: TransformContext
): CallExpression { ): CallExpression {
@ -166,11 +169,11 @@ function createChildrenCodegenNode(
return createCallExpression(helper(CREATE_BLOCK), [ return createCallExpression(helper(CREATE_BLOCK), [
helper(FRAGMENT), helper(FRAGMENT),
keyExp, keyExp,
node.children branch.children
]) ])
} else { } else {
let childCodegen = node.codegenNode! let childCodegen = node.codegenNode!
if (childCodegen.callee === helper(APPLY_DIRECTIVES)) { if (childCodegen.callee.includes(APPLY_DIRECTIVES)) {
childCodegen = childCodegen.arguments[0] as CallExpression childCodegen = childCodegen.arguments[0] as CallExpression
} }
// change child to a block // change child to a block
@ -181,7 +184,10 @@ function createChildrenCodegenNode(
childCodegen.arguments[1] = keyExp childCodegen.arguments[1] = keyExp
} else { } else {
// inject branch key if not already have a key // 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) { if (props.type === NodeTypes.JS_CALL_EXPRESSION) {
// merged props... add ours // merged props... add ours
// only inject key to object literal if it's the first argument so that // only inject key to object literal if it's the first argument so that
@ -192,11 +198,17 @@ function createChildrenCodegenNode(
} else { } else {
props.arguments.unshift(keyExp) props.arguments.unshift(keyExp)
} }
} else { } else if (props.type === NodeTypes.JS_OBJECT_EXPRESSION) {
props.properties.unshift(createKeyProperty(index)) 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!
} }
} }