From 89bd64b43bec218acea302e6d4d1284635a485a5 Mon Sep 17 00:00:00 2001 From: defcc Date: Thu, 17 Nov 2016 21:00:02 +0800 Subject: [PATCH 01/11] add if conditions --- flow/compiler.js | 3 +- src/compiler/codegen/index.js | 23 ++++++--- src/compiler/optimizer.js | 4 +- src/compiler/parser/index.js | 53 ++++++++++++++------ test/unit/modules/compiler/optimizer.spec.js | 2 +- test/unit/modules/compiler/parser.spec.js | 11 ++-- 6 files changed, 67 insertions(+), 29 deletions(-) diff --git a/flow/compiler.js b/flow/compiler.js index c6e46868bf0..752e63bcdf2 100644 --- a/flow/compiler.js +++ b/flow/compiler.js @@ -94,8 +94,9 @@ declare type ASTElement = { if?: string; ifProcessed?: boolean; + elseif?: string; else?: true; - elseBlock?: ASTElement; + conditions?: Array<{ exp: ?string; block: ASTElement }>; for?: string; forProcessed?: boolean; diff --git a/src/compiler/codegen/index.js b/src/compiler/codegen/index.js index 937e037cb50..4452b682dc7 100644 --- a/src/compiler/codegen/index.js +++ b/src/compiler/codegen/index.js @@ -109,17 +109,28 @@ function genOnce (el: ASTElement): string { } } -// v-if with v-once shuold generate code like (a)?_m(0):_m(1) function genIf (el: any): string { const exp = el.if el.ifProcessed = true // avoid recursion - return `(${exp})?${el.once ? genOnce(el) : genElement(el)}:${genElse(el)}` + return genIfConditions(el.conditions) } -function genElse (el: ASTElement): string { - return el.elseBlock - ? genElement(el.elseBlock) - : '_e()' +function genIfConditions (conditions: any): string { + if (!conditions.length) { + return '_e()' + } + + var condition = conditions.shift() + if (condition.exp) { + return `(${condition.exp})?${genTernaryExp(condition.block)}:${genIfConditions(conditions)}` + } else { + return `${genTernaryExp(condition.block)}` + } + + // v-if with v-once shuold generate code like (a)?_m(0):_m(1) + function genTernaryExp (el) { + return el.once ? genOnce(el) : genElement(el) + } } function genFor (el: any): string { diff --git a/src/compiler/optimizer.js b/src/compiler/optimizer.js index 4bbd2ccf644..60571ad4c41 100644 --- a/src/compiler/optimizer.js +++ b/src/compiler/optimizer.js @@ -72,8 +72,8 @@ function markStaticRoots (node: ASTNode, isInFor: boolean) { const child = node.children[i] isInFor = isInFor || !!node.for markStaticRoots(child, isInFor) - if (child.type === 1 && child.elseBlock) { - markStaticRoots(child.elseBlock, isInFor) + if (child.type === 1 && child.conditions) { + child.conditions.forEach( _ => { markStaticRoots(_.block, isInFor) }) } } } diff --git a/src/compiler/parser/index.js b/src/compiler/parser/index.js index 761c3fe3b37..8fb2565392b 100644 --- a/src/compiler/parser/index.js +++ b/src/compiler/parser/index.js @@ -154,9 +154,12 @@ export function parse ( checkRootConstraints(root) } else if (!stack.length) { // allow 2 root elements with v-if and v-else - if (root.if && element.else) { + if (root.if && (element.alternate)) { checkRootConstraints(element) - root.elseBlock = element + addIfCondition(root, { + exp: element.alternate, + block: element + }) } else if (process.env.NODE_ENV !== 'production' && !warned) { warned = true warn( @@ -165,8 +168,8 @@ export function parse ( } } if (currentParent && !element.forbidden) { - if (element.else) { - processElse(element, currentParent) + if (element.alternate) { + processIfAternate(element, currentParent) } else { currentParent.children.push(element) element.parent = currentParent @@ -313,23 +316,45 @@ function processIf (el) { const exp = getAndRemoveAttr(el, 'v-if') if (exp) { el.if = exp - } - if (getAndRemoveAttr(el, 'v-else') != null) { - el.else = true + addIfCondition(el, { + exp: exp, + block: el + }) + } else { + if (getAndRemoveAttr(el, 'v-else') != null) { + el.else = true + } + + const elseif = getAndRemoveAttr(el, 'v-elseif') + if (elseif) { + el.aternate = elseif + } + } } -function processElse (el, parent) { - const prev = findPrevElement(parent.children) - if (prev && prev.if) { - prev.elseBlock = el +function processIfAternate (el, parent) { + const prev = findPrevIfElement(parent.children) + if (prev) { + addIfCondition(prev, { + exp: el.aternate, + block: el + }) } else if (process.env.NODE_ENV !== 'production') { warn( - `v-else used on element <${el.tag}> without corresponding v-if.` + `v-${el.elseif ? ('elseif="' + el.elseif + '"') : 'else'} ` + + `used on element <${el.tag}> without corresponding v-if.` ) } } +function addIfCondition (el, condition) { + if (!el.conditions) { + el.conditions = [] + } + el.conditions.push(condition) +} + function processOnce (el) { const once = getAndRemoveAttr(el, 'v-once') if (once != null) { @@ -448,10 +473,10 @@ function makeAttrsMap (attrs: Array, isIE: ?boolean): Object { return map } -function findPrevElement (children: Array): ASTElement | void { +function findPrevIfElement (children: Array): ASTElement | void { let i = children.length while (i--) { - if (children[i].tag) return children[i] + if (children[i].tag && children[i].if) return children[i] } } diff --git a/test/unit/modules/compiler/optimizer.spec.js b/test/unit/modules/compiler/optimizer.spec.js index 3f5a92c8293..80cf4a28614 100644 --- a/test/unit/modules/compiler/optimizer.spec.js +++ b/test/unit/modules/compiler/optimizer.spec.js @@ -59,7 +59,7 @@ describe('optimizer', () => { optimize(ast, baseOptions) expect(ast.static).toBe(false) expect(ast.children[0].static).toBe(false) - expect(ast.children[0].elseBlock.static).toBeUndefined() + expect(ast.children[0].conditions.static).toBeUndefined() }) it('v-pre directive', () => { diff --git a/test/unit/modules/compiler/parser.spec.js b/test/unit/modules/compiler/parser.spec.js index 21a7f26ad46..57d3db9d65b 100644 --- a/test/unit/modules/compiler/parser.spec.js +++ b/test/unit/modules/compiler/parser.spec.js @@ -103,7 +103,7 @@ describe('parser', () => {

`, baseOptions) expect(ast.tag).toBe('div') - expect(ast.elseBlock.tag).toBe('p') + expect(ast.conditions[1].block.tag).toBe('p') }) it('warn 2 root elements with v-if', () => { @@ -193,15 +193,16 @@ describe('parser', () => { it('v-if directive syntax', () => { const ast = parse('

hello world

', baseOptions) expect(ast.if).toBe('show') + expect(ast.conditions[0].exp).toBe('show') }) it('v-else directive syntax', () => { const ast = parse('

hello

world

', baseOptions) const ifAst = ast.children[0] - const elseAst = ifAst.elseBlock - expect(elseAst.else).toBe(true) - expect(elseAst.children[0].text).toBe('world') - expect(elseAst.parent).toBe(ast) + const conditionsAst = ifAst.conditions + expect(conditionsAst.length).toBe(2) + expect(conditionsAst[1].block.children[0].text).toBe('world') + expect(conditionsAst[1].block.parent).toBe(ast) }) it('v-else directive invalid syntax', () => { From 12f15d395092a46515e4c7b1bb106f44bd24fffd Mon Sep 17 00:00:00 2001 From: defcc Date: Fri, 18 Nov 2016 23:23:44 +0800 Subject: [PATCH 02/11] update v-if conditional --- flow/compiler.js | 2 +- src/compiler/codegen/index.js | 1 - src/compiler/optimizer.js | 2 +- src/compiler/parser/index.js | 19 ++++++++----------- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/flow/compiler.js b/flow/compiler.js index 752e63bcdf2..48be8eb15cd 100644 --- a/flow/compiler.js +++ b/flow/compiler.js @@ -95,7 +95,7 @@ declare type ASTElement = { if?: string; ifProcessed?: boolean; elseif?: string; - else?: true; + condition?: true; conditions?: Array<{ exp: ?string; block: ASTElement }>; for?: string; diff --git a/src/compiler/codegen/index.js b/src/compiler/codegen/index.js index 4452b682dc7..a6ffb9f7e01 100644 --- a/src/compiler/codegen/index.js +++ b/src/compiler/codegen/index.js @@ -110,7 +110,6 @@ function genOnce (el: ASTElement): string { } function genIf (el: any): string { - const exp = el.if el.ifProcessed = true // avoid recursion return genIfConditions(el.conditions) } diff --git a/src/compiler/optimizer.js b/src/compiler/optimizer.js index 60571ad4c41..95820fcef3f 100644 --- a/src/compiler/optimizer.js +++ b/src/compiler/optimizer.js @@ -73,7 +73,7 @@ function markStaticRoots (node: ASTNode, isInFor: boolean) { isInFor = isInFor || !!node.for markStaticRoots(child, isInFor) if (child.type === 1 && child.conditions) { - child.conditions.forEach( _ => { markStaticRoots(_.block, isInFor) }) + child.conditions.forEach(_ => { markStaticRoots(_.block, isInFor) }) } } } diff --git a/src/compiler/parser/index.js b/src/compiler/parser/index.js index 93ca5a0c6dd..f86e87bc44b 100644 --- a/src/compiler/parser/index.js +++ b/src/compiler/parser/index.js @@ -154,10 +154,10 @@ export function parse ( checkRootConstraints(root) } else if (!stack.length) { // allow 2 root elements with v-if and v-else - if (root.if && (element.alternate)) { + if (root.if && element.condition) { checkRootConstraints(element) addIfCondition(root, { - exp: element.alternate, + exp: element.elseif, block: element }) } else if (process.env.NODE_ENV !== 'production' && !warned) { @@ -168,7 +168,7 @@ export function parse ( } } if (currentParent && !element.forbidden) { - if (element.alternate) { + if (element.condition) { processIfAternate(element, currentParent) } else { currentParent.children.push(element) @@ -321,15 +321,12 @@ function processIf (el) { block: el }) } else { - if (getAndRemoveAttr(el, 'v-else') != null) { - el.else = true - } - + const hasElse = getAndRemoveAttr(el, 'v-else') != null const elseif = getAndRemoveAttr(el, 'v-elseif') - if (elseif) { - el.aternate = elseif + if (hasElse || elseif) { + el.condition = true } - + elseif && (el.elseif = elseif) } } @@ -337,7 +334,7 @@ function processIfAternate (el, parent) { const prev = findPrevIfElement(parent.children) if (prev) { addIfCondition(prev, { - exp: el.aternate, + exp: el.elseif, block: el }) } else if (process.env.NODE_ENV !== 'production') { From 687fd2beb2ace5c06699908fc01f218dd9c69c0d Mon Sep 17 00:00:00 2001 From: defcc Date: Sun, 20 Nov 2016 17:42:04 +0800 Subject: [PATCH 03/11] update test --- test/unit/features/directives/if.spec.js | 49 ++++++++++++ test/unit/modules/compiler/codegen.spec.js | 21 +++++ test/unit/modules/compiler/parser.spec.js | 92 +++++++++++++++++++++- 3 files changed, 161 insertions(+), 1 deletion(-) diff --git a/test/unit/features/directives/if.spec.js b/test/unit/features/directives/if.spec.js index 893c83d35a3..7146ec0e048 100644 --- a/test/unit/features/directives/if.spec.js +++ b/test/unit/features/directives/if.spec.js @@ -88,6 +88,55 @@ describe('Directive v-if', () => { }).then(done) }) + it('should work well with v-elseif', done => { + const vm = new Vue({ + template: ` +
+ hello + elseif + bye +
+ `, + data: { foo: true, bar: false } + }).$mount() + expect(vm.$el.innerHTML.trim()).toBe('hello') + vm.foo = false + waitForUpdate(() => { + expect(vm.$el.innerHTML.trim()).toBe('bye') + vm.bar = true + }).then(() => { + expect(vm.$el.innerHTML.trim()).toBe('elseif') + vm.bar = false + }).then(() => { + expect(vm.$el.innerHTML.trim()).toBe('bye') + vm.foo = true + }).then(() => { + expect(vm.$el.innerHTML.trim()).toBe('hello') + vm.foo = false + vm.bar = {} + }).then(() => { + expect(vm.$el.innerHTML.trim()).toBe('elseif') + vm.bar = 0 + }).then(() => { + expect(vm.$el.innerHTML.trim()).toBe('bye') + vm.bar = [] + }).then(() => { + expect(vm.$el.innerHTML.trim()).toBe('elseif') + vm.bar = null + }).then(() => { + expect(vm.$el.innerHTML.trim()).toBe('bye') + vm.bar = '0' + }).then(() => { + expect(vm.$el.innerHTML.trim()).toBe('elseif') + vm.bar = undefined + }).then(() => { + expect(vm.$el.innerHTML.trim()).toBe('bye') + vm.bar = 1 + }).then(() => { + expect(vm.$el.innerHTML.trim()).toBe('elseif') + }).then(done) + }) + it('should work well with v-for', done => { const vm = new Vue({ template: ` diff --git a/test/unit/modules/compiler/codegen.spec.js b/test/unit/modules/compiler/codegen.spec.js index 9da00d94129..7bfef3cf399 100644 --- a/test/unit/modules/compiler/codegen.spec.js +++ b/test/unit/modules/compiler/codegen.spec.js @@ -67,6 +67,27 @@ describe('codegen', () => { ) }) + it('generate v-elseif directive', () => { + assertCodegen( + '

hello

world

', + `with(this){return _h('div',[(show)?_h('p',["hello"]):(hide)?_h('p',["world"]):_e()])}` + ) + }) + + it('generate v-elseif with v-else directive', () => { + assertCodegen( + '

hello

world

bye

', + `with(this){return _h('div',[(show)?_h('p',["hello"]):(hide)?_h('p',["world"]):_h('p',["bye"])])}` + ) + }) + + it('generate v-elseif with v-else directive', () => { + assertCodegen( + '

hello

world

bye

', + `with(this){return _h('div',[(show)?_h('p',["hello"]):(hide)?_h('p',["world"]):_h('p',["bye"])])}` + ) + }) + it('generate ref', () => { assertCodegen( '

', diff --git a/test/unit/modules/compiler/parser.spec.js b/test/unit/modules/compiler/parser.spec.js index 57d3db9d65b..3d13527865f 100644 --- a/test/unit/modules/compiler/parser.spec.js +++ b/test/unit/modules/compiler/parser.spec.js @@ -88,6 +88,12 @@ describe('parser', () => { .not.toHaveBeenWarned() }) + it('not warn 3 root elements with v-if, v-elseif and v-else', () => { + parse('
', baseOptions) + expect('Component template should contain exactly one root element') + .not.toHaveBeenWarned() + }) + it('not warn 2 root elements with v-if and v-else on separate lines', () => { parse(`
@@ -97,6 +103,26 @@ describe('parser', () => { .not.toHaveBeenWarned() }) + it('not warn 3 or more root elements with v-if, v-elseif and v-else on separate lines', () => { + parse(` +
+
+
+ `, baseOptions) + expect('Component template should contain exactly one root element') + .not.toHaveBeenWarned() + + parse(` +
+
+
+
+
+ `, baseOptions) + expect('Component template should contain exactly one root element') + .not.toHaveBeenWarned() + }) + it('generate correct ast for 2 root elements with v-if and v-else on separate lines', () => { const ast = parse(`
@@ -106,6 +132,32 @@ describe('parser', () => { expect(ast.conditions[1].block.tag).toBe('p') }) + it('generate correct ast for 3 or more root elements with v-if and v-else on separate lines', () => { + const ast = parse(` +
+ +

+ `, baseOptions) + expect(ast.tag).toBe('div') + expect(ast.conditions[0].block.tag).toBe('div') + expect(ast.conditions[1].block.tag).toBe('span') + expect(ast.conditions[2].block.tag).toBe('p') + + const astMore = parse(` +
+ +
+ +

+ `, baseOptions) + expect(astMore.tag).toBe('div') + expect(astMore.conditions[0].block.tag).toBe('div') + expect(astMore.conditions[1].block.tag).toBe('span') + expect(astMore.conditions[2].block.tag).toBe('div') + expect(astMore.conditions[3].block.tag).toBe('span') + expect(astMore.conditions[4].block.tag).toBe('p') + }) + it('warn 2 root elements with v-if', () => { parse('
', baseOptions) expect('Component template should contain exactly one root element:\n\n
') @@ -118,12 +170,34 @@ describe('parser', () => { .toHaveBeenWarned() }) + it('warn 3 root elements with v-if and v-elseif on first 2', () => { + parse('
', baseOptions) + expect('Component template should contain exactly one root element:\n\n' + + '
') + .toHaveBeenWarned() + }) + + it('warn 4 root elements with v-if, v-elseif and v-else on first 2', () => { + parse('
', baseOptions) + expect('Component template should contain exactly one root element:\n\n' + + '
') + .toHaveBeenWarned() + }) + it('warn 2 root elements with v-if and v-else with v-for on 2nd', () => { parse('
', baseOptions) - expect('Cannot use v-for on stateful component root element because it renders multiple elements:\n
') + expect('Cannot use v-for on stateful component root element because it renders multiple elements:\n' + + '
') .toHaveBeenWarned() }) + it('warn 2 root elements with v-if and v-elseif with v-for on 2nd', () => { + parse('
', baseOptions) + expect('Cannot use v-for on stateful component root element because it renders multiple elements:\n' + + '
') + .toHaveBeenWarned() + }) + it('warn