Quellcode durchsuchen

feat(compiler-vapor): v-slot props + v-slot on component (#223)

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
Rizumu Ayaka vor 1 Jahr
Ursprung
Commit
208dbc6d65

+ 5 - 5
packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap

@@ -5,7 +5,7 @@ exports[`compiler: v-for > array de-structured value 1`] = `
 const t0 = _template("<div></div>")
 
 export function render(_ctx) {
-  const n0 = _createFor(() => (_ctx.list), _withDestructure((_state, [[id, ...other], index] = _state) => [id, other, index], (_ctx0) => {
+  const n0 = _createFor(() => (_ctx.list), _withDestructure(([[id, ...other], index]) => [id, other, index], (_ctx0) => {
     const n2 = t0()
     _renderEffect(() => _setText(n2, _ctx0[0] + _ctx0[1] + _ctx0[2]))
     return n2
@@ -53,9 +53,9 @@ const t1 = _template("<div></div>")
 export function render(_ctx) {
   const n0 = _createFor(() => (_ctx.list), (_ctx0) => {
     const n5 = t1()
-    const n2 = _createFor(() => (_ctx0[0]), (_ctx2) => {
+    const n2 = _createFor(() => (_ctx0[0]), (_ctx1) => {
       const n4 = t0()
-      _renderEffect(() => _setText(n4, _ctx2[0]+_ctx0[0]))
+      _renderEffect(() => _setText(n4, _ctx1[0]+_ctx0[0]))
       return n4
     })
     _insert(n2, n5)
@@ -70,7 +70,7 @@ exports[`compiler: v-for > object de-structured value 1`] = `
 const t0 = _template("<div></div>")
 
 export function render(_ctx) {
-  const n0 = _createFor(() => (_ctx.list), _withDestructure((_state, [{ id, ...other }, index] = _state) => [id, other, index], (_ctx0) => {
+  const n0 = _createFor(() => (_ctx.list), _withDestructure(([{ id, ...other }, index]) => [id, other, index], (_ctx0) => {
     const n2 = t0()
     _renderEffect(() => _setText(n2, _ctx0[0] + _ctx0[1] + _ctx0[2]))
     return n2
@@ -84,7 +84,7 @@ exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = `
 const t0 = _template("<div></div>")
 
 export function render(_ctx) {
-  const n0 = _createFor(() => (_ctx.list), _withDestructure((_state, [{ foo = bar, baz: [qux = quux] }] = _state) => [foo, qux], (_ctx0) => {
+  const n0 = _createFor(() => (_ctx.list), _withDestructure(([{ foo = bar, baz: [qux = quux] }]) => [foo, qux], (_ctx0) => {
     const n2 = t0()
     _renderEffect(() => _setText(n2, _ctx0[0] + _ctx.bar + _ctx.baz + _ctx0[1] + _ctx.quux))
     return n2

+ 62 - 18
packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap

@@ -18,17 +18,17 @@ export function render(_ctx) {
 `;
 
 exports[`compiler: transform slot > dynamic slots name w/ v-for 1`] = `
-"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createForSlots as _createForSlots, template as _template } from 'vue/vapor';
+"import { resolveComponent as _resolveComponent, createComponent as _createComponent, withDestructure as _withDestructure, createForSlots as _createForSlots, template as _template } from 'vue/vapor';
 const t0 = _template("foo")
 
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
   const n2 = _createComponent(_component_Comp, null, null, () => [_createForSlots(_ctx.list, (item) => ({
     name: item, 
-    fn: () => {
+    fn: _withDestructure(({ bar }) => [bar], (_ctx0) => {
       const n0 = t0()
       return n0
-    }
+    })
   }))], true)
   return n2
 }"
@@ -52,7 +52,7 @@ export function render(_ctx) {
 `;
 
 exports[`compiler: transform slot > dynamic slots name w/ v-if / v-else[-if] 1`] = `
-"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
+"import { resolveComponent as _resolveComponent, createComponent as _createComponent, withDestructure as _withDestructure, template as _template } from 'vue/vapor';
 const t0 = _template("condition slot")
 const t1 = _template("another condition")
 const t2 = _template("else condition")
@@ -71,10 +71,10 @@ export function render(_ctx) {
     : _ctx.anotherCondition
       ? {
         name: "condition", 
-        fn: () => {
+        fn: _withDestructure(({ foo, bar }) => [foo, bar], (_ctx0) => {
           const n2 = t1()
           return n2
-        }, 
+        }), 
         key: "1"
       }
       : {
@@ -126,20 +126,64 @@ export function render(_ctx) {
 }"
 `;
 
-exports[`compiler: transform slot > nested slots 1`] = `
-"import { resolveComponent as _resolveComponent, createComponent as _createComponent, template as _template } from 'vue/vapor';
-const t0 = _template("<div></div>")
+exports[`compiler: transform slot > nested slots scoping 1`] = `
+"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createTextNode as _createTextNode, withDestructure as _withDestructure, template as _template } from 'vue/vapor';
+const t0 = _template(" ")
 
 export function render(_ctx) {
-  const _component_Bar = _resolveComponent("Bar")
-  const _component_Foo = _resolveComponent("Foo")
-  const n3 = _createComponent(_component_Foo, null, { one: () => {
-    const n1 = _createComponent(_component_Bar, null, { default: () => {
-      const n0 = t0()
+  const _component_Inner = _resolveComponent("Inner")
+  const _component_Comp = _resolveComponent("Comp")
+  const n5 = _createComponent(_component_Comp, null, { default: _withDestructure(({ foo }) => [foo], (_ctx0) => {
+    const n2 = t0()
+    const n1 = _createComponent(_component_Inner, null, { default: _withDestructure(({ bar }) => [bar], (_ctx1) => {
+      const n0 = _createTextNode(() => [_ctx0[0] + _ctx1[0] + _ctx.baz])
       return n0
-    } })
-    return n1
-  } }, null, true)
-  return n3
+    }) })
+    const n3 = _createTextNode(() => [_ctx0[0] + _ctx.bar + _ctx.baz])
+    return [n1, n2, n3]
+  }) }, null, true)
+  return n5
+}"
+`;
+
+exports[`compiler: transform slot > on component dynamically named slot 1`] = `
+"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createTextNode as _createTextNode, withDestructure as _withDestructure } from 'vue/vapor';
+
+export function render(_ctx) {
+  const _component_Comp = _resolveComponent("Comp")
+  const n1 = _createComponent(_component_Comp, null, {  }, () => [{
+    name: _ctx.named, 
+    fn: _withDestructure(({ foo }) => [foo], (_ctx0) => {
+      const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar])
+      return n0
+    })
+  }], true)
+  return n1
+}"
+`;
+
+exports[`compiler: transform slot > on component named slot 1`] = `
+"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createTextNode as _createTextNode, withDestructure as _withDestructure } from 'vue/vapor';
+
+export function render(_ctx) {
+  const _component_Comp = _resolveComponent("Comp")
+  const n1 = _createComponent(_component_Comp, null, { named: _withDestructure(({ foo }) => [foo], (_ctx0) => {
+    const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar])
+    return n0
+  }) }, null, true)
+  return n1
+}"
+`;
+
+exports[`compiler: transform slot > on-component default slot 1`] = `
+"import { resolveComponent as _resolveComponent, createComponent as _createComponent, createTextNode as _createTextNode, withDestructure as _withDestructure } from 'vue/vapor';
+
+export function render(_ctx) {
+  const _component_Comp = _resolveComponent("Comp")
+  const n1 = _createComponent(_component_Comp, null, { default: _withDestructure(({ foo }) => [foo], (_ctx0) => {
+    const n0 = _createTextNode(() => [_ctx0[0] + _ctx.bar])
+    return n0
+  }) }, null, true)
+  return n1
 }"
 `;

+ 5 - 11
packages/compiler-vapor/__tests__/transforms/vFor.spec.ts

@@ -93,8 +93,8 @@ describe('compiler: v-for', () => {
     )
     expect(code).matchSnapshot()
     expect(code).contains(`_createFor(() => (_ctx.list), (_ctx0) => {`)
-    expect(code).contains(`_createFor(() => (_ctx0[0]), (_ctx2) => {`)
-    expect(code).contains(`_ctx2[0]+_ctx0[0]`)
+    expect(code).contains(`_createFor(() => (_ctx0[0]), (_ctx1) => {`)
+    expect(code).contains(`_ctx1[0]+_ctx0[0]`)
     expect(ir.template).toEqual(['<span></span>', '<div></div>'])
     expect(ir.block.operation).toMatchObject([
       {
@@ -129,9 +129,7 @@ describe('compiler: v-for', () => {
       `<div v-for="(  { id, ...other }, index) in list" :key="id">{{ id + other + index }}</div>`,
     )
     expect(code).matchSnapshot()
-    expect(code).contains(
-      `(_state, [{ id, ...other }, index] = _state) => [id, other, index]`,
-    )
+    expect(code).contains(`([{ id, ...other }, index]) => [id, other, index]`)
     expect(code).contains(`_ctx0[0] + _ctx0[1] + _ctx0[2]`)
     expect(ir.block.operation[0]).toMatchObject({
       type: IRNodeTypes.FOR,
@@ -164,9 +162,7 @@ describe('compiler: v-for', () => {
       `<div v-for="([id, ...other], index) in list" :key="id">{{ id + other + index }}</div>`,
     )
     expect(code).matchSnapshot()
-    expect(code).contains(
-      `(_state, [[id, ...other], index] = _state) => [id, other, index]`,
-    )
+    expect(code).contains(`([[id, ...other], index]) => [id, other, index]`)
     expect(code).contains(`_ctx0[0] + _ctx0[1] + _ctx0[2]`)
     expect(ir.block.operation[0]).toMatchObject({
       type: IRNodeTypes.FOR,
@@ -201,9 +197,7 @@ describe('compiler: v-for', () => {
       </div>`,
     )
     expect(code).matchSnapshot()
-    expect(code).contains(
-      `(_state, [{ foo = bar, baz: [qux = quux] }] = _state) => [foo, qux]`,
-    )
+    expect(code).contains(`([{ foo = bar, baz: [qux = quux] }]) => [foo, qux]`)
     expect(code).contains(
       `_ctx0[0] + _ctx.bar + _ctx.baz + _ctx0[1] + _ctx.quux`,
     )

+ 196 - 9
packages/compiler-vapor/__tests__/transforms/vSlot.spec.ts

@@ -58,6 +58,98 @@ describe('compiler: transform slot', () => {
     })
   })
 
+  test('on-component default slot', () => {
+    const { ir, code, vaporHelpers } = compileWithSlots(
+      `<Comp v-slot="{ foo }">{{ foo + bar }}</Comp>`,
+    )
+    expect(code).toMatchSnapshot()
+
+    expect(vaporHelpers).contains('withDestructure')
+    expect(code).contains(`({ foo }) => [foo]`)
+    expect(code).contains(`_ctx0[0] + _ctx.bar`)
+
+    expect(ir.block.operation).toMatchObject([
+      {
+        type: IRNodeTypes.CREATE_COMPONENT_NODE,
+        tag: 'Comp',
+        props: [[]],
+        slots: {
+          default: {
+            type: IRNodeTypes.BLOCK,
+            props: {
+              type: NodeTypes.SIMPLE_EXPRESSION,
+              content: '{ foo }',
+              ast: {
+                type: 'ArrowFunctionExpression',
+                params: [{ type: 'ObjectPattern' }],
+              },
+            },
+          },
+        },
+      },
+    ])
+  })
+
+  test('on component named slot', () => {
+    const { ir, code } = compileWithSlots(
+      `<Comp v-slot:named="{ foo }">{{ foo + bar }}</Comp>`,
+    )
+    expect(code).toMatchSnapshot()
+
+    expect(code).contains(`({ foo }) => [foo]`)
+    expect(code).contains(`_ctx0[0] + _ctx.bar`)
+
+    expect(ir.block.operation).toMatchObject([
+      {
+        type: IRNodeTypes.CREATE_COMPONENT_NODE,
+        tag: 'Comp',
+        slots: {
+          named: {
+            type: IRNodeTypes.BLOCK,
+            props: {
+              type: NodeTypes.SIMPLE_EXPRESSION,
+              content: '{ foo }',
+            },
+          },
+        },
+      },
+    ])
+  })
+
+  test('on component dynamically named slot', () => {
+    const { ir, code, vaporHelpers } = compileWithSlots(
+      `<Comp v-slot:[named]="{ foo }">{{ foo + bar }}</Comp>`,
+    )
+    expect(code).toMatchSnapshot()
+
+    expect(vaporHelpers).contains('withDestructure')
+    expect(code).contains(`({ foo }) => [foo]`)
+    expect(code).contains(`_ctx0[0] + _ctx.bar`)
+
+    expect(ir.block.operation).toMatchObject([
+      {
+        type: IRNodeTypes.CREATE_COMPONENT_NODE,
+        tag: 'Comp',
+        dynamicSlots: [
+          {
+            name: {
+              type: NodeTypes.SIMPLE_EXPRESSION,
+              content: 'named',
+              isStatic: false,
+            },
+            fn: {
+              type: IRNodeTypes.BLOCK,
+              props: {
+                type: NodeTypes.SIMPLE_EXPRESSION,
+                content: '{ foo }',
+              },
+            },
+          },
+        ],
+      },
+    ])
+  })
+
   test('named slots w/ implicit default slot', () => {
     const { ir, code } = compileWithSlots(
       `<Comp>
@@ -91,13 +183,56 @@ describe('compiler: transform slot', () => {
     ])
   })
 
-  test('nested slots', () => {
-    const { code } = compileWithSlots(
-      `<Foo>
-        <template #one><Bar><div/></Bar></template>
-      </Foo>`,
+  test('nested slots scoping', () => {
+    const { ir, code, vaporHelpers } = compileWithSlots(
+      `<Comp>
+        <template #default="{ foo }">
+          <Inner v-slot="{ bar }">
+            {{ foo + bar + baz }}
+          </Inner>
+          {{ foo + bar + baz }}
+        </template>
+      </Comp>`,
     )
     expect(code).toMatchSnapshot()
+
+    expect(vaporHelpers).contains('withDestructure')
+    expect(code).contains(`({ foo }) => [foo]`)
+    expect(code).contains(`({ bar }) => [bar]`)
+    expect(code).contains(`_ctx0[0] + _ctx1[0] + _ctx.baz`)
+    expect(code).contains(`_ctx0[0] + _ctx.bar + _ctx.baz`)
+
+    expect(ir.block.operation).toMatchObject([
+      {
+        type: IRNodeTypes.CREATE_COMPONENT_NODE,
+        tag: 'Comp',
+        props: [[]],
+        slots: {
+          default: {
+            type: IRNodeTypes.BLOCK,
+            props: {
+              type: NodeTypes.SIMPLE_EXPRESSION,
+              content: '{ foo }',
+            },
+          },
+        },
+      },
+    ])
+    expect(
+      (ir.block.operation[0] as any).slots.default.operation[0],
+    ).toMatchObject({
+      type: IRNodeTypes.CREATE_COMPONENT_NODE,
+      tag: 'Inner',
+      slots: {
+        default: {
+          type: IRNodeTypes.BLOCK,
+          props: {
+            type: NodeTypes.SIMPLE_EXPRESSION,
+            content: '{ bar }',
+          },
+        },
+      },
+    })
   })
 
   test('dynamic slots name', () => {
@@ -128,12 +263,16 @@ describe('compiler: transform slot', () => {
   })
 
   test('dynamic slots name w/ v-for', () => {
-    const { ir, code } = compileWithSlots(
+    const { ir, code, vaporHelpers } = compileWithSlots(
       `<Comp>
-        <template v-for="item in list" #[item]>foo</template>
+        <template v-for="item in list" #[item]="{ bar }">foo</template>
       </Comp>`,
     )
     expect(code).toMatchSnapshot()
+
+    expect(vaporHelpers).contains('withDestructure')
+    expect(code).contains(`({ bar }) => [bar]`)
+
     expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
     expect(ir.block.operation).toMatchObject([
       {
@@ -196,14 +335,18 @@ describe('compiler: transform slot', () => {
   })
 
   test('dynamic slots name w/ v-if / v-else[-if]', () => {
-    const { ir, code } = compileWithSlots(
+    const { ir, code, vaporHelpers } = compileWithSlots(
       `<Comp>
         <template v-if="condition" #condition>condition slot</template>
-        <template v-else-if="anotherCondition" #condition>another condition</template>
+        <template v-else-if="anotherCondition" #condition="{ foo, bar }">another condition</template>
         <template v-else #condition>else condition</template>
       </Comp>`,
     )
     expect(code).toMatchSnapshot()
+
+    expect(vaporHelpers).contains('withDestructure')
+    expect(code).contains(`({ foo, bar }) => [foo, bar]`)
+
     expect(ir.block.operation[0].type).toBe(IRNodeTypes.CREATE_COMPONENT_NODE)
     expect(ir.block.operation).toMatchObject([
       {
@@ -277,5 +420,49 @@ describe('compiler: transform slot', () => {
         },
       })
     })
+
+    test('error on invalid mixed slot usage', () => {
+      const onError = vi.fn()
+      const source = `<Comp v-slot="foo"><template #foo></template></Comp>`
+      compileWithSlots(source, { onError })
+      const index = source.lastIndexOf('v-slot="foo"')
+      expect(onError.mock.calls[0][0]).toMatchObject({
+        code: ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE,
+        loc: {
+          start: {
+            offset: index,
+            line: 1,
+            column: index + 1,
+          },
+          end: {
+            offset: index + 12,
+            line: 1,
+            column: index + 13,
+          },
+        },
+      })
+    })
+
+    test('error on v-slot usage on plain elements', () => {
+      const onError = vi.fn()
+      const source = `<div v-slot/>`
+      compileWithSlots(source, { onError })
+      const index = source.indexOf('v-slot')
+      expect(onError.mock.calls[0][0]).toMatchObject({
+        code: ErrorCodes.X_V_SLOT_MISPLACED,
+        loc: {
+          start: {
+            offset: index,
+            line: 1,
+            column: index + 1,
+          },
+          end: {
+            offset: index + 6,
+            line: 1,
+            column: index + 7,
+          },
+        },
+      })
+    })
   })
 })

+ 5 - 0
packages/compiler-vapor/src/generate.ts

@@ -59,6 +59,11 @@ export class CodegenContext {
     return () => (this.block = parent)
   }
 
+  scopeLevel: number = 0
+  enterScope() {
+    return [this.scopeLevel++, () => this.scopeLevel--] as const
+  }
+
   constructor(
     public ir: RootIRNode,
     options: CodegenOptions,

+ 71 - 4
packages/compiler-vapor/src/generators/component.ts

@@ -5,6 +5,7 @@ import {
   type ComponentConditionalDynamicSlot,
   type ComponentDynamicSlot,
   type ComponentLoopDynamicSlot,
+  type ComponentSlotBlockIRNode,
   type ComponentSlots,
   type CreateComponentIRNode,
   DynamicSlotType,
@@ -27,7 +28,11 @@ import {
 } from './utils'
 import { genExpression } from './expression'
 import { genPropKey } from './prop'
-import { createSimpleExpression, toValidAssetId } from '@vue/compiler-dom'
+import {
+  createSimpleExpression,
+  toValidAssetId,
+  walkIdentifiers,
+} from '@vue/compiler-core'
 import { genEventHandler } from './event'
 import { genDirectiveModifiers, genDirectivesForElement } from './directive'
 import { genModelHandler } from './modelValue'
@@ -151,7 +156,11 @@ function genSlots(slots: ComponentSlots, context: CodegenContext) {
   const names = Object.keys(slots)
   return genMulti(
     names.length > 1 ? DELIMITERS_OBJECT_NEWLINE : DELIMITERS_OBJECT,
-    ...names.map(name => [name, ': ', ...genBlock(slots[name], context)]),
+    ...names.map(name => [
+      name,
+      ': ',
+      ...genSlotBlockWithProps(slots[name], context),
+    ]),
   )
 }
 
@@ -188,7 +197,7 @@ function genBasicDynamicSlot(
   return genMulti(
     DELIMITERS_OBJECT_NEWLINE,
     ['name: ', ...genExpression(name, context)],
-    ['fn: ', ...genBlock(fn, context)],
+    ['fn: ', ...genSlotBlockWithProps(fn, context)],
     ...(key !== undefined ? [`key: "${key}"`] : []),
   )
 }
@@ -210,7 +219,10 @@ function genLoopSlot(
   const slotExpr = genMulti(
     DELIMITERS_OBJECT_NEWLINE,
     ['name: ', ...context.withId(() => genExpression(name, context), idMap)],
-    ['fn: ', ...context.withId(() => genBlock(fn, context), idMap)],
+    [
+      'fn: ',
+      ...context.withId(() => genSlotBlockWithProps(fn, context), idMap),
+    ],
   )
   return [
     ...genCall(
@@ -248,3 +260,58 @@ function genConditionalSlot(
     INDENT_END,
   ]
 }
+
+function genSlotBlockWithProps(
+  oper: ComponentSlotBlockIRNode,
+  context: CodegenContext,
+) {
+  let isDestructureAssignment = false
+  let rawProps: string | undefined
+  let propsName: string | undefined
+  let exitScope: (() => void) | undefined
+  let depth: number | undefined
+  const { props } = oper
+  const idsOfProps = new Set<string>()
+
+  if (props) {
+    rawProps = props.content
+    if ((isDestructureAssignment = !!props.ast)) {
+      ;[depth, exitScope] = context.enterScope()
+      propsName = `_ctx${depth}`
+      walkIdentifiers(
+        props.ast,
+        (id, _, __, ___, isLocal) => {
+          if (isLocal) idsOfProps.add(id.name)
+        },
+        true,
+      )
+    } else {
+      idsOfProps.add((propsName = rawProps))
+    }
+  }
+
+  const idMap: Record<string, string | null> = {}
+
+  Array.from(idsOfProps).forEach(
+    (id, idIndex) =>
+      (idMap[id] = isDestructureAssignment ? `${propsName}[${idIndex}]` : null),
+  )
+  let blockFn = context.withId(
+    () => genBlock(oper, context, [propsName]),
+    idMap,
+  )
+  exitScope && exitScope()
+
+  if (isDestructureAssignment) {
+    const idMap: Record<string, null> = {}
+    idsOfProps.forEach(id => (idMap[id] = null))
+
+    blockFn = genCall(
+      context.vaporHelper('withDestructure'),
+      ['(', rawProps, ') => ', ...genMulti(DELIMITERS_ARRAY, ...idsOfProps)],
+      blockFn,
+    )
+  }
+
+  return blockFn
+}

+ 6 - 4
packages/compiler-vapor/src/generators/for.ts

@@ -41,7 +41,8 @@ export function genFor(
     }
   }
 
-  const propsName = `_ctx${id}`
+  const [depth, exitScope] = context.enterScope()
+  const propsName = `_ctx${depth}`
   const idMap: Record<string, string | null> = {}
   Array.from(idsOfValue).forEach(
     (id, idIndex) => (idMap[id] = `${propsName}[${idIndex}]`),
@@ -53,6 +54,7 @@ export function genFor(
     () => genBlock(render, context, [propsName]),
     idMap,
   )
+  exitScope()
 
   let getKeyFn: CodeFragment[] | false = false
   if (keyProp) {
@@ -81,14 +83,14 @@ export function genFor(
     if (rawKey) idMap[rawKey] = null
     if (rawIndex) idMap[rawIndex] = null
     const destructureAssignmentFn: CodeFragment[] = [
-      '(_state, ',
+      '(',
       ...genMulti(
         DELIMITERS_ARRAY,
         rawValue ? rawValue : rawKey || rawIndex ? '_' : undefined,
         rawKey ? rawKey : rawIndex ? '__' : undefined,
         rawIndex,
       ),
-      ' = _state) => ',
+      ') => ',
       ...genMulti(DELIMITERS_ARRAY, ...idsOfValue, rawKey, rawIndex),
     ]
 
@@ -101,7 +103,7 @@ export function genFor(
 
   return [
     NEWLINE,
-    `const n${oper.id} = `,
+    `const n${id} = `,
     ...genCall(
       vaporHelper('createFor'),
       sourceExpr,

+ 1 - 1
packages/compiler-vapor/src/ir.ts

@@ -208,7 +208,7 @@ export interface WithDirectiveIRNode extends BaseIRNode {
 }
 
 export interface ComponentSlotBlockIRNode extends BlockIRNode {
-  // TODO slot props
+  props?: SimpleExpressionNode
 }
 export type ComponentSlots = Record<string, ComponentSlotBlockIRNode>
 

+ 44 - 14
packages/compiler-vapor/src/transforms/vSlot.ts

@@ -11,9 +11,9 @@ import {
 import type { NodeTransform, TransformContext } from '../transform'
 import { newBlock } from './utils'
 import {
-  type BlockIRNode,
   type ComponentBasicDynamicSlot,
   type ComponentConditionalDynamicSlot,
+  type ComponentSlotBlockIRNode,
   DynamicFlag,
   DynamicSlotType,
   type IRFor,
@@ -25,19 +25,22 @@ import { findDir, resolveExpression } from '../utils'
 export const transformVSlot: NodeTransform = (node, context) => {
   if (node.type !== NodeTypes.ELEMENT) return
 
-  let dir: VaporDirectiveNode | undefined
+  const dir = findDir(node, 'slot', true)
   const { tagType, children } = node
   const { parent } = context
 
-  const isDefaultSlot = tagType === ElementTypes.COMPONENT && children.length
+  const isComponent = tagType === ElementTypes.COMPONENT
   const isSlotTemplate =
     isTemplateNode(node) &&
     parent &&
     parent.node.type === NodeTypes.ELEMENT &&
     parent.node.tagType === ElementTypes.COMPONENT
 
-  if (isDefaultSlot) {
-    const defaultChildren = children.filter(
+  if (isComponent && children.length) {
+    const arg = dir && dir.arg
+    const slotName = arg ? arg.content : 'default'
+
+    const nonSlotTemplateChildren = children.filter(
       n =>
         isNonWhitespaceContent(node) &&
         !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)),
@@ -45,6 +48,7 @@ export const transformVSlot: NodeTransform = (node, context) => {
 
     const [block, onExit] = createSlotBlock(
       node,
+      dir,
       context as TransformContext<ElementNode>,
     )
 
@@ -54,25 +58,44 @@ export const transformVSlot: NodeTransform = (node, context) => {
     return () => {
       onExit()
 
-      if (defaultChildren.length) {
+      let hasOtherSlots = !!Object.keys(slots).length
+
+      if (dir && (hasOtherSlots || dynamicSlots.length)) {
+        // already has on-component slot - this is incorrect usage.
+        context.options.onError(
+          createCompilerError(ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE, dir.loc),
+        )
+        // discarding other slots, referenced how compiler-core implements
+        Object.keys(slots).forEach(slotName => delete slots[slotName])
+        dynamicSlots.length = 0
+        hasOtherSlots = false
+      }
+
+      if (nonSlotTemplateChildren.length) {
         if (slots.default) {
           context.options.onError(
             createCompilerError(
               ErrorCodes.X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN,
-              defaultChildren[0].loc,
+              nonSlotTemplateChildren[0].loc,
             ),
           )
+        } else if (!arg || arg.isStatic) {
+          slots[slotName] = block
         } else {
-          slots.default = block
+          dynamicSlots.push({
+            slotType: DynamicSlotType.BASIC,
+            name: arg,
+            fn: block,
+          })
         }
         context.slots = slots
-      } else if (Object.keys(slots).length) {
+      } else if (hasOtherSlots) {
         context.slots = slots
       }
 
       if (dynamicSlots.length) context.dynamicSlots = dynamicSlots
     }
-  } else if (isSlotTemplate && (dir = findDir(node, 'slot', true))) {
+  } else if (isSlotTemplate && dir) {
     let { arg } = dir
 
     context.dynamic.flags |= DynamicFlag.NON_TEMPLATE
@@ -85,6 +108,7 @@ export const transformVSlot: NodeTransform = (node, context) => {
 
     const [block, onExit] = createSlotBlock(
       node,
+      dir,
       context as TransformContext<ElementNode>,
     )
 
@@ -173,16 +197,22 @@ export const transformVSlot: NodeTransform = (node, context) => {
     }
 
     return () => onExit()
+  } else if (!isComponent && dir) {
+    context.options.onError(
+      createCompilerError(ErrorCodes.X_V_SLOT_MISPLACED, dir.loc),
+    )
   }
 }
 
 function createSlotBlock(
   slotNode: ElementNode,
+  dir: VaporDirectiveNode | undefined,
   context: TransformContext<ElementNode>,
-): [BlockIRNode, () => void] {
-  const branch: BlockIRNode = newBlock(slotNode)
-  const exitBlock = context.enterBlock(branch)
-  return [branch, exitBlock]
+): [ComponentSlotBlockIRNode, () => void] {
+  const block: ComponentSlotBlockIRNode = newBlock(slotNode)
+  block.props = dir && dir.exp
+  const exitBlock = context.enterBlock(block)
+  return [block, exitBlock]
 }
 
 function isNonWhitespaceContent(node: TemplateChildNode): boolean {