]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor(compiler): split slot / slot outlet / slot scope handling into separate...
authorEvan You <yyx990803@gmail.com>
Sat, 28 Sep 2019 04:19:24 +0000 (00:19 -0400)
committerEvan You <yyx990803@gmail.com>
Sat, 28 Sep 2019 04:19:24 +0000 (00:19 -0400)
13 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__/transforms/transformSlotOutlet.spec.ts [new file with mode: 0644]
packages/compiler-core/__tests__/transforms/vSlot.spec.ts
packages/compiler-core/src/ast.ts
packages/compiler-core/src/index.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-core/src/transforms/transfromSlotOutlet.ts [new file with mode: 0644]
packages/compiler-core/src/transforms/vFor.ts
packages/compiler-core/src/transforms/vSlot.ts

index 8331ad7dcb732769c18b403bc604dac266e6054e..ffeada4ccb127879368945dff56c8bbf8bfda4d5 100644 (file)
@@ -8,7 +8,9 @@ return function render() {
       id: \\"foo\\",
       [prop]: bar,
       [foo + bar]: bar
-    }, [createVNode(\\"p\\", { \\"some-key\\": \\"foo\\" })], [
+    }, [
+      createVNode(\\"p\\", { \\"some-key\\": \\"foo\\" })
+    ], [
       foo,
       createVNode(\\"p\\")
     ])
index 2a0a6c1dce3e117a9990bb9b5c188a7dc30df511..f73f3dd7a10a8a447a00fa3c1a85a596ec29de24 100644 (file)
@@ -14,7 +14,9 @@ return function render() {
         ? _createVNode(\\"div\\", 0, \\"yes\\")
         : \\"no\\",
       _renderList(list, (value, index) => {
-        return _createVNode(\\"div\\", 0, [_createVNode(\\"span\\", 0, _toString(value + index))])
+        return _createVNode(\\"div\\", 0, [
+          _createVNode(\\"span\\", 0, _toString(value + index))
+        ])
       })
     ])
   }
@@ -35,7 +37,9 @@ return function render() {
       ? createVNode(\\"div\\", 0, \\"yes\\")
       : \\"no\\",
     renderList(_ctx.list, (value, index) => {
-      return createVNode(\\"div\\", 0, [createVNode(\\"span\\", 0, toString(value + index))])
+      return createVNode(\\"div\\", 0, [
+        createVNode(\\"span\\", 0, toString(value + index))
+      ])
     })
   ])
 }"
@@ -55,7 +59,9 @@ export default function render() {
       ? createVNode(\\"div\\", 0, \\"yes\\")
       : \\"no\\",
     _renderList(_ctx.list, (value, index) => {
-      return createVNode(\\"div\\", 0, [createVNode(\\"span\\", 0, _toString(value + index))])
+      return createVNode(\\"div\\", 0, [
+        createVNode(\\"span\\", 0, _toString(value + index))
+      ])
     })
   ])
 }"
index c4be89a1f0ae8c23e02d20709015a9c5705b8256..1ab8afc66cc7845917cf1e9ec338dab5fbdd64a9 100644 (file)
@@ -553,7 +553,9 @@ describe('compiler: codegen', () => {
       id: "foo",
       [prop]: bar,
       [foo + bar]: bar
-    }, [${CREATE_VNODE}("p", { "some-key": "foo" })], [
+    }, [
+      ${CREATE_VNODE}("p", { "some-key": "foo" })
+    ], [
       foo,
       ${CREATE_VNODE}("p")
     ])`)
diff --git a/packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts b/packages/compiler-core/__tests__/transforms/transformSlotOutlet.spec.ts
new file mode 100644 (file)
index 0000000..f9fa595
--- /dev/null
@@ -0,0 +1,324 @@
+import {
+  CompilerOptions,
+  parse,
+  transform,
+  ElementNode,
+  NodeTypes
+} from '../../src'
+import { transformElement } from '../../src/transforms/transformElement'
+import { transformOn } from '../../src/transforms/vOn'
+import { transformBind } from '../../src/transforms/vBind'
+import { transformExpression } from '../../src/transforms/transformExpression'
+import { RENDER_SLOT } from '../../src/runtimeConstants'
+import { transformSlotOutlet } from '../../src/transforms/transfromSlotOutlet'
+
+function parseWithSlots(template: string, options: CompilerOptions = {}) {
+  const ast = parse(template)
+  transform(ast, {
+    nodeTransforms: [
+      ...(options.prefixIdentifiers ? [transformExpression] : []),
+      transformSlotOutlet,
+      transformElement
+    ],
+    directiveTransforms: {
+      on: transformOn,
+      bind: transformBind
+    },
+    ...options
+  })
+  return ast
+}
+
+describe('compiler: transform <slot> outlets', () => {
+  test('default slot outlet', () => {
+    const ast = parseWithSlots(`<slot/>`)
+    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: `_${RENDER_SLOT}`,
+      arguments: [`$slots.default`]
+    })
+  })
+
+  test('statically named slot outlet', () => {
+    const ast = parseWithSlots(`<slot name="foo" />`)
+    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: `_${RENDER_SLOT}`,
+      arguments: [`$slots.foo`]
+    })
+  })
+
+  test('statically named slot outlet w/ name that needs quotes', () => {
+    const ast = parseWithSlots(`<slot name="foo-bar" />`)
+    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: `_${RENDER_SLOT}`,
+      arguments: [`$slots["foo-bar"]`]
+    })
+  })
+
+  test('dynamically named slot outlet', () => {
+    const ast = parseWithSlots(`<slot :name="foo" />`)
+    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: `_${RENDER_SLOT}`,
+      arguments: [
+        {
+          type: NodeTypes.COMPOUND_EXPRESSION,
+          children: [
+            `$slots[`,
+            {
+              type: NodeTypes.SIMPLE_EXPRESSION,
+              content: `foo`,
+              isStatic: false
+            },
+            `]`
+          ]
+        }
+      ]
+    })
+  })
+
+  test('dynamically named slot outlet w/ prefixIdentifiers: true', () => {
+    const ast = parseWithSlots(`<slot :name="foo + bar" />`, {
+      prefixIdentifiers: true
+    })
+    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: RENDER_SLOT,
+      arguments: [
+        {
+          type: NodeTypes.COMPOUND_EXPRESSION,
+          children: [
+            `_ctx.$slots[`,
+            {
+              type: NodeTypes.SIMPLE_EXPRESSION,
+              content: `_ctx.foo`,
+              isStatic: false
+            },
+            ` + `,
+            {
+              type: NodeTypes.SIMPLE_EXPRESSION,
+              content: `_ctx.bar`,
+              isStatic: false
+            },
+            `]`
+          ]
+        }
+      ]
+    })
+  })
+
+  test('default slot outlet with props', () => {
+    const ast = parseWithSlots(`<slot foo="bar" :baz="qux" />`)
+    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: `_${RENDER_SLOT}`,
+      arguments: [
+        `$slots.default`,
+        {
+          type: NodeTypes.JS_OBJECT_EXPRESSION,
+          properties: [
+            {
+              key: {
+                content: `foo`,
+                isStatic: true
+              },
+              value: {
+                content: `bar`,
+                isStatic: true
+              }
+            },
+            {
+              key: {
+                content: `baz`,
+                isStatic: true
+              },
+              value: {
+                content: `qux`,
+                isStatic: false
+              }
+            }
+          ]
+        }
+      ]
+    })
+  })
+
+  test('statically named slot outlet with props', () => {
+    const ast = parseWithSlots(`<slot name="foo" foo="bar" :baz="qux" />`)
+    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: `_${RENDER_SLOT}`,
+      arguments: [
+        `$slots.foo`,
+        {
+          type: NodeTypes.JS_OBJECT_EXPRESSION,
+          // props should not include name
+          properties: [
+            {
+              key: {
+                content: `foo`,
+                isStatic: true
+              },
+              value: {
+                content: `bar`,
+                isStatic: true
+              }
+            },
+            {
+              key: {
+                content: `baz`,
+                isStatic: true
+              },
+              value: {
+                content: `qux`,
+                isStatic: false
+              }
+            }
+          ]
+        }
+      ]
+    })
+  })
+
+  test('dynamically named slot outlet with props', () => {
+    const ast = parseWithSlots(`<slot :name="foo" foo="bar" :baz="qux" />`)
+    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: `_${RENDER_SLOT}`,
+      arguments: [
+        {
+          type: NodeTypes.COMPOUND_EXPRESSION,
+          children: [`$slots[`, { content: `foo` }, `]`]
+        },
+        {
+          type: NodeTypes.JS_OBJECT_EXPRESSION,
+          // props should not include name
+          properties: [
+            {
+              key: {
+                content: `foo`,
+                isStatic: true
+              },
+              value: {
+                content: `bar`,
+                isStatic: true
+              }
+            },
+            {
+              key: {
+                content: `baz`,
+                isStatic: true
+              },
+              value: {
+                content: `qux`,
+                isStatic: false
+              }
+            }
+          ]
+        }
+      ]
+    })
+  })
+
+  test('default slot outlet with fallback', () => {
+    const ast = parseWithSlots(`<slot><div/></slot>`)
+    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: `_${RENDER_SLOT}`,
+      arguments: [
+        `$slots.default`,
+        `{}`,
+        [
+          {
+            type: NodeTypes.ELEMENT,
+            tag: `div`
+          }
+        ]
+      ]
+    })
+  })
+
+  test('named slot outlet with fallback', () => {
+    const ast = parseWithSlots(`<slot name="foo"><div/></slot>`)
+    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: `_${RENDER_SLOT}`,
+      arguments: [
+        `$slots.foo`,
+        `{}`,
+        [
+          {
+            type: NodeTypes.ELEMENT,
+            tag: `div`
+          }
+        ]
+      ]
+    })
+  })
+
+  test('default slot outlet with props & fallback', () => {
+    const ast = parseWithSlots(`<slot :foo="bar"><div/></slot>`)
+    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: `_${RENDER_SLOT}`,
+      arguments: [
+        `$slots.default`,
+        {
+          type: NodeTypes.JS_OBJECT_EXPRESSION,
+          properties: [
+            {
+              key: {
+                content: `foo`,
+                isStatic: true
+              },
+              value: {
+                content: `bar`,
+                isStatic: false
+              }
+            }
+          ]
+        },
+        [
+          {
+            type: NodeTypes.ELEMENT,
+            tag: `div`
+          }
+        ]
+      ]
+    })
+  })
+
+  test('named slot outlet with props & fallback', () => {
+    const ast = parseWithSlots(`<slot name="foo" :foo="bar"><div/></slot>`)
+    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: `_${RENDER_SLOT}`,
+      arguments: [
+        `$slots.foo`,
+        {
+          type: NodeTypes.JS_OBJECT_EXPRESSION,
+          properties: [
+            {
+              key: {
+                content: `foo`,
+                isStatic: true
+              },
+              value: {
+                content: `bar`,
+                isStatic: false
+              }
+            }
+          ]
+        },
+        [
+          {
+            type: NodeTypes.ELEMENT,
+            tag: `div`
+          }
+        ]
+      ]
+    })
+  })
+})
index d7a36196323dea17c7caba351be5d376ea5f0b25..295d8d8d80ef45068f7c45228b47467455078838 100644 (file)
@@ -1,23 +1,17 @@
-import {
-  CompilerOptions,
-  parse,
-  transform,
-  ElementNode,
-  NodeTypes,
-  generate
-} from '../../src'
+import { CompilerOptions, parse, transform, generate } from '../../src'
 import { transformElement } from '../../src/transforms/transformElement'
 import { transformOn } from '../../src/transforms/vOn'
 import { transformBind } from '../../src/transforms/vBind'
 import { transformExpression } from '../../src/transforms/transformExpression'
-import { RENDER_SLOT } from '../../src/runtimeConstants'
+import { trackSlotScopes } from '../../src/transforms/vSlot'
 
 function parseWithSlots(template: string, options: CompilerOptions = {}) {
   const ast = parse(template)
   transform(ast, {
     nodeTransforms: [
-      ...(options.prefixIdentifiers ? [transformExpression] : []),
-      // slot transform is part of transformElement
+      ...(options.prefixIdentifiers
+        ? [transformExpression, trackSlotScopes]
+        : []),
       transformElement
     ],
     directiveTransforms: {
@@ -29,302 +23,19 @@ function parseWithSlots(template: string, options: CompilerOptions = {}) {
   return ast
 }
 
-describe('compiler: transform slots', () => {
-  test('default slot outlet', () => {
-    const ast = parseWithSlots(`<slot/>`)
-    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
-      type: NodeTypes.JS_CALL_EXPRESSION,
-      callee: `_${RENDER_SLOT}`,
-      arguments: [`$slots.default`]
-    })
-  })
-
-  test('statically named slot outlet', () => {
-    const ast = parseWithSlots(`<slot name="foo" />`)
-    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
-      type: NodeTypes.JS_CALL_EXPRESSION,
-      callee: `_${RENDER_SLOT}`,
-      arguments: [`$slots.foo`]
-    })
-  })
-
-  test('statically named slot outlet w/ name that needs quotes', () => {
-    const ast = parseWithSlots(`<slot name="foo-bar" />`)
-    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
-      type: NodeTypes.JS_CALL_EXPRESSION,
-      callee: `_${RENDER_SLOT}`,
-      arguments: [`$slots["foo-bar"]`]
-    })
-  })
-
-  test('dynamically named slot outlet', () => {
-    const ast = parseWithSlots(`<slot :name="foo" />`)
-    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
-      type: NodeTypes.JS_CALL_EXPRESSION,
-      callee: `_${RENDER_SLOT}`,
-      arguments: [
-        {
-          type: NodeTypes.COMPOUND_EXPRESSION,
-          children: [
-            `$slots[`,
-            {
-              type: NodeTypes.SIMPLE_EXPRESSION,
-              content: `foo`,
-              isStatic: false
-            },
-            `]`
-          ]
-        }
-      ]
-    })
-  })
-
-  test('dynamically named slot outlet w/ prefixIdentifiers: true', () => {
-    const ast = parseWithSlots(`<slot :name="foo + bar" />`, {
-      prefixIdentifiers: true
-    })
-    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
-      type: NodeTypes.JS_CALL_EXPRESSION,
-      callee: RENDER_SLOT,
-      arguments: [
-        {
-          type: NodeTypes.COMPOUND_EXPRESSION,
-          children: [
-            `_ctx.$slots[`,
-            {
-              type: NodeTypes.SIMPLE_EXPRESSION,
-              content: `_ctx.foo`,
-              isStatic: false
-            },
-            ` + `,
-            {
-              type: NodeTypes.SIMPLE_EXPRESSION,
-              content: `_ctx.bar`,
-              isStatic: false
-            },
-            `]`
-          ]
-        }
-      ]
-    })
-  })
-
-  test('default slot outlet with props', () => {
-    const ast = parseWithSlots(`<slot foo="bar" :baz="qux" />`)
-    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
-      type: NodeTypes.JS_CALL_EXPRESSION,
-      callee: `_${RENDER_SLOT}`,
-      arguments: [
-        `$slots.default`,
-        {
-          type: NodeTypes.JS_OBJECT_EXPRESSION,
-          properties: [
-            {
-              key: {
-                content: `foo`,
-                isStatic: true
-              },
-              value: {
-                content: `bar`,
-                isStatic: true
-              }
-            },
-            {
-              key: {
-                content: `baz`,
-                isStatic: true
-              },
-              value: {
-                content: `qux`,
-                isStatic: false
-              }
-            }
-          ]
-        }
-      ]
-    })
-  })
-
-  test('statically named slot outlet with props', () => {
-    const ast = parseWithSlots(`<slot name="foo" foo="bar" :baz="qux" />`)
-    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
-      type: NodeTypes.JS_CALL_EXPRESSION,
-      callee: `_${RENDER_SLOT}`,
-      arguments: [
-        `$slots.foo`,
-        {
-          type: NodeTypes.JS_OBJECT_EXPRESSION,
-          // props should not include name
-          properties: [
-            {
-              key: {
-                content: `foo`,
-                isStatic: true
-              },
-              value: {
-                content: `bar`,
-                isStatic: true
-              }
-            },
-            {
-              key: {
-                content: `baz`,
-                isStatic: true
-              },
-              value: {
-                content: `qux`,
-                isStatic: false
-              }
-            }
-          ]
-        }
-      ]
-    })
-  })
-
-  test('dynamically named slot outlet with props', () => {
-    const ast = parseWithSlots(`<slot :name="foo" foo="bar" :baz="qux" />`)
-    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
-      type: NodeTypes.JS_CALL_EXPRESSION,
-      callee: `_${RENDER_SLOT}`,
-      arguments: [
-        {
-          type: NodeTypes.COMPOUND_EXPRESSION,
-          children: [`$slots[`, { content: `foo` }, `]`]
-        },
-        {
-          type: NodeTypes.JS_OBJECT_EXPRESSION,
-          // props should not include name
-          properties: [
-            {
-              key: {
-                content: `foo`,
-                isStatic: true
-              },
-              value: {
-                content: `bar`,
-                isStatic: true
-              }
-            },
-            {
-              key: {
-                content: `baz`,
-                isStatic: true
-              },
-              value: {
-                content: `qux`,
-                isStatic: false
-              }
-            }
-          ]
-        }
-      ]
-    })
-  })
-
-  test('default slot outlet with fallback', () => {
-    const ast = parseWithSlots(`<slot><div/></slot>`)
-    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
-      type: NodeTypes.JS_CALL_EXPRESSION,
-      callee: `_${RENDER_SLOT}`,
-      arguments: [
-        `$slots.default`,
-        `{}`,
-        [
-          {
-            type: NodeTypes.ELEMENT,
-            tag: `div`
-          }
-        ]
-      ]
-    })
-  })
-
-  test('named slot outlet with fallback', () => {
-    const ast = parseWithSlots(`<slot name="foo"><div/></slot>`)
-    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
-      type: NodeTypes.JS_CALL_EXPRESSION,
-      callee: `_${RENDER_SLOT}`,
-      arguments: [
-        `$slots.foo`,
-        `{}`,
-        [
-          {
-            type: NodeTypes.ELEMENT,
-            tag: `div`
-          }
-        ]
-      ]
-    })
-  })
-
-  test('default slot outlet with props & fallback', () => {
-    const ast = parseWithSlots(`<slot :foo="bar"><div/></slot>`)
-    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
-      type: NodeTypes.JS_CALL_EXPRESSION,
-      callee: `_${RENDER_SLOT}`,
-      arguments: [
-        `$slots.default`,
-        {
-          type: NodeTypes.JS_OBJECT_EXPRESSION,
-          properties: [
-            {
-              key: {
-                content: `foo`,
-                isStatic: true
-              },
-              value: {
-                content: `bar`,
-                isStatic: false
-              }
-            }
-          ]
-        },
-        [
-          {
-            type: NodeTypes.ELEMENT,
-            tag: `div`
-          }
-        ]
-      ]
-    })
-  })
-
-  test('named slot outlet with props & fallback', () => {
-    const ast = parseWithSlots(`<slot name="foo" :foo="bar"><div/></slot>`)
-    expect((ast.children[0] as ElementNode).codegenNode).toMatchObject({
-      type: NodeTypes.JS_CALL_EXPRESSION,
-      callee: `_${RENDER_SLOT}`,
-      arguments: [
-        `$slots.foo`,
-        {
-          type: NodeTypes.JS_OBJECT_EXPRESSION,
-          properties: [
-            {
-              key: {
-                content: `foo`,
-                isStatic: true
-              },
-              value: {
-                content: `bar`,
-                isStatic: false
-              }
-            }
-          ]
-        },
-        [
-          {
-            type: NodeTypes.ELEMENT,
-            tag: `div`
-          }
-        ]
-      ]
-    })
-  })
-
+describe('compiler: transform component slots', () => {
   test('generate slot', () => {
-    const ast = parseWithSlots(`<Comp><div/></Comp>`)
-    const { code } = generate(ast)
+    const ast = parseWithSlots(
+      `
+<Comp>
+  <Comp v-slot="{ dur }">
+    hello {{ dur }}
+  </Comp>
+</Comp>
+`,
+      { prefixIdentifiers: true }
+    )
+    const { code } = generate(ast, { prefixIdentifiers: true })
     console.log(code)
   })
 })
index d86b325ac3ffe2b0028dc72e2b7573e8edf3e70c..edb924c8aa804d79be581782f77bbf12441d169d 100644 (file)
@@ -117,6 +117,9 @@ export interface SimpleExpressionNode extends Node {
   type: NodeTypes.SIMPLE_EXPRESSION
   content: string
   isStatic: boolean
+  // an expression parsed as the params of a function will track
+  // the identifiers declared inside the function body.
+  identifiers?: string[]
 }
 
 export interface InterpolationNode extends Node {
@@ -128,6 +131,9 @@ export interface InterpolationNode extends Node {
 export interface CompoundExpressionNode extends Node {
   type: NodeTypes.COMPOUND_EXPRESSION
   children: (SimpleExpressionNode | string)[]
+  // an expression parsed as the params of a function will track
+  // the identifiers declared inside the function body.
+  identifiers?: string[]
 }
 
 export interface IfNode extends Node {
index 64a2d4be10ad9f796bc8b8e26ea1c77f2cdc38b3..53badd58fb348903dc60a0d951a2f9af37af4eb4 100644 (file)
@@ -5,12 +5,14 @@ import { RootNode } from './ast'
 import { isString } from '@vue/shared'
 import { transformIf } from './transforms/vIf'
 import { transformFor } from './transforms/vFor'
+import { transformExpression } from './transforms/transformExpression'
+import { transformStyle } from './transforms/transformStyle'
+import { transformSlotOutlet } from './transforms/transfromSlotOutlet'
 import { transformElement } from './transforms/transformElement'
 import { transformOn } from './transforms/vOn'
 import { transformBind } from './transforms/vBind'
-import { transformExpression } from './transforms/transformExpression'
 import { defaultOnError, createCompilerError, ErrorCodes } from './errors'
-import { transformStyle } from './transforms/transformStyle'
+import { trackSlotScopes } from './transforms/vSlot'
 
 export type CompilerOptions = ParserOptions & TransformOptions & CodegenOptions
 
@@ -41,8 +43,9 @@ export function baseCompile(
     nodeTransforms: [
       transformIf,
       transformFor,
-      ...(prefixIdentifiers ? [transformExpression] : []),
+      ...(prefixIdentifiers ? [transformExpression, trackSlotScopes] : []),
       transformStyle,
+      transformSlotOutlet,
       transformElement,
       ...(options.nodeTransforms || []) // user transforms
     ],
index bf5fdf91706d6ba1e758b0a3ad105ad68c6fd6ec..0587434d65a6faf59cdb11412b2b73d114418c84 100644 (file)
@@ -8,8 +8,7 @@ import {
   Property,
   ExpressionNode,
   createSimpleExpression,
-  JSChildNode,
-  SimpleExpressionNode
+  JSChildNode
 } from './ast'
 import { isString, isArray } from '@vue/shared'
 import { CompilerError, defaultOnError } from './errors'
@@ -64,8 +63,8 @@ export interface TransformContext extends Required<TransformOptions> {
   replaceNode(node: ChildNode): void
   removeNode(node?: ChildNode): void
   onNodeRemoved: () => void
-  addIdentifier(exp: SimpleExpressionNode): void
-  removeIdentifier(exp: SimpleExpressionNode): void
+  addIdentifier(id: string): void
+  removeIdentifier(id: string): void
   hoist(exp: JSChildNode): ExpressionNode
 }
 
@@ -127,15 +126,15 @@ function createTransformContext(
       context.parent.children.splice(removalIndex, 1)
     },
     onNodeRemoved: () => {},
-    addIdentifier({ content }) {
+    addIdentifier(id) {
       const { identifiers } = context
-      if (identifiers[content] === undefined) {
-        identifiers[content] = 0
+      if (identifiers[id] === undefined) {
+        identifiers[id] = 0
       }
-      ;(identifiers[content] as number)++
+      ;(identifiers[id] as number)++
     },
-    removeIdentifier({ content }) {
-      ;(context.identifiers[content] as number)--
+    removeIdentifier(id) {
+      ;(context.identifiers[id] as number)--
     },
     hoist(exp) {
       context.hoists.push(exp)
index dab39b438a76a7eb5d19a038b2e6cf12fefc42b4..a076328affdb0a2a3d71a8f47a8569e265ea21bc 100644 (file)
@@ -27,7 +27,7 @@ import {
   TO_HANDLERS
 } from '../runtimeConstants'
 import { getInnerRange } from '../utils'
-import { buildSlotOutlet, buildSlots } from './vSlot'
+import { buildSlots } from './vSlot'
 
 const toValidId = (str: string): string => str.replace(/[^\w]/g, '')
 
@@ -95,10 +95,7 @@ export const transformElement: NodeTransform = (node, context) => {
       } else {
         node.codegenNode = vnode
       }
-    } else if (node.tagType === ElementTypes.SLOT) {
-      buildSlotOutlet(node, context)
     }
-    // node.tagType can also be TEMPLATE, in which case nothing needs to be done
   }
 }
 
index 35a076cb42c60bd38196fd51cef82a5a0a3351ae..7b90f874120974d1cd7749d40992d4582c55e08d 100644 (file)
@@ -87,12 +87,12 @@ export function processExpression(
     _parseScript || (_parseScript = require('meriyah').parseScript)
   const walk = _walk || (_walk = require('estree-walker').walk)
 
-  let ast
+  let ast: any
   // if the expression is supposed to be used in a function params position
   // we need to parse it differently.
   const source = `(${node.content})${asParams ? `=>{}` : ``}`
   try {
-    ast = parseScript(source, { ranges: true }) as any
+    ast = parseScript(source, { ranges: true })
   } catch (e) {
     context.onError(e)
     return node
@@ -139,11 +139,22 @@ export function processExpression(
                   parent.right === child
                 )
               ) {
-                knownIds[child.name] = true
+                const { name } = child
+                if (
+                  (node as any)._scopeIds &&
+                  (node as any)._scopeIds.has(name)
+                ) {
+                  return
+                }
+                if (name in knownIds) {
+                  knownIds[name]++
+                } else {
+                  knownIds[name] = 1
+                }
                 ;(
                   (node as any)._scopeIds ||
                   ((node as any)._scopeIds = new Set())
-                ).add(child.name)
+                ).add(name)
               }
             }
           })
@@ -151,9 +162,12 @@ export function processExpression(
       }
     },
     leave(node: any) {
-      if (node._scopeIds) {
+      if (node !== ast.body[0].expression && node._scopeIds) {
         node._scopeIds.forEach((id: string) => {
-          delete knownIds[id]
+          knownIds[id]--
+          if (knownIds[id] === 0) {
+            delete knownIds[id]
+          }
         })
       }
     }
@@ -185,11 +199,14 @@ export function processExpression(
     }
   })
 
+  let ret
   if (children.length) {
-    return createCompoundExpression(children, node.loc)
+    ret = createCompoundExpression(children, node.loc)
   } else {
-    return node
+    ret = node
   }
+  ret.identifiers = Object.keys(knownIds)
+  return ret
 }
 
 const isFunction = (node: Node): node is Function =>
diff --git a/packages/compiler-core/src/transforms/transfromSlotOutlet.ts b/packages/compiler-core/src/transforms/transfromSlotOutlet.ts
new file mode 100644 (file)
index 0000000..99f7354
--- /dev/null
@@ -0,0 +1,98 @@
+import { NodeTransform } from '../transform'
+import {
+  NodeTypes,
+  ElementTypes,
+  CompoundExpressionNode,
+  createCompoundExpression,
+  CallExpression,
+  createCallExpression
+} from '../ast'
+import { isSimpleIdentifier } from '../utils'
+import { buildProps } from './transformElement'
+import { createCompilerError, ErrorCodes } from '../errors'
+import { RENDER_SLOT } from '../runtimeConstants'
+
+export const transformSlotOutlet: NodeTransform = (node, context) => {
+  if (node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT) {
+    const { props, children, loc } = node
+    const $slots = context.prefixIdentifiers ? `_ctx.$slots` : `$slots`
+    let slot: string | CompoundExpressionNode = $slots + `.default`
+
+    // check for <slot name="xxx" OR :name="xxx" />
+    let nameIndex: number = -1
+    for (let i = 0; i < props.length; i++) {
+      const prop = props[i]
+      if (prop.type === NodeTypes.ATTRIBUTE) {
+        if (prop.name === `name` && prop.value) {
+          // static name="xxx"
+          const name = prop.value.content
+          const accessor = isSimpleIdentifier(name)
+            ? `.${name}`
+            : `[${JSON.stringify(name)}]`
+          slot = `${$slots}${accessor}`
+          nameIndex = i
+          break
+        }
+      } else if (prop.name === `bind`) {
+        const { arg, exp } = prop
+        if (
+          arg &&
+          exp &&
+          arg.type === NodeTypes.SIMPLE_EXPRESSION &&
+          arg.isStatic &&
+          arg.content === `name`
+        ) {
+          // dynamic :name="xxx"
+          slot = createCompoundExpression(
+            [
+              $slots + `[`,
+              ...(exp.type === NodeTypes.SIMPLE_EXPRESSION
+                ? [exp]
+                : exp.children),
+              `]`
+            ],
+            loc
+          )
+          nameIndex = i
+          break
+        }
+      }
+    }
+
+    const slotArgs: CallExpression['arguments'] = [slot]
+    const propsWithoutName =
+      nameIndex > -1
+        ? props.slice(0, nameIndex).concat(props.slice(nameIndex + 1))
+        : props
+    const hasProps = propsWithoutName.length
+    if (hasProps) {
+      const { props: propsExpression, directives } = buildProps(
+        propsWithoutName,
+        loc,
+        context
+      )
+      if (directives.length) {
+        context.onError(
+          createCompilerError(
+            ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
+            directives[0].loc
+          )
+        )
+      }
+      slotArgs.push(propsExpression)
+    }
+
+    if (children.length) {
+      if (!hasProps) {
+        slotArgs.push(`{}`)
+      }
+      slotArgs.push(children)
+    }
+
+    node.codegenNode = createCallExpression(
+      context.helper(RENDER_SLOT),
+      slotArgs,
+      loc
+    )
+  }
+}
index a045f2270af364e67935975d36e8494bb6564aa0..794f3968b2e6e139e2bb29baffbea310add6bd3f 100644 (file)
@@ -43,15 +43,15 @@ export const transformFor = createStructuralDirectiveTransform(
         const { addIdentifier, removeIdentifier } = context
 
         // inject identifiers to context
-        value && addIdentifier(value)
-        key && addIdentifier(key)
-        index && addIdentifier(index)
+        value && addIdentifier(value.content)
+        key && addIdentifier(key.content)
+        index && addIdentifier(index.content)
 
         return () => {
           // remove injected identifiers on exit
-          value && removeIdentifier(value)
-          key && removeIdentifier(key)
-          index && removeIdentifier(index)
+          value && removeIdentifier(value.content)
+          key && removeIdentifier(key.content)
+          index && removeIdentifier(index.content)
         }
       } else {
         context.onError(
index 670371799e0e48b3e9ae12a49d73a6a7a1c3fa32..7ba344f2c470b4bd192f700b87b8ffa287675ff2 100644 (file)
@@ -3,10 +3,6 @@ import {
   ObjectExpression,
   createObjectExpression,
   NodeTypes,
-  createCompoundExpression,
-  createCallExpression,
-  CompoundExpressionNode,
-  CallExpression,
   createObjectProperty,
   createSimpleExpression,
   createFunctionExpression,
@@ -17,16 +13,37 @@ import {
   ChildNode,
   SourceLocation
 } from '../ast'
-import { TransformContext } from '../transform'
-import { buildProps } from './transformElement'
+import { TransformContext, NodeTransform } from '../transform'
 import { createCompilerError, ErrorCodes } from '../errors'
-import { isSimpleIdentifier } from '../utils'
-import { RENDER_SLOT } from '../runtimeConstants'
 import { isString } from '@vue/shared'
 
 const isVSlot = (p: ElementNode['props'][0]): p is DirectiveNode =>
   p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
 
+// A NodeTransform that tracks scope identifiers for scoped slots so that they
+// don't get prefixed by transformExpression. This transform is only applied
+// in non-browser builds with { prefixIdentifiers: true }
+export const trackSlotScopes: NodeTransform = (node, context) => {
+  if (
+    node.type === NodeTypes.ELEMENT &&
+    (node.tagType === ElementTypes.COMPONENT ||
+      node.tagType === ElementTypes.TEMPLATE)
+  ) {
+    const vSlot = node.props.find(isVSlot)
+    if (vSlot && vSlot.exp) {
+      const { identifiers } = vSlot.exp
+      if (identifiers) {
+        identifiers.forEach(context.addIdentifier)
+        return () => {
+          identifiers.forEach(context.removeIdentifier)
+        }
+      }
+    }
+  }
+}
+
+// Instead of being a DirectiveTransform, v-slot processing is called during
+// transformElement to build the slots object for a component.
 export function buildSlots(
   { props, children, loc }: ElementNode,
   context: TransformContext
@@ -128,86 +145,3 @@ function buildSlot(
     loc
   )
 }
-
-export function buildSlotOutlet(node: ElementNode, context: TransformContext) {
-  const { props, children, loc } = node
-  const $slots = context.prefixIdentifiers ? `_ctx.$slots` : `$slots`
-  let slot: string | CompoundExpressionNode = $slots + `.default`
-
-  // check for <slot name="xxx" OR :name="xxx" />
-  let nameIndex: number = -1
-  for (let i = 0; i < props.length; i++) {
-    const prop = props[i]
-    if (prop.type === NodeTypes.ATTRIBUTE) {
-      if (prop.name === `name` && prop.value) {
-        // static name="xxx"
-        const name = prop.value.content
-        const accessor = isSimpleIdentifier(name)
-          ? `.${name}`
-          : `[${JSON.stringify(name)}]`
-        slot = `${$slots}${accessor}`
-        nameIndex = i
-        break
-      }
-    } else if (prop.name === `bind`) {
-      const { arg, exp } = prop
-      if (
-        arg &&
-        exp &&
-        arg.type === NodeTypes.SIMPLE_EXPRESSION &&
-        arg.isStatic &&
-        arg.content === `name`
-      ) {
-        // dynamic :name="xxx"
-        slot = createCompoundExpression(
-          [
-            $slots + `[`,
-            ...(exp.type === NodeTypes.SIMPLE_EXPRESSION
-              ? [exp]
-              : exp.children),
-            `]`
-          ],
-          loc
-        )
-        nameIndex = i
-        break
-      }
-    }
-  }
-
-  const slotArgs: CallExpression['arguments'] = [slot]
-  const propsWithoutName =
-    nameIndex > -1
-      ? props.slice(0, nameIndex).concat(props.slice(nameIndex + 1))
-      : props
-  const hasProps = propsWithoutName.length
-  if (hasProps) {
-    const { props: propsExpression, directives } = buildProps(
-      propsWithoutName,
-      loc,
-      context
-    )
-    if (directives.length) {
-      context.onError(
-        createCompilerError(
-          ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
-          directives[0].loc
-        )
-      )
-    }
-    slotArgs.push(propsExpression)
-  }
-
-  if (children.length) {
-    if (!hasProps) {
-      slotArgs.push(`{}`)
-    }
-    slotArgs.push(children)
-  }
-
-  node.codegenNode = createCallExpression(
-    context.helper(RENDER_SLOT),
-    slotArgs,
-    loc
-  )
-}