]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip(compiler): generate blocks for v-if
authorEvan You <yyx990803@gmail.com>
Tue, 1 Oct 2019 16:25:13 +0000 (12:25 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 1 Oct 2019 16:25:29 +0000 (12:25 -0400)
21 files changed:
packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap
packages/compiler-core/__tests__/__snapshots__/compile.spec.ts.snap
packages/compiler-core/__tests__/codegen.spec.ts
packages/compiler-core/__tests__/compile.spec.ts
packages/compiler-core/__tests__/transforms/__snapshots__/optimizeText.spec.ts.snap
packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-core/__tests__/transforms/transformExpressions.spec.ts
packages/compiler-core/__tests__/transforms/vBind.spec.ts
packages/compiler-core/__tests__/transforms/vIf.spec.ts
packages/compiler-core/__tests__/transforms/vOn.spec.ts
packages/compiler-core/src/ast.ts
packages/compiler-core/src/codegen.ts
packages/compiler-core/src/errors.ts
packages/compiler-core/src/runtimeConstants.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-core/src/transforms/transfromSlotOutlet.ts
packages/compiler-core/src/transforms/vBind.ts
packages/compiler-core/src/transforms/vIf.ts
packages/compiler-core/src/transforms/vOn.ts
packages/compiler-core/src/transforms/vSlot.ts

index e388868723eaf6f08f745a6e2c8e4083b0d49c16..eb1c55a7c6c04bbf316459f6267c1aa5612de872 100644 (file)
@@ -105,9 +105,11 @@ return function render() {
 
 exports[`compiler: codegen function mode preamble 1`] = `
 "const _Vue = Vue
+
 return function render() {
   with (this) {
     const { helperOne: _helperOne, helperTwo: _helperTwo } = _Vue
+    
     return null
   }
 }"
@@ -137,24 +139,7 @@ exports[`compiler: codegen ifNode 1`] = `
 "
 return function render() {
   with (this) {
-    return foo
-      ? \\"foo\\"
-      : (a + b)
-        ? _toString(bye)
-        : _createVNode(_Comment, 0, \\"foo\\")
-  }
-}"
-`;
-
-exports[`compiler: codegen ifNode with no v-else 1`] = `
-"
-return function render() {
-  with (this) {
-    return foo
-      ? \\"foo\\"
-      : (a + b)
-        ? _toString(bye)
-        : null
+    return (foo, bar)
   }
 }"
 `;
index 047d00ad3de26d8b93d5be147398cb977a307507..2bd766517055f6d9bb445b59d69477726b9373c7 100644 (file)
@@ -2,17 +2,27 @@
 
 exports[`compiler: integration tests function mode 1`] = `
 "const _Vue = Vue
+
 return function render() {
   with (this) {
-    const { createVNode: _createVNode, toString: _toString, renderList: _renderList } = _Vue
+    const { createVNode: _createVNode, toString: _toString, openBlock: _openBlock, applyDirectives: _applyDirectives, createBlock: _createBlock, Empty: _Empty, Fragment: _Fragment, renderList: _renderList } = _Vue
+    
     return _createVNode(\\"div\\", {
       id: \\"foo\\",
       class: bar.baz
     }, [
       _toString(world.burn()),
-      ok
-        ? _createVNode(\\"div\\", null, \\"yes\\")
-        : \\"no\\",
+      (_openBlock(), ok
+        ? _createBlock(
+            \\"div\\",
+            { key: 0 },
+            \\"yes\\"
+          )
+        : _createBlock(
+            _Fragment,
+            { key: 1 },
+            \\"no\\"
+          )),
       _renderList(list, (value, index) => {
         return _createVNode(\\"div\\", null, [
           _createVNode(\\"span\\", null, _toString(value + index))
@@ -24,7 +34,7 @@ return function render() {
 `;
 
 exports[`compiler: integration tests function mode w/ prefixIdentifiers: true 1`] = `
-"const { createVNode, toString, renderList } = Vue
+"const { createVNode, toString, openBlock, applyDirectives, createBlock, Empty, Fragment, renderList } = Vue
 
 return function render() {
   const _ctx = this
@@ -33,9 +43,17 @@ return function render() {
     class: _ctx.bar.baz
   }, [
     toString(_ctx.world.burn()),
-    (_ctx.ok)
-      ? createVNode(\\"div\\", null, \\"yes\\")
-      : \\"no\\",
+    (openBlock(), (_ctx.ok)
+      ? 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))
@@ -46,7 +64,7 @@ return function render() {
 `;
 
 exports[`compiler: integration tests module mode 1`] = `
-"import { createVNode, toString, renderList } from \\"vue\\"
+"import { createVNode, toString, openBlock, applyDirectives, createBlock, Empty, Fragment, renderList } from \\"vue\\"
 
 export default function render() {
   const _ctx = this
@@ -55,9 +73,17 @@ export default function render() {
     class: _ctx.bar.baz
   }, [
     _toString(_ctx.world.burn()),
-    (_ctx.ok)
-      ? createVNode(\\"div\\", null, \\"yes\\")
-      : \\"no\\",
+    (openBlock(), (_ctx.ok)
+      ? 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))
index 93de38fbc115bf7c380dc08b2859be7670d33db4..3e2b48d11197c5405f7943bddb665323977a7fb1 100644 (file)
@@ -12,7 +12,8 @@ import {
   createArrayExpression,
   ElementNode,
   createCompoundExpression,
-  createInterpolation
+  createInterpolation,
+  createSequenceExpression
 } from '../src'
 import {
   CREATE_VNODE,
@@ -100,8 +101,7 @@ describe('compiler: codegen', () => {
           [
             createObjectProperty(
               createSimpleExpression(`id`, true, mockLoc),
-              createSimpleExpression(`foo`, true, mockLoc),
-              mockLoc
+              createSimpleExpression(`foo`, true, mockLoc)
             )
           ],
           mockLoc
@@ -226,19 +226,16 @@ describe('compiler: codegen', () => {
     const { code } = generate(
       createRoot({
         children: [
-          createCompoundExpression(
-            [
-              `_ctx.`,
-              createSimpleExpression(`foo`, false, mockLoc),
-              ` + `,
-              {
-                type: NodeTypes.INTERPOLATION,
-                loc: mockLoc,
-                content: createSimpleExpression(`bar`, false, mockLoc)
-              }
-            ],
-            mockLoc
-          )
+          createCompoundExpression([
+            `_ctx.`,
+            createSimpleExpression(`foo`, false, mockLoc),
+            ` + `,
+            {
+              type: NodeTypes.INTERPOLATION,
+              loc: mockLoc,
+              content: createSimpleExpression(`bar`, false, mockLoc)
+            }
+          ])
         ]
       })
     )
@@ -253,90 +250,16 @@ describe('compiler: codegen', () => {
           {
             type: NodeTypes.IF,
             loc: mockLoc,
-            branches: [
-              {
-                type: NodeTypes.IF_BRANCH,
-                condition: createSimpleExpression('foo', false, mockLoc),
-                loc: mockLoc,
-                children: [
-                  {
-                    type: NodeTypes.TEXT,
-                    content: 'foo',
-                    isEmpty: false,
-                    loc: mockLoc
-                  }
-                ]
-              },
-              {
-                type: NodeTypes.IF_BRANCH,
-                condition: createSimpleExpression('a + b', false, mockLoc),
-                loc: mockLoc,
-                children: [createInterpolation(`bye`, mockLoc)]
-              },
-              {
-                type: NodeTypes.IF_BRANCH,
-                condition: undefined,
-                loc: mockLoc,
-                children: [
-                  {
-                    type: NodeTypes.COMMENT,
-                    content: 'foo',
-                    loc: mockLoc
-                  }
-                ]
-              }
-            ]
+            branches: [],
+            codegenNode: createSequenceExpression([
+              createSimpleExpression('foo', false),
+              createSimpleExpression('bar', false)
+            ])
           }
         ]
       })
     )
-    expect(code).toMatch(`
-    return foo
-      ? "foo"
-      : (a + b)
-        ? _${TO_STRING}(bye)
-        : _${CREATE_VNODE}(_${COMMENT}, 0, "foo")`)
-    expect(code).toMatchSnapshot()
-  })
-
-  test('ifNode with no v-else', () => {
-    const { code } = generate(
-      createRoot({
-        children: [
-          {
-            type: NodeTypes.IF,
-            loc: mockLoc,
-            branches: [
-              {
-                type: NodeTypes.IF_BRANCH,
-                condition: createSimpleExpression('foo', false, mockLoc),
-                loc: mockLoc,
-                children: [
-                  {
-                    type: NodeTypes.TEXT,
-                    content: 'foo',
-                    isEmpty: false,
-                    loc: mockLoc
-                  }
-                ]
-              },
-              {
-                type: NodeTypes.IF_BRANCH,
-                condition: createSimpleExpression('a + b', false, mockLoc),
-                loc: mockLoc,
-                children: [createInterpolation(`bye`, mockLoc)]
-              }
-            ]
-          }
-        ]
-      })
-    )
-    expect(code).toMatch(`
-    return foo
-      ? "foo"
-      : (a + b)
-        ? _${TO_STRING}(bye)
-        : null`)
+    expect(code).toMatch(`return (foo, bar)`)
     expect(code).toMatchSnapshot()
   })
 
@@ -570,13 +493,11 @@ describe('compiler: codegen', () => {
               [
                 createObjectProperty(
                   createSimpleExpression(`id`, true, mockLoc),
-                  createSimpleExpression(`foo`, true, mockLoc),
-                  mockLoc
+                  createSimpleExpression(`foo`, true, mockLoc)
                 ),
                 createObjectProperty(
                   createSimpleExpression(`prop`, false, mockLoc),
-                  createSimpleExpression(`bar`, false, mockLoc),
-                  mockLoc
+                  createSimpleExpression(`bar`, false, mockLoc)
                 ),
                 // compound expression as computed key
                 createObjectProperty(
@@ -588,8 +509,7 @@ describe('compiler: codegen', () => {
                       createSimpleExpression(`bar`, false, mockLoc)
                     ]
                   },
-                  createSimpleExpression(`bar`, false, mockLoc),
-                  mockLoc
+                  createSimpleExpression(`bar`, false, mockLoc)
                 )
               ],
               mockLoc
@@ -603,8 +523,7 @@ describe('compiler: codegen', () => {
                     createObjectProperty(
                       // should quote the key!
                       createSimpleExpression(`some-key`, true, mockLoc),
-                      createSimpleExpression(`foo`, true, mockLoc),
-                      mockLoc
+                      createSimpleExpression(`foo`, true, mockLoc)
                     )
                   ],
                   mockLoc
@@ -641,4 +560,8 @@ describe('compiler: codegen', () => {
     ])`)
     expect(code).toMatchSnapshot()
   })
+
+  test.todo('SequenceExpression')
+
+  test.todo('ConditionalExpression')
 })
index 951702ba2aecf01e4140e1b6e67a4e468bcf1d07..668519511868020bcc2b8ac42cf716ed1aa9a542 100644 (file)
@@ -50,10 +50,6 @@ describe('compiler: integration tests', () => {
       filename: `foo.vue`
     })
 
-    expect(code).toMatch(
-      `const { createVNode: _createVNode, toString: _toString, renderList: _renderList } = _Vue`
-    )
-
     expect(code).toMatchSnapshot()
     expect(map!.sources).toEqual([`foo.vue`])
     expect(map!.sourcesContent).toEqual([source])
@@ -120,8 +116,6 @@ describe('compiler: integration tests', () => {
       prefixIdentifiers: true
     })
 
-    expect(code).toMatch(`const { createVNode, toString, renderList } = Vue`)
-
     expect(code).toMatchSnapshot()
     expect(map!.sources).toEqual([`foo.vue`])
     expect(map!.sourcesContent).toEqual([source])
@@ -197,10 +191,6 @@ describe('compiler: integration tests', () => {
       filename: `foo.vue`
     })
 
-    expect(code).toMatch(
-      `import { createVNode, toString, renderList } from "vue"`
-    )
-
     expect(code).toMatchSnapshot()
     expect(map!.sources).toEqual([`foo.vue`])
     expect(map!.sourcesContent).toEqual([source])
index 921cbec13fb3207027b8247842a3abb5d2190288..89da629edf1e8d9875550fa95e3d684daccdf3ce 100644 (file)
@@ -2,9 +2,11 @@
 
 exports[`compiler: optimize interpolation consecutive text 1`] = `
 "const _Vue = Vue
+
 return function render() {
   with (this) {
     const { toString: _toString } = _Vue
+    
     return _toString(foo) + \\" bar \\" + _toString(baz)
   }
 }"
@@ -12,9 +14,11 @@ return function render() {
 
 exports[`compiler: optimize interpolation consecutive text between elements 1`] = `
 "const _Vue = Vue
+
 return function render() {
   with (this) {
     const { createVNode: _createVNode, toString: _toString } = _Vue
+    
     return [
       _createVNode(\\"div\\"),
       _toString(foo) + \\" bar \\" + _toString(baz),
@@ -26,9 +30,11 @@ return function render() {
 
 exports[`compiler: optimize interpolation consecutive text mixed with elements 1`] = `
 "const _Vue = Vue
+
 return function render() {
   with (this) {
     const { createVNode: _createVNode, toString: _toString } = _Vue
+    
     return [
       _createVNode(\\"div\\"),
       _toString(foo) + \\" bar \\" + _toString(baz),
@@ -42,9 +48,11 @@ return function render() {
 
 exports[`compiler: optimize interpolation no consecutive text 1`] = `
 "const _Vue = Vue
+
 return function render() {
   with (this) {
     const { toString: _toString } = _Vue
+    
     return _toString(foo)
   }
 }"
index 2af63cb8b6c9265244079ef186a5cde7e619805d..8f2cce9eef500b07333c19e9abe9e3c85ec174db 100644 (file)
@@ -74,42 +74,25 @@ describe('compiler: element transform', () => {
   })
 
   test('static props', () => {
-    const { root, node } = parseWithElementTransform(
-      `<div id="foo" class="bar" />`
-    )
+    const { node } = parseWithElementTransform(`<div id="foo" class="bar" />`)
     expect(node.callee).toBe(`_${CREATE_VNODE}`)
-    // should hoist the static object
-    expect(root.hoists).toMatchObject([
+    expect(node.arguments).toMatchObject([
+      `"div"`,
       createStaticObjectMatcher({
         id: 'foo',
         class: 'bar'
       })
     ])
-    expect(node.arguments).toMatchObject([
-      `"div"`,
-      {
-        type: NodeTypes.SIMPLE_EXPRESSION,
-        content: `_hoisted_1`
-      }
-    ])
   })
 
   test('props + children', () => {
-    const { root, node } = parseWithElementTransform(
-      `<div id="foo"><span/></div>`
-    )
+    const { node } = parseWithElementTransform(`<div id="foo"><span/></div>`)
     expect(node.callee).toBe(`_${CREATE_VNODE}`)
-    expect(root.hoists).toMatchObject([
-      createStaticObjectMatcher({
-        id: 'foo'
-      })
-    ])
     expect(node.arguments).toMatchObject([
       `"div"`,
-      {
-        type: NodeTypes.SIMPLE_EXPRESSION,
-        content: `_hoisted_1`
-      },
+      createStaticObjectMatcher({
+        id: 'foo'
+      }),
       [
         {
           type: NodeTypes.ELEMENT,
@@ -298,7 +281,7 @@ describe('compiler: element transform', () => {
         foo(dir) {
           _dir = dir
           return {
-            props: createObjectProperty(dir.arg!, dir.exp!, dir.loc),
+            props: createObjectProperty(dir.arg!, dir.exp!),
             needRuntime: false
           }
         }
@@ -326,7 +309,7 @@ describe('compiler: element transform', () => {
           foo(dir) {
             _dir = dir
             return {
-              props: [createObjectProperty(dir.arg!, dir.exp!, dir.loc)],
+              props: [createObjectProperty(dir.arg!, dir.exp!)],
               needRuntime: true
             }
           }
index cf806f8a2d38b6b0789c1565b117d3ccaee87644..0104742922c481de5e6a29d69ef0d7515f6924c4 100644 (file)
@@ -5,7 +5,6 @@ import {
   DirectiveNode,
   NodeTypes,
   CompilerOptions,
-  IfNode,
   InterpolationNode
 } from '../../src'
 import { transformIf } from '../../src/transforms/vIf'
@@ -159,14 +158,6 @@ describe('compiler: expression transform', () => {
     })
   })
 
-  test('should prefix v-if condition', () => {
-    const node = parseWithExpressionTransform(`<div v-if="ok"/>`) as IfNode
-    expect(node.branches[0].condition).toMatchObject({
-      type: NodeTypes.SIMPLE_EXPRESSION,
-      content: `_ctx.ok`
-    })
-  })
-
   test('should not prefix whitelisted globals', () => {
     const node = parseWithExpressionTransform(
       `{{ Math.max(1, 2) }}`
index 5c3f252c5e16748796ffdaf2a6f87303e9f7ac83..0f6a1a1a24bf47470f0f6e3fbd4c4c37f835a5e4 100644 (file)
@@ -61,16 +61,6 @@ describe('compiler: transform v-bind', () => {
             column: 19
           }
         }
-      },
-      loc: {
-        start: {
-          line: 1,
-          column: 6
-        },
-        end: {
-          line: 1,
-          column: 20
-        }
       }
     })
   })
index dcac2812c1ae45151b2e375305239fafcfd48180..5871476fdebbafd18b5d60874083b312d3cfef13 100644 (file)
@@ -1,6 +1,7 @@
 import { parse } from '../../src/parse'
 import { transform } from '../../src/transform'
 import { transformIf } from '../../src/transforms/vIf'
+import { transformElement } from '../../src/transforms/transformElement'
 import {
   IfNode,
   NodeTypes,
@@ -18,7 +19,10 @@ function parseWithIfTransform(
   returnIndex: number = 0
 ): IfNode {
   const node = parse(template, options)
-  transform(node, { nodeTransforms: [transformIf], ...options })
+  transform(node, {
+    nodeTransforms: [transformIf, transformElement],
+    ...options
+  })
   if (!options.onError) {
     expect(node.children.length).toBe(1)
     expect(node.children[0].type).toBe(NodeTypes.IF)
@@ -153,67 +157,87 @@ describe('compiler: transform v-if', () => {
     expect((b3.children[1] as TextNode).content).toBe(`fine`)
   })
 
-  test('error on v-else missing adjacent v-if', () => {
-    const onError = jest.fn()
-
-    const node1 = parseWithIfTransform(`<div v-else/>`, { onError })
-    expect(onError.mock.calls[0]).toMatchObject([
-      {
-        code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
-        loc: node1.loc
-      }
-    ])
-
-    const node2 = parseWithIfTransform(`<div/><div v-else/>`, { onError }, 1)
-    expect(onError.mock.calls[1]).toMatchObject([
-      {
-        code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
-        loc: node2.loc
-      }
-    ])
-
-    const node3 = parseWithIfTransform(`<div/>foo<div v-else/>`, { onError }, 2)
-    expect(onError.mock.calls[2]).toMatchObject([
-      {
-        code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
-        loc: node3.loc
-      }
-    ])
+  test('should prefix v-if condition', () => {
+    const node = parseWithIfTransform(`<div v-if="ok"/>`, {
+      prefixIdentifiers: true
+    }) as IfNode
+    expect(node.branches[0].condition).toMatchObject({
+      type: NodeTypes.SIMPLE_EXPRESSION,
+      content: `_ctx.ok`
+    })
   })
 
-  test('error on v-else-if missing adjacent v-if', () => {
-    const onError = jest.fn()
-
-    const node1 = parseWithIfTransform(`<div v-else-if="foo"/>`, { onError })
-    expect(onError.mock.calls[0]).toMatchObject([
-      {
-        code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
-        loc: node1.loc
-      }
-    ])
-
-    const node2 = parseWithIfTransform(
-      `<div/><div v-else-if="foo"/>`,
-      { onError },
-      1
-    )
-    expect(onError.mock.calls[1]).toMatchObject([
-      {
-        code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
-        loc: node2.loc
-      }
-    ])
-
-    const node3 = parseWithIfTransform(
-      `<div/>foo<div v-else-if="foo"/>`,
-      { onError },
-      2
-    )
-    expect(onError.mock.calls[2]).toMatchObject([
-      {
-        code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
-        loc: node3.loc
-      }
-    ])
+  describe('codegen', () => {
+    // TODO
+  })
+
+  describe('errors', () => {
+    test('error on v-else missing adjacent v-if', () => {
+      const onError = jest.fn()
+
+      const node1 = parseWithIfTransform(`<div v-else/>`, { onError })
+      expect(onError.mock.calls[0]).toMatchObject([
+        {
+          code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
+          loc: node1.loc
+        }
+      ])
+
+      const node2 = parseWithIfTransform(`<div/><div v-else/>`, { onError }, 1)
+      expect(onError.mock.calls[1]).toMatchObject([
+        {
+          code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
+          loc: node2.loc
+        }
+      ])
+
+      const node3 = parseWithIfTransform(
+        `<div/>foo<div v-else/>`,
+        { onError },
+        2
+      )
+      expect(onError.mock.calls[2]).toMatchObject([
+        {
+          code: ErrorCodes.X_ELSE_NO_ADJACENT_IF,
+          loc: node3.loc
+        }
+      ])
+    })
+
+    test('error on v-else-if missing adjacent v-if', () => {
+      const onError = jest.fn()
+
+      const node1 = parseWithIfTransform(`<div v-else-if="foo"/>`, { onError })
+      expect(onError.mock.calls[0]).toMatchObject([
+        {
+          code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
+          loc: node1.loc
+        }
+      ])
+
+      const node2 = parseWithIfTransform(
+        `<div/><div v-else-if="foo"/>`,
+        { onError },
+        1
+      )
+      expect(onError.mock.calls[1]).toMatchObject([
+        {
+          code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
+          loc: node2.loc
+        }
+      ])
+
+      const node3 = parseWithIfTransform(
+        `<div/>foo<div v-else-if="foo"/>`,
+        { onError },
+        2
+      )
+      expect(onError.mock.calls[2]).toMatchObject([
+        {
+          code: ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF,
+          loc: node3.loc
+        }
+      ])
+    })
   })
 })
index c0cffa668a55be744c511edb987dd29ad0d43995..8e92123daeac7e2784f0051f7b6ec2bad65dc86c 100644 (file)
@@ -58,16 +58,6 @@ describe('compiler: transform v-on', () => {
             column: 25
           }
         }
-      },
-      loc: {
-        start: {
-          line: 1,
-          column: 6
-        },
-        end: {
-          line: 1,
-          column: 26
-        }
       }
     })
   })
index b894be857059217845e1658078d8d052e5940857..eafe9e8fc82b77fa3b14042b9e7a306389a1339c 100644 (file)
@@ -142,7 +142,7 @@ export interface CompoundExpressionNode extends Node {
 export interface IfNode extends Node {
   type: NodeTypes.IF
   branches: IfBranchNode[]
-  codegenNode: JSChildNode | undefined
+  codegenNode: SequenceExpression
 }
 
 export interface IfBranchNode extends Node {
@@ -212,9 +212,20 @@ export interface ConditionalExpression extends Node {
   alternate: JSChildNode
 }
 
+// AST Utilities ---------------------------------------------------------------
+
+// Some expressions, e.g. sequence and conditional expressions, are never
+// associated with template nodes, so their source locations are just a stub.
+// Container types like CompoundExpression also don't need a real location.
+const locStub: SourceLocation = {
+  source: '',
+  start: { line: 1, column: 1, offset: 0 },
+  end: { line: 1, column: 1, offset: 0 }
+}
+
 export function createArrayExpression(
   elements: ArrayExpression['elements'],
-  loc: SourceLocation
+  loc: SourceLocation = locStub
 ): ArrayExpression {
   return {
     type: NodeTypes.JS_ARRAY_EXPRESSION,
@@ -225,7 +236,7 @@ export function createArrayExpression(
 
 export function createObjectExpression(
   properties: Property[],
-  loc: SourceLocation
+  loc: SourceLocation = locStub
 ): ObjectExpression {
   return {
     type: NodeTypes.JS_OBJECT_EXPRESSION,
@@ -236,12 +247,11 @@ export function createObjectExpression(
 
 export function createObjectProperty(
   key: ExpressionNode,
-  value: JSChildNode,
-  loc: SourceLocation
+  value: JSChildNode
 ): Property {
   return {
     type: NodeTypes.JS_PROPERTY,
-    loc,
+    loc: locStub,
     key,
     value
   }
@@ -250,7 +260,7 @@ export function createObjectProperty(
 export function createSimpleExpression(
   content: string,
   isStatic: boolean,
-  loc: SourceLocation
+  loc: SourceLocation = locStub
 ): SimpleExpressionNode {
   return {
     type: NodeTypes.SIMPLE_EXPRESSION,
@@ -274,20 +284,19 @@ export function createInterpolation(
 }
 
 export function createCompoundExpression(
-  children: CompoundExpressionNode['children'],
-  loc: SourceLocation
+  children: CompoundExpressionNode['children']
 ): CompoundExpressionNode {
   return {
     type: NodeTypes.COMPOUND_EXPRESSION,
-    loc,
+    loc: locStub,
     children
   }
 }
 
 export function createCallExpression(
   callee: string,
-  args: CallExpression['arguments'],
-  loc: SourceLocation
+  args: CallExpression['arguments'] = [],
+  loc: SourceLocation = locStub
 ): CallExpression {
   return {
     type: NodeTypes.JS_CALL_EXPRESSION,
@@ -300,7 +309,7 @@ export function createCallExpression(
 export function createFunctionExpression(
   params: ExpressionNode | undefined,
   returns: TemplateChildNode[],
-  loc: SourceLocation
+  loc: SourceLocation = locStub
 ): SlotFunctionExpression {
   return {
     type: NodeTypes.JS_SLOT_FUNCTION,
@@ -310,15 +319,9 @@ export function createFunctionExpression(
   }
 }
 
-// sequence and conditional expressions are never associated with template nodes,
-// so their source locations are just a stub.
-const locStub: SourceLocation = {
-  source: '',
-  start: { line: 1, column: 1, offset: 0 },
-  end: { line: 1, column: 1, offset: 0 }
-}
-
-export function createSequenceExpression(expressions: JSChildNode[]) {
+export function createSequenceExpression(
+  expressions: JSChildNode[]
+): SequenceExpression {
   return {
     type: NodeTypes.JS_SEQUENCE_EXPRESSION,
     expressions,
@@ -330,7 +333,7 @@ export function createConditionalExpression(
   test: ExpressionNode,
   consequent: JSChildNode,
   alternate: JSChildNode
-) {
+): ConditionalExpression {
   return {
     type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
     test,
index 0d01772ad46b731eeaf7600ebb5a388b6da9b4db..5f80c074a647671b68f402900bbbf17bbef19010 100644 (file)
@@ -12,7 +12,6 @@ import {
   CallExpression,
   ArrayExpression,
   ObjectExpression,
-  IfBranchNode,
   SourceLocation,
   Position,
   InterpolationNode,
@@ -196,7 +195,7 @@ export function generate(
         push(`const { ${ast.imports.join(', ')} } = Vue\n`)
       } else {
         // save Vue in a separate variable to avoid collision
-        push(`const _Vue = Vue`)
+        push(`const _Vue = Vue\n`)
       }
     }
     genHoists(ast.hoists, context)
@@ -222,6 +221,7 @@ export function generate(
     if (hasImports) {
       push(`const { ${ast.imports.map(n => `${n}: _${n}`).join(', ')} } = _Vue`)
       newline()
+      newline()
     }
   } else {
     push(`const _ctx = this`)
@@ -471,44 +471,7 @@ function genComment(node: CommentNode, context: CodegenContext) {
 
 // control flow
 function genIf(node: IfNode, context: CodegenContext) {
-  genIfBranch(node.branches[0], node.branches, 1, context)
-}
-
-function genIfBranch(
-  { condition, children }: IfBranchNode,
-  branches: IfBranchNode[],
-  nextIndex: number,
-  context: CodegenContext
-) {
-  if (condition) {
-    // v-if or v-else-if
-    const { push, indent, deindent, newline } = context
-    if (condition.type === NodeTypes.SIMPLE_EXPRESSION) {
-      const needsQuote = !isSimpleIdentifier(condition.content)
-      needsQuote && push(`(`)
-      genExpression(condition, context)
-      needsQuote && push(`)`)
-    } else {
-      genCompoundExpression(condition, context)
-    }
-    indent()
-    context.indentLevel++
-    push(`? `)
-    genChildren(children, context, true)
-    context.indentLevel--
-    newline()
-    push(`: `)
-    if (nextIndex < branches.length) {
-      genIfBranch(branches[nextIndex], branches, nextIndex + 1, context)
-    } else {
-      context.push(`null`)
-    }
-    deindent(true /* without newline */)
-  } else {
-    // v-else
-    __DEV__ && assert(nextIndex === branches.length)
-    genChildren(children, context, true)
-  }
+  genNode(node.codegenNode, context)
 }
 
 function genFor(node: ForNode, context: CodegenContext) {
@@ -623,7 +586,14 @@ function genConditionalExpression(
   context.indentLevel--
   newline()
   push(`: `)
+  const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
+  if (!isNested) {
+    context.indentLevel++
+  }
   genNode(alternate, context)
+  if (!isNested) {
+    context.indentLevel--
+  }
   deindent(true /* without newline */)
 }
 
index 559a44fc681554fd416ec24a3f4b4540af89c993..4ec54b74419ed10a64b3888666f746aaa2effd81 100644 (file)
@@ -62,6 +62,7 @@ export const enum ErrorCodes {
   X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END,
 
   // transform errors
+  X_IF_NO_EXPRESSION,
   X_ELSE_IF_NO_ADJACENT_IF,
   X_ELSE_NO_ADJACENT_IF,
   X_FOR_NO_EXPRESSION,
@@ -138,9 +139,10 @@ export const errorMessages: { [code: number]: string } = {
     'Note that dynamic directive argument connot contain spaces.',
 
   // transform errors
+  [ErrorCodes.X_IF_NO_EXPRESSION]: `v-if/v-else-if is missing expression.`,
   [ErrorCodes.X_ELSE_IF_NO_ADJACENT_IF]: `v-else-if has no adjacent v-if.`,
   [ErrorCodes.X_ELSE_NO_ADJACENT_IF]: `v-else has no adjacent v-if.`,
-  [ErrorCodes.X_FOR_NO_EXPRESSION]: `v-for has no expression.`,
+  [ErrorCodes.X_FOR_NO_EXPRESSION]: `v-for is missing expression.`,
   [ErrorCodes.X_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
   [ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
   [ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
index 79e3d09b1e86efc888bd7e2a70efc6aa23bdad59..6276f21b657e7851b3207e6063740087f91faa99 100644 (file)
@@ -5,6 +5,9 @@ export const PORTAL = `Portal`
 export const COMMENT = `Comment`
 export const TEXT = `Text`
 export const SUSPENSE = `Suspense`
+export const EMPTY = `Empty`
+export const OPEN_BLOCK = `openBlock`
+export const CREATE_BLOCK = `createBlock`
 export const CREATE_VNODE = `createVNode`
 export const RESOLVE_COMPONENT = `resolveComponent`
 export const RESOLVE_DIRECTIVE = `resolveDirective`
index 469f4e09d16d6ea6e269c812aa60e55ae8008fa5..a697cc9de92f4c172ab71d6b0dda2a339865b068 100644 (file)
@@ -148,7 +148,6 @@ export function buildProps(
   patchFlag: number
   dynamicPropNames: string[]
 } {
-  let isStatic = true
   let properties: ObjectExpression['properties'] = []
   const mergeArgs: PropsExpression[] = []
   const runtimeDirectives: DirectiveNode[] = []
@@ -180,13 +179,11 @@ export function buildProps(
             value ? value.content : '',
             true,
             value ? value.loc : loc
-          ),
-          loc
+          )
         )
       )
     } else {
       // directives
-      isStatic = false
       const { name, arg, exp, loc } = prop
 
       // skip v-slot - it is handled by its dedicated transform.
@@ -297,11 +294,6 @@ export function buildProps(
     )
   }
 
-  // hoist the object if it's fully static
-  if (isStatic && propsExpression) {
-    propsExpression = context.hoist(propsExpression)
-  }
-
   // determine the flags to add
   if (hasDynammicKeys) {
     patchFlag |= PatchFlags.FULL_PROPS
@@ -391,8 +383,7 @@ function createDirectiveArgs(
         dir.modifiers.map(modifier =>
           createObjectProperty(
             createSimpleExpression(modifier, true, loc),
-            createSimpleExpression(`true`, false, loc),
-            loc
+            createSimpleExpression(`true`, false, loc)
           )
         ),
         loc
index 439fa19b2bf411200b12e12c1e0f5c156e0403ba..6cbb596271f19e952682bae1a0573195685340b7 100644 (file)
@@ -192,7 +192,7 @@ export function processExpression(
 
   let ret
   if (children.length) {
-    ret = createCompoundExpression(children, node.loc)
+    ret = createCompoundExpression(children)
   } else {
     ret = node
   }
index 1c4e07dd41abe8a7994fc53ea8b5f583adb9f625..24032550fc9715ce2a0e65343e450637c575a6d4 100644 (file)
@@ -43,16 +43,13 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
           arg.content === `name`
         ) {
           // dynamic :name="xxx"
-          slot = createCompoundExpression(
-            [
-              $slots + `[`,
-              ...(exp.type === NodeTypes.SIMPLE_EXPRESSION
-                ? [exp]
-                : exp.children),
-              `]`
-            ],
-            loc
-          )
+          slot = createCompoundExpression([
+            $slots + `[`,
+            ...(exp.type === NodeTypes.SIMPLE_EXPRESSION
+              ? [exp]
+              : exp.children),
+            `]`
+          ])
           nameIndex = i
           break
         }
index b8a71c4b0b5cf8627b769296a6743250f4dc4ff2..fc774e45ff2bc5d7a28d205d739219463a2c5af1 100644 (file)
@@ -30,8 +30,7 @@ export const transformBind: DirectiveTransform = (dir, context) => {
   return {
     props: createObjectProperty(
       arg!,
-      exp || createSimpleExpression('', true, loc),
-      loc
+      exp || createSimpleExpression('', true, loc)
     ),
     needRuntime: false
   }
index 966cc41b40d796ce453d22e1122547fba413e6ff..9c415fd6c5891ac41b105617412a141691ae2273 100644 (file)
@@ -1,6 +1,7 @@
 import {
   createStructuralDirectiveTransform,
-  traverseChildren
+  traverseChildren,
+  TransformContext
 } from '../transform'
 import {
   NodeTypes,
@@ -8,25 +9,66 @@ import {
   ElementNode,
   DirectiveNode,
   IfBranchNode,
-  SimpleExpressionNode
+  SimpleExpressionNode,
+  createSequenceExpression,
+  createCallExpression,
+  createConditionalExpression,
+  ConditionalExpression,
+  CallExpression,
+  createSimpleExpression,
+  JSChildNode,
+  ObjectExpression,
+  createObjectProperty,
+  Property
 } from '../ast'
 import { createCompilerError, ErrorCodes } from '../errors'
 import { processExpression } from './transformExpression'
+import {
+  OPEN_BLOCK,
+  CREATE_BLOCK,
+  EMPTY,
+  FRAGMENT,
+  APPLY_DIRECTIVES
+} from '../runtimeConstants'
+import { isString } from '@vue/shared'
 
 export const transformIf = createStructuralDirectiveTransform(
   /^(if|else|else-if)$/,
   (node, dir, context) => {
+    if (
+      dir.name !== 'else' &&
+      (!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
+    ) {
+      const loc = dir.exp ? dir.exp.loc : node.loc
+      context.onError(createCompilerError(ErrorCodes.X_IF_NO_EXPRESSION, loc))
+      dir.exp = createSimpleExpression(`true`, false, loc)
+    }
+
     if (!__BROWSER__ && context.prefixIdentifiers && dir.exp) {
       // dir.exp can only be simple expression because vIf transform is applied
       // before expression transform.
-      processExpression(dir.exp as SimpleExpressionNode, context)
+      dir.exp = processExpression(dir.exp as SimpleExpressionNode, context)
     }
+
     if (dir.name === 'if') {
+      const codegenNode = createSequenceExpression([
+        createCallExpression(context.helper(OPEN_BLOCK))
+      ])
+
       context.replaceNode({
         type: NodeTypes.IF,
         loc: node.loc,
-        branches: [createIfBranch(node, dir)]
+        branches: [createIfBranch(node, dir)],
+        codegenNode
       })
+
+      // Exit callback. Complete the codegenNode when all children have been
+      // transformed.
+      return () => {
+        codegenNode.expressions.push(
+          createCodegenNodeForBranch(node, dir, 0, context)
+        )
+      }
     } else {
       // locate the adjacent v-if
       const siblings = context.parent!.children
@@ -50,6 +92,25 @@ export const transformIf = createStructuralDirectiveTransform(
           // since the branch was removed, it will not be traversed.
           // make sure to traverse here.
           traverseChildren(branch, context)
+          // attach this branch's codegen node to the v-if root.
+          let parentCondition = sibling.codegenNode
+            .expressions[1] as ConditionalExpression
+          while (true) {
+            if (
+              parentCondition.alternate.type ===
+              NodeTypes.JS_CONDITIONAL_EXPRESSION
+            ) {
+              parentCondition = parentCondition.alternate
+            } else {
+              parentCondition.alternate = createCodegenNodeForBranch(
+                node,
+                dir,
+                sibling.branches.length - 1,
+                context
+              )
+              break
+            }
+          }
         } else {
           context.onError(
             createCompilerError(
@@ -74,3 +135,74 @@ function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
     children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node]
   }
 }
+
+function createCodegenNodeForBranch(
+  node: ElementNode,
+  dir: DirectiveNode,
+  index: number,
+  context: TransformContext
+): ConditionalExpression | CallExpression {
+  if (dir.exp) {
+    return createConditionalExpression(
+      dir.exp,
+      createChildrenCodegenNode(node, index, context),
+      createCallExpression(context.helper(CREATE_BLOCK), [
+        context.helper(EMPTY)
+      ])
+    )
+  } else {
+    return createChildrenCodegenNode(node, index, context)
+  }
+}
+
+function createChildrenCodegenNode(
+  node: ElementNode,
+  index: number,
+  { helper }: TransformContext
+): CallExpression {
+  const isTemplate = node.tagType === ElementTypes.TEMPLATE
+  const keyExp = `{ key: ${index} }`
+  if (isTemplate) {
+    return createCallExpression(helper(CREATE_BLOCK), [
+      helper(FRAGMENT),
+      keyExp,
+      node.children
+    ])
+  } else {
+    let childCodegen = node.codegenNode!
+    if (childCodegen.callee === helper(APPLY_DIRECTIVES)) {
+      childCodegen = childCodegen.arguments[0] as CallExpression
+    }
+    // change child to a block
+    childCodegen.callee = helper(CREATE_BLOCK)
+    // branch key
+    const existingProps = childCodegen.arguments[1]
+    if (!existingProps || existingProps === `null`) {
+      childCodegen.arguments[1] = keyExp
+    } else {
+      // inject branch key if not already have a key
+      const props = existingProps as CallExpression | ObjectExpression
+      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
+        // if doesn't override user provided keys
+        const first = props.arguments[0] as string | JSChildNode
+        if (!isString(first) && first.type === NodeTypes.JS_OBJECT_EXPRESSION) {
+          first.properties.unshift(createKeyProperty(index))
+        } else {
+          props.arguments.unshift(keyExp)
+        }
+      } else {
+        props.properties.unshift(createKeyProperty(index))
+      }
+    }
+    return childCodegen
+  }
+}
+
+function createKeyProperty(index: number): Property {
+  return createObjectProperty(
+    createSimpleExpression(`key`, true),
+    createSimpleExpression(index + '', false)
+  )
+}
index 9cc2f63c8322ccb805fdf1e45a6b47b412a3071b..2c89038f132eed0a0c43389d4863b721e2968fd7 100644 (file)
@@ -27,7 +27,7 @@ export const transformOn: DirectiveTransform = (dir, context) => {
         arg.loc
       )
     } else {
-      eventName = createCompoundExpression([`"on" + (`, arg, `)`], arg.loc)
+      eventName = createCompoundExpression([`"on" + (`, arg, `)`])
     }
   } else {
     // already a compound epxression.
@@ -40,8 +40,7 @@ export const transformOn: DirectiveTransform = (dir, context) => {
   return {
     props: createObjectProperty(
       eventName,
-      exp || createSimpleExpression(`() => {}`, false, loc),
-      loc
+      exp || createSimpleExpression(`() => {}`, false, loc)
     ),
     needRuntime: false
   }
index 92ab4a6b657360274906312d9ca0dc359ad50976..7a1a0f7d33c86d87cee8507457520042c4f75664 100644 (file)
@@ -144,7 +144,6 @@ function buildSlot(
       slotProps,
       children,
       children.length ? children[0].loc : loc
-    ),
-    loc
+    )
   )
 }