From d435acbfb224dc2cbaff6412dd4f4dbc3c66b847 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Mon, 4 Mar 2024 23:36:17 +0900
Subject: [PATCH 01/33] feat(runtime-vapor): component slot

---
 packages/runtime-vapor/src/component.ts      |   3 +
 packages/runtime-vapor/src/componentSlots.ts |  21 ++++
 packages/runtime-vapor/src/index.ts          |   1 +
 packages/runtime-vapor/src/render.ts         |   7 +-
 packages/runtime-vapor/src/slot.ts           |   6 ++
 playground/src/main.ts                       |   2 +-
 playground/src/slots.js                      | 100 +++++++++++++++++++
 7 files changed, 137 insertions(+), 3 deletions(-)
 create mode 100644 packages/runtime-vapor/src/componentSlots.ts
 create mode 100644 packages/runtime-vapor/src/slot.ts
 create mode 100644 playground/src/slots.js

diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index fce0a9e0d..6b7c4ad16 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -15,6 +15,7 @@ import {
   emit,
   normalizeEmitsOptions,
 } from './componentEmits'
+import type { InternalSlots } from './componentSlots'
 
 import type { Data } from '@vue/shared'
 import { VaporLifecycleHooks } from './enums'
@@ -56,6 +57,7 @@ export interface ComponentInternalInstance {
   setupState: Data
   emit: EmitFn
   emitted: Record<string, boolean> | null
+  slots: InternalSlots
   refs: Data
 
   vapor: true
@@ -175,6 +177,7 @@ export const createComponentInstance = (
     props: EMPTY_OBJ,
     attrs: EMPTY_OBJ,
     setupState: EMPTY_OBJ,
+    slots: EMPTY_OBJ,
     refs: EMPTY_OBJ,
     vapor: true,
 
diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
new file mode 100644
index 000000000..73521af5b
--- /dev/null
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -0,0 +1,21 @@
+import type { IfAny } from '@vue/shared'
+import type { Block } from './render'
+import type { ComponentInternalInstance } from './component'
+
+export type Slot<T extends any = any> = (
+  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
+) => Block[]
+
+export type InternalSlots = {
+  [name: string]: Slot | undefined
+}
+
+export type Slots = Readonly<InternalSlots>
+
+export const initSlots = (
+  instance: ComponentInternalInstance,
+  slots: Slots,
+) => {
+  // TODO: normalize?
+  instance.slots = slots
+}
diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts
index 0cf0116b9..fa7a30b86 100644
--- a/packages/runtime-vapor/src/index.ts
+++ b/packages/runtime-vapor/src/index.ts
@@ -105,6 +105,7 @@ export {
 } from './apiLifecycle'
 export { createIf } from './if'
 export { createFor } from './for'
+export { createSlots as createSlot } from './slot'
 
 // **Internal** DOM-only runtime directive helpers
 export {
diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts
index e7425a4c3..fd8729503 100644
--- a/packages/runtime-vapor/src/render.ts
+++ b/packages/runtime-vapor/src/render.ts
@@ -14,6 +14,7 @@ import {
   unsetCurrentInstance,
 } from './component'
 import { initProps } from './componentProps'
+import { type Slots, initSlots } from './componentSlots'
 import { invokeDirectiveHook } from './directives'
 import { insert, querySelector, remove } from './dom/element'
 import { flushPostFlushCbs, queuePostRenderEffect } from './scheduler'
@@ -30,10 +31,12 @@ export type Fragment = {
 export function render(
   comp: Component,
   props: Data,
+  slots: Slots, // TODO:
   container: string | ParentNode,
 ): ComponentInternalInstance {
   const instance = createComponentInstance(comp, props)
   initProps(instance, props, !isFunction(instance.component))
+  initSlots(instance, slots)
   const component = mountComponent(
     instance,
     (container = normalizeContainer(container)),
@@ -56,8 +59,8 @@ function mountComponent(
 
   const reset = setCurrentInstance(instance)
   const block = instance.scope.run(() => {
-    const { component, props, emit, attrs } = instance
-    const ctx = { expose: () => {}, emit, attrs }
+    const { component, props, emit, attrs, slots } = instance
+    const ctx = { expose: () => {}, emit, attrs, slots }
 
     const setupFn = isFunction(component) ? component : component.setup
     const stateOrNode = setupFn && setupFn(props, ctx)
diff --git a/packages/runtime-vapor/src/slot.ts b/packages/runtime-vapor/src/slot.ts
new file mode 100644
index 000000000..46db4c631
--- /dev/null
+++ b/packages/runtime-vapor/src/slot.ts
@@ -0,0 +1,6 @@
+import type { Slots } from './componentSlots'
+
+// TODO: intercept?
+export const createSlots = (slots: Slots) => {
+  return slots
+}
diff --git a/playground/src/main.ts b/playground/src/main.ts
index d962dae1f..3d672a377 100644
--- a/playground/src/main.ts
+++ b/playground/src/main.ts
@@ -7,7 +7,7 @@ const mod = (modules['.' + location.pathname] || modules['./App.vue'])()
 
 mod.then(({ default: mod }) => {
   if (mod.vapor) {
-    const instance = render(mod, {}, '#app')
+    const instance = render(mod, {}, {}, '#app')
     // @ts-expect-error
     globalThis.unmount = () => {
       unmountComponent(instance)
diff --git a/playground/src/slots.js b/playground/src/slots.js
new file mode 100644
index 000000000..d8afd49df
--- /dev/null
+++ b/playground/src/slots.js
@@ -0,0 +1,100 @@
+// @ts-check
+import {
+  children,
+  createSlot,
+  defineComponent,
+  getCurrentInstance,
+  insert,
+  on,
+  ref,
+  render as renderComponent,
+  renderEffect,
+  setText,
+  template,
+} from '@vue/vapor'
+
+const t0 = template('<div class="parnet-container"></div>')
+
+// <template #mySlot="{ message, changeMessage }">
+//   <div clas="slotted">
+//     <h1>{{ message }}</h1>
+//     <button @click="changeMessage">btn parent</button>
+//   </div>
+// </template>
+const t1 = template(
+  '<div class="slotted"><h1><!></h1><button>parent btn</button></div>',
+)
+
+const Parent = defineComponent({
+  vapor: true,
+  props: undefined,
+  setup(props) {},
+  render(_ctx) {
+    const n0 = /** @type {any} */ (t0())
+    const s0 = createSlot({
+      mySlot: scope => {
+        const n1 = t1()
+        const n2 = /** @type {any} */ (children(n1, 0))
+        const n3 = /** @type {any} */ (children(n1, 1))
+        renderEffect(() => {
+          setText(n2, scope.message)
+        })
+        on(n3, 'click', scope.changeMessage)
+        return [n1]
+      },
+      // e.g. default slot
+      // default: () => {
+      //   const n1 = t1()
+      //   return [n1]
+      // }
+    })
+    renderComponent(Child, {}, s0, n0)
+    return n0
+  },
+})
+
+const t2 = template(
+  '<div class="child-container"><button>child btn</button></div>',
+)
+
+const Child = defineComponent({
+  vapor: true,
+  props: undefined,
+  setup(props, { expose: __expose }) {
+    __expose()
+    const message = ref('Hello World!')
+    function changeMessage() {
+      message.value += '!'
+    }
+    const __returned__ = { message, changeMessage }
+    Object.defineProperty(__returned__, '__isScriptSetup', {
+      enumerable: false,
+      value: true,
+    })
+    return __returned__
+  },
+  render(_ctx) {
+    const instance = /** @type {any} */ (getCurrentInstance())
+    const slots = instance.slots
+
+    // <div>
+    //   <slot name="mySlot" :message="msg" :changeMessage="changeMessage" />
+    //   <button @click="changeMessage">button in child</button>
+    // </div>
+    const n0 = /** @type {any} */ (t2())
+    const n1 = /** @type {any} */ (children(n0, 0))
+    on(n1, 'click', _ctx.changeMessage)
+    const mySlot = slots.mySlot({
+      get message() {
+        return _ctx.message
+      },
+      get changeMessage() {
+        return _ctx.changeMessage
+      },
+    })
+    insert(mySlot, n0, n1)
+    return n0
+  },
+})
+
+export default Parent

From 61ada62d7e0d58f931fdfd449042ab1beadd264d Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Mon, 4 Mar 2024 23:46:07 +0900
Subject: [PATCH 02/33] chore: render fn arg

---
 packages/runtime-vapor/__tests__/_utils.ts              | 4 +++-
 packages/runtime-vapor/__tests__/componentProps.spec.ts | 1 +
 playground/src/props.js                                 | 3 +--
 3 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/packages/runtime-vapor/__tests__/_utils.ts b/packages/runtime-vapor/__tests__/_utils.ts
index d4501273d..17adc4c84 100644
--- a/packages/runtime-vapor/__tests__/_utils.ts
+++ b/packages/runtime-vapor/__tests__/_utils.ts
@@ -6,6 +6,7 @@ import {
   render as _render,
   defineComponent,
 } from '../src'
+import type { Slots } from '../src/componentSlots'
 
 export function makeRender<Component = ObjectComponent | SetupFn>(
   initHost = () => {
@@ -28,9 +29,10 @@ export function makeRender<Component = ObjectComponent | SetupFn>(
     let instance: ComponentInternalInstance
     const render = (
       props: Data = {},
+      slots: Slots = {},
       container: string | ParentNode = '#host',
     ) => {
-      instance = _render(component, props, container)
+      instance = _render(component, props, slots, container)
       return res()
     }
     const res = () => ({
diff --git a/packages/runtime-vapor/__tests__/componentProps.spec.ts b/packages/runtime-vapor/__tests__/componentProps.spec.ts
index df493d71d..72eff574c 100644
--- a/packages/runtime-vapor/__tests__/componentProps.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentProps.spec.ts
@@ -316,6 +316,7 @@ describe('component props (vapor)', () => {
               return _ctx.id
             },
           },
+          {},
           n0 as HTMLDivElement,
         )
         return n0
diff --git a/playground/src/props.js b/playground/src/props.js
index 8da476cc8..10960ef90 100644
--- a/playground/src/props.js
+++ b/playground/src/props.js
@@ -46,8 +46,6 @@ export default defineComponent({
     // insert(n0, c0)
     renderComponent(
       /** @type {any} */ (child),
-
-      // TODO: proxy??
       {
         /* <Comp :count="count" /> */
         get count() {
@@ -59,6 +57,7 @@ export default defineComponent({
           return _ctx.count * 2
         },
       },
+      {},
       // @ts-expect-error TODO
       n0[0],
     )

From f4768bc536b178254307eadebe0abeb093f2ff47 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Mon, 4 Mar 2024 23:48:39 +0900
Subject: [PATCH 03/33] chore playground

---
 playground/src/slots.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/playground/src/slots.js b/playground/src/slots.js
index d8afd49df..880ad57cb 100644
--- a/playground/src/slots.js
+++ b/playground/src/slots.js
@@ -75,7 +75,7 @@ const Child = defineComponent({
   },
   render(_ctx) {
     const instance = /** @type {any} */ (getCurrentInstance())
-    const slots = instance.slots
+    const { slots } = instance
 
     // <div>
     //   <slot name="mySlot" :message="msg" :changeMessage="changeMessage" />
@@ -84,7 +84,7 @@ const Child = defineComponent({
     const n0 = /** @type {any} */ (t2())
     const n1 = /** @type {any} */ (children(n0, 0))
     on(n1, 'click', _ctx.changeMessage)
-    const mySlot = slots.mySlot({
+    const s0 = slots.mySlot({
       get message() {
         return _ctx.message
       },
@@ -92,7 +92,7 @@ const Child = defineComponent({
         return _ctx.changeMessage
       },
     })
-    insert(mySlot, n0, n1)
+    insert(s0, n0, n1)
     return n0
   },
 })

From ffee6720e72475eaeff9adf46a26ceb87317b256 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 10 Mar 2024 18:41:38 +0900
Subject: [PATCH 04/33] test(runtime-vapor): component slots (def test cases)

---
 .../__tests__/componentSlots.spec.ts          | 43 +++++++++++++++++++
 1 file changed, 43 insertions(+)
 create mode 100644 packages/runtime-vapor/__tests__/componentSlots.spec.ts

diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
new file mode 100644
index 000000000..5b1a7b9e4
--- /dev/null
+++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
@@ -0,0 +1,43 @@
+// NOTE: This test is implemented based on the case of `runtime-core/__test__/componentSlots.spec.ts`.
+
+import { makeRender } from './_utils'
+
+const define = makeRender<any>()
+
+describe('component: slots', () => {
+  test.todo('initSlots: instance.slots should be set correctly', () => {})
+
+  test.todo(
+    'initSlots: should normalize object slots (when value is null, string, array)',
+    () => {},
+  )
+
+  test.todo(
+    'initSlots: should normalize object slots (when value is function)',
+    () => {},
+  )
+
+  test.todo(
+    'initSlots: instance.slots should be set correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)',
+    () => {},
+  )
+
+  test.todo(
+    'updateSlots: instance.slots should be updated correctly (when slotType is number)',
+    async () => {},
+  )
+
+  test.todo(
+    'updateSlots: instance.slots should be updated correctly (when slotType is null)',
+    async () => {},
+  )
+
+  test.todo(
+    'updateSlots: instance.slots should be update correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)',
+    async () => {},
+  )
+
+  test.todo('should respect $stable flag', async () => {})
+
+  test.todo('should not warn when mounting another app in setup', () => {})
+})

From 246badc0769d931a7e9aa122fb261d1308a6149b Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Mon, 11 Mar 2024 00:04:13 +0900
Subject: [PATCH 05/33] test(runtime-vapor): component slots [wip]

---
 .../__tests__/componentSlots.spec.ts          | 234 ++++++++++++++++--
 1 file changed, 220 insertions(+), 14 deletions(-)

diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
index 5b1a7b9e4..4c67102fa 100644
--- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
@@ -1,43 +1,249 @@
 // NOTE: This test is implemented based on the case of `runtime-core/__test__/componentSlots.spec.ts`.
 
+import {
+  defineComponent,
+  getCurrentInstance,
+  nextTick,
+  ref,
+  render as renderComponent,
+  template,
+} from '../src'
+import { createSlots } from '../src/slot'
 import { makeRender } from './_utils'
 
 const define = makeRender<any>()
 
 describe('component: slots', () => {
-  test.todo('initSlots: instance.slots should be set correctly', () => {})
+  function renderWithSlots(slots: any): any {
+    let instance: any
+    const Comp = defineComponent({
+      vapor: true,
+      render() {
+        const t0 = template('<div></div>')
+        const n0 = t0()
+        instance = getCurrentInstance()
+        return n0
+      },
+    })
+
+    const { render } = define({
+      render() {
+        const t0 = template('<div></div>')
+        const n0 = t0()
+        renderComponent(Comp, {}, slots, n0 as ParentNode)
+      },
+    })
+
+    render()
+    return instance
+  }
+
+  test('initSlots: instance.slots should be set correctly', () => {
+    const { slots } = renderWithSlots({ _: 1 })
+    expect(slots).toMatchObject({ _: 1 })
+  })
 
   test.todo(
     'initSlots: should normalize object slots (when value is null, string, array)',
-    () => {},
+    () => {
+      // TODO: normalize
+    },
   )
 
   test.todo(
     'initSlots: should normalize object slots (when value is function)',
-    () => {},
+    () => {
+      // TODO: normalize
+    },
   )
 
-  test.todo(
-    'initSlots: instance.slots should be set correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)',
-    () => {},
-  )
+  test('initSlots: instance.slots should be set correctly', () => {
+    let proxy: any
+    const { render } = define({
+      render() {
+        const t0 = template('<div></div>')
+        const n0 = t0()
+        proxy = getCurrentInstance()
+        return n0
+      },
+    })
+
+    render(
+      {},
+      createSlots({
+        header: () => {
+          const t0 = template('header')
+          // TODO: single node
+          return [t0()]
+        },
+      }),
+    )
+    expect(proxy.slots.header()).toMatchObject([
+      document.createTextNode('header'),
+    ])
+  })
 
+  test('initSlots: instance.slots should be set correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)', () => {
+    const { slots } = renderWithSlots(
+      createSlots({
+        // TODO: normalize from array
+        default: () => {
+          const t0 = template('<span></span>')
+          return [t0()]
+        },
+      }),
+    )
+
+    // TODO: warn
+    // expect(
+    //   '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.',
+    // ).toHaveBeenWarned()
+
+    expect(slots.default()).toMatchObject([document.createElement('span')])
+  })
+
+  // TODO: dynamic slot
   test.todo(
-    'updateSlots: instance.slots should be updated correctly (when slotType is number)',
-    async () => {},
+    'updateSlots: instance.slots should be updated correctly',
+    async () => {
+      const flag1 = ref(true)
+
+      let instance: any
+      const Child = () => {
+        instance = getCurrentInstance()
+        return template('child')()
+      }
+
+      const { render } = define({
+        render() {
+          const t0 = template('<div></div>')
+          const n0 = t0()
+          renderComponent(
+            Child,
+            {},
+            createSlots({
+              default: () => {
+                // TODO: dynamic slot
+                return flag1.value
+                  ? [template('<span></span>')()]
+                  : [template('<div></div>')()]
+              },
+            }),
+            n0 as ParentNode,
+          )
+          return []
+        },
+      })
+
+      render()
+
+      expect(instance.slots.default()).toMatchObject([])
+      expect(instance.slots.default()).not.toMatchObject([])
+
+      flag1.value = false
+      await nextTick()
+
+      expect(instance.slots.default()).not.toMatchObject([])
+      expect(instance.slots.default()).toMatchObject([])
+    },
   )
 
+  // TODO: dynamic slots
   test.todo(
-    'updateSlots: instance.slots should be updated correctly (when slotType is null)',
-    async () => {},
+    'updateSlots: instance.slots should be updated correctly',
+    async () => {
+      const flag1 = ref(true)
+
+      let instance: any
+      const Child = () => {
+        instance = getCurrentInstance()
+        return template('child')()
+      }
+
+      const oldSlots = {
+        header: () => template('header')(),
+        footer: undefined,
+      }
+      const newSlots = {
+        header: undefined,
+        footer: () => template('footer')(),
+      }
+
+      const { render } = define({
+        render() {
+          const t0 = template('<div></div>')
+          const n0 = t0()
+          // renderComponent(
+          //   Child,
+          //   {},
+          //   createSlots(flag1.value ? oldSlots : newSlots),
+          //   n0 as ParentNode,
+          // )
+          return []
+        },
+      })
+
+      render()
+
+      expect(instance.slots).toMatchObject({ _: null })
+
+      flag1.value = false
+      await nextTick()
+
+      expect(instance.slots).toMatchObject({ _: null })
+    },
   )
 
   test.todo(
     'updateSlots: instance.slots should be update correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)',
-    async () => {},
+    async () => {
+      // TODO: dynamic slots
+    },
   )
 
-  test.todo('should respect $stable flag', async () => {})
+  test.todo('should respect $stable flag', async () => {
+    // TODO: $stable flag
+  })
 
-  test.todo('should not warn when mounting another app in setup', () => {})
+  test.todo('should not warn when mounting another app in setup', () => {
+    // TODO: warning and createApp fn
+    // const Comp = {
+    //   render() {
+    //     const i = getCurrentInstance()
+    //     return i?.slots.default?.()
+    //   },
+    // }
+    // const mountComp = () => {
+    //   createApp({
+    //     render() {
+    //       const t0 = template('<div></div>')
+    //       const n0 = t0()
+    //       renderComponent(
+    //         Comp,
+    //         {},
+    //         createSlots({
+    //           default: () => {
+    //             const t0 = template('msg')
+    //             return [t0()]
+    //           },
+    //         }),
+    //         n0,
+    //       )
+    //       return n0
+    //     },
+    //   })
+    // }
+    // const App = {
+    //   setup() {
+    //     mountComp()
+    //   },
+    //   render() {
+    //     return null
+    //   },
+    // }
+    // createApp(App).mount(document.createElement('div'))
+    // expect(
+    //   'Slot "default" invoked outside of the render function',
+    // ).not.toHaveBeenWarned()
+  })
 })

From 60edb08528d1747b36e48fe669217f5d3d1eea6d Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 17 Mar 2024 16:19:05 +0900
Subject: [PATCH 06/33] chore: add comment

---
 packages/runtime-vapor/src/componentSlots.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
index 73521af5b..f2164d2a2 100644
--- a/packages/runtime-vapor/src/componentSlots.ts
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -19,3 +19,5 @@ export const initSlots = (
   // TODO: normalize?
   instance.slots = slots
 }
+
+// TODO: $stable ?

From d6903315a844659b77e772bccdbdca8090e9dbdf Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 17 Mar 2024 17:07:41 +0900
Subject: [PATCH 07/33] chore: merge refactoring of component

---
 .../runtime-vapor/src/apiCreateComponent.ts   |  9 +-
 packages/runtime-vapor/src/component.ts       |  5 +-
 packages/runtime-vapor/src/componentSlots.ts  |  5 +-
 playground/src/slots.js                       | 99 +++++++++----------
 4 files changed, 58 insertions(+), 60 deletions(-)

diff --git a/packages/runtime-vapor/src/apiCreateComponent.ts b/packages/runtime-vapor/src/apiCreateComponent.ts
index c74783b32..4d40f0e66 100644
--- a/packages/runtime-vapor/src/apiCreateComponent.ts
+++ b/packages/runtime-vapor/src/apiCreateComponent.ts
@@ -5,10 +5,15 @@ import {
 } from './component'
 import { setupComponent } from './apiRender'
 import type { RawProps } from './componentProps'
+import type { Slots } from './componentSlots'
 
-export function createComponent(comp: Component, rawProps: RawProps = null) {
+export function createComponent(
+  comp: Component,
+  rawProps: RawProps = null,
+  slots: Slots | null = null,
+) {
   const current = currentInstance!
-  const instance = createComponentInstance(comp, rawProps)
+  const instance = createComponentInstance(comp, rawProps, slots)
   setupComponent(instance)
 
   // register sub-component with current component for lifecycle management
diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 9aa512bf3..54c755754 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -17,7 +17,7 @@ import {
   emit,
   normalizeEmitsOptions,
 } from './componentEmits'
-import type { InternalSlots } from './componentSlots'
+import { type InternalSlots, type Slots, initSlots } from './componentSlots'
 import { VaporLifecycleHooks } from './apiLifecycle'
 import type { Data } from '@vue/shared'
 
@@ -142,6 +142,7 @@ let uid = 0
 export function createComponentInstance(
   component: ObjectComponent | FunctionalComponent,
   rawProps: RawProps | null,
+  slots: Slots | null,
 ): ComponentInternalInstance {
   const instance: ComponentInternalInstance = {
     uid: uid++,
@@ -228,7 +229,7 @@ export function createComponentInstance(
     // [VaporLifecycleHooks.SERVER_PREFETCH]: null,
   }
   initProps(instance, rawProps, !isFunction(component))
-  // TODO: initSlots(instance, slots)
+  initSlots(instance, slots)
   instance.emit = emit.bind(null, instance)
 
   return instance
diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
index f2164d2a2..5bacab2f0 100644
--- a/packages/runtime-vapor/src/componentSlots.ts
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -1,6 +1,6 @@
 import type { IfAny } from '@vue/shared'
-import type { Block } from './render'
 import type { ComponentInternalInstance } from './component'
+import type { Block } from './apiRender'
 
 export type Slot<T extends any = any> = (
   ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
@@ -14,8 +14,9 @@ export type Slots = Readonly<InternalSlots>
 
 export const initSlots = (
   instance: ComponentInternalInstance,
-  slots: Slots,
+  slots: Slots | null,
 ) => {
+  if (!slots) slots = {}
   // TODO: normalize?
   instance.slots = slots
 }
diff --git a/playground/src/slots.js b/playground/src/slots.js
index 880ad57cb..2cd6982b1 100644
--- a/playground/src/slots.js
+++ b/playground/src/slots.js
@@ -1,13 +1,13 @@
 // @ts-check
 import {
   children,
-  createSlot,
+  createComponent,
+  createSlots,
   defineComponent,
   getCurrentInstance,
   insert,
   on,
   ref,
-  render as renderComponent,
   renderEffect,
   setText,
   template,
@@ -27,29 +27,31 @@ const t1 = template(
 
 const Parent = defineComponent({
   vapor: true,
-  props: undefined,
-  setup(props) {},
-  render(_ctx) {
-    const n0 = /** @type {any} */ (t0())
-    const s0 = createSlot({
-      mySlot: scope => {
-        const n1 = t1()
-        const n2 = /** @type {any} */ (children(n1, 0))
-        const n3 = /** @type {any} */ (children(n1, 1))
-        renderEffect(() => {
-          setText(n2, scope.message)
-        })
-        on(n3, 'click', scope.changeMessage)
-        return [n1]
-      },
-      // e.g. default slot
-      // default: () => {
-      //   const n1 = t1()
-      //   return [n1]
-      // }
-    })
-    renderComponent(Child, {}, s0, n0)
-    return n0
+
+  setup(props) {
+    return (() => {
+      const n0 = /** @type {any} */ (t0())
+      const s0 = createSlots({
+        mySlot: scope => {
+          const n1 = t1()
+          const n2 = /** @type {any} */ (children(n1, 0))
+          const n3 = /** @type {any} */ (children(n1, 1))
+          renderEffect(() => {
+            setText(n2, scope.message())
+          })
+          on(n3, 'click', scope.changeMessage)
+          return [n1]
+        },
+        // e.g. default slot
+        // default: () => {
+        //   const n1 = t1()
+        //   return [n1]
+        // }
+      })
+      /** @type {any} */
+      const n1 = createComponent(Child, {}, s0)
+      return [n0, n1]
+    })()
   },
 })
 
@@ -59,41 +61,30 @@ const t2 = template(
 
 const Child = defineComponent({
   vapor: true,
-  props: undefined,
-  setup(props, { expose: __expose }) {
+  setup(_, { expose: __expose }) {
     __expose()
     const message = ref('Hello World!')
     function changeMessage() {
       message.value += '!'
     }
-    const __returned__ = { message, changeMessage }
-    Object.defineProperty(__returned__, '__isScriptSetup', {
-      enumerable: false,
-      value: true,
-    })
-    return __returned__
-  },
-  render(_ctx) {
-    const instance = /** @type {any} */ (getCurrentInstance())
-    const { slots } = instance
 
-    // <div>
-    //   <slot name="mySlot" :message="msg" :changeMessage="changeMessage" />
-    //   <button @click="changeMessage">button in child</button>
-    // </div>
-    const n0 = /** @type {any} */ (t2())
-    const n1 = /** @type {any} */ (children(n0, 0))
-    on(n1, 'click', _ctx.changeMessage)
-    const s0 = slots.mySlot({
-      get message() {
-        return _ctx.message
-      },
-      get changeMessage() {
-        return _ctx.changeMessage
-      },
-    })
-    insert(s0, n0, n1)
-    return n0
+    return (() => {
+      const instance = /** @type {any} */ (getCurrentInstance())
+      const { slots } = instance
+      // <div>
+      //   <slot name="mySlot" :message="msg" :changeMessage="changeMessage" />
+      //   <button @click="changeMessage">button in child</button>
+      // </div>
+      const n0 = /** @type {any} */ (t2())
+      const n1 = /** @type {any} */ (children(n0, 0))
+      on(n1, 'click', () => changeMessage)
+      const s0 = slots.mySlot({
+        message: () => message.value,
+        changeMessage: () => changeMessage,
+      })
+      insert(s0, n0, n1)
+      return n0
+    })()
   },
 })
 

From e967ed17de1635065d607d75cae83d13a2fabf69 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 17 Mar 2024 17:13:48 +0900
Subject: [PATCH 08/33] chore: fix test

---
 packages/runtime-vapor/__tests__/_utils.ts     |  2 +-
 .../__tests__/componentSlots.spec.ts           | 18 ++++++------------
 .../runtime-vapor/src/apiCreateVaporApp.ts     |  2 +-
 3 files changed, 8 insertions(+), 14 deletions(-)

diff --git a/packages/runtime-vapor/__tests__/_utils.ts b/packages/runtime-vapor/__tests__/_utils.ts
index 0fa8d92bb..befb3dff3 100644
--- a/packages/runtime-vapor/__tests__/_utils.ts
+++ b/packages/runtime-vapor/__tests__/_utils.ts
@@ -34,7 +34,7 @@ export function makeRender<Component = ObjectComponent | SetupFn>(
       slots: Slots = {},
       container: string | ParentNode = '#host',
     ) => {
-      app = createVaporApp(component, props)
+      app = createVaporApp(component, props, slots)
       instance = app.mount(container)
 
       return res()
diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
index 4c67102fa..625af8684 100644
--- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
@@ -1,11 +1,11 @@
 // NOTE: This test is implemented based on the case of `runtime-core/__test__/componentSlots.spec.ts`.
 
 import {
+  createComponent,
   defineComponent,
   getCurrentInstance,
   nextTick,
   ref,
-  render as renderComponent,
   template,
 } from '../src'
 import { createSlots } from '../src/slot'
@@ -28,9 +28,7 @@ describe('component: slots', () => {
 
     const { render } = define({
       render() {
-        const t0 = template('<div></div>')
-        const n0 = t0()
-        renderComponent(Comp, {}, slots, n0 as ParentNode)
+        return createComponent(Comp, {}, slots)
       },
     })
 
@@ -58,12 +56,12 @@ describe('component: slots', () => {
   )
 
   test('initSlots: instance.slots should be set correctly', () => {
-    let proxy: any
+    let instance: any
     const { render } = define({
       render() {
         const t0 = template('<div></div>')
         const n0 = t0()
-        proxy = getCurrentInstance()
+        instance = getCurrentInstance()
         return n0
       },
     })
@@ -78,7 +76,7 @@ describe('component: slots', () => {
         },
       }),
     )
-    expect(proxy.slots.header()).toMatchObject([
+    expect(instance.slots.header()).toMatchObject([
       document.createTextNode('header'),
     ])
   })
@@ -116,9 +114,7 @@ describe('component: slots', () => {
 
       const { render } = define({
         render() {
-          const t0 = template('<div></div>')
-          const n0 = t0()
-          renderComponent(
+          return createComponent(
             Child,
             {},
             createSlots({
@@ -129,9 +125,7 @@ describe('component: slots', () => {
                   : [template('<div></div>')()]
               },
             }),
-            n0 as ParentNode,
           )
-          return []
         },
       })
 
diff --git a/packages/runtime-vapor/src/apiCreateVaporApp.ts b/packages/runtime-vapor/src/apiCreateVaporApp.ts
index 31dacf356..084514fb6 100644
--- a/packages/runtime-vapor/src/apiCreateVaporApp.ts
+++ b/packages/runtime-vapor/src/apiCreateVaporApp.ts
@@ -40,7 +40,7 @@ export function createVaporApp(
 
     mount(rootContainer): any {
       if (!instance) {
-        instance = createComponentInstance(rootComponent, rootProps)
+        instance = createComponentInstance(rootComponent, rootProps, rootSlots)
         setupComponent(instance)
         render(instance, rootContainer)
         return instance

From fd2d9ff092c3795f750581d66bf02929643ef864 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 17 Mar 2024 19:25:14 +0900
Subject: [PATCH 09/33] feat(runtime-vapor): dynamic slots

---
 .../__tests__/componentSlots.spec.ts          | 221 +++++++++---------
 packages/runtime-vapor/src/apiCreateSlots.ts  |  65 ++++++
 packages/runtime-vapor/src/componentSlots.ts  |   6 +-
 packages/runtime-vapor/src/index.ts           |   2 +-
 packages/runtime-vapor/src/slot.ts            |   6 -
 5 files changed, 182 insertions(+), 118 deletions(-)
 create mode 100644 packages/runtime-vapor/src/apiCreateSlots.ts
 delete mode 100644 packages/runtime-vapor/src/slot.ts

diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
index 625af8684..8484482cf 100644
--- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
@@ -2,13 +2,14 @@
 
 import {
   createComponent,
+  createVaporApp,
   defineComponent,
   getCurrentInstance,
   nextTick,
   ref,
   template,
 } from '../src'
-import { createSlots } from '../src/slot'
+import { createSlots } from '../src/apiCreateSlots'
 import { makeRender } from './_utils'
 
 const define = makeRender<any>()
@@ -69,80 +70,65 @@ describe('component: slots', () => {
     render(
       {},
       createSlots({
-        header: () => {
-          const t0 = template('header')
-          // TODO: single node
-          return [t0()]
-        },
+        header: () => template('header')(),
       }),
     )
-    expect(instance.slots.header()).toMatchObject([
+    expect(instance.slots.header()).toMatchObject(
       document.createTextNode('header'),
-    ])
+    )
   })
 
+  // TODO: test case name
   test('initSlots: instance.slots should be set correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)', () => {
     const { slots } = renderWithSlots(
       createSlots({
-        // TODO: normalize from array
-        default: () => {
-          const t0 = template('<span></span>')
-          return [t0()]
-        },
+        // TODO: normalize from array?
+        default: () => template('<span></span>')(),
       }),
     )
 
-    // TODO: warn
     // expect(
     //   '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.',
     // ).toHaveBeenWarned()
 
-    expect(slots.default()).toMatchObject([document.createElement('span')])
+    expect(slots.default()).toMatchObject(document.createElement('span'))
   })
 
-  // TODO: dynamic slot
-  test.todo(
-    'updateSlots: instance.slots should be updated correctly',
-    async () => {
-      const flag1 = ref(true)
+  test('updateSlots: instance.slots should be updated correctly', async () => {
+    const flag1 = ref(true)
 
-      let instance: any
-      const Child = () => {
-        instance = getCurrentInstance()
-        return template('child')()
-      }
+    let instance: any
+    const Child = () => {
+      instance = getCurrentInstance()
+      return template('child')()
+    }
 
-      const { render } = define({
-        render() {
-          return createComponent(
-            Child,
-            {},
-            createSlots({
-              default: () => {
-                // TODO: dynamic slot
-                return flag1.value
-                  ? [template('<span></span>')()]
-                  : [template('<div></div>')()]
-              },
-            }),
-          )
-        },
-      })
+    const { render } = define({
+      render() {
+        return createComponent(
+          Child,
+          {},
+          createSlots({ _: 2 as any }, () => [
+            flag1.value
+              ? { name: 'one', fn: () => template('<span></span>')() }
+              : { name: 'two', fn: () => template('<div></div>')() },
+          ]),
+        )
+      },
+    })
 
-      render()
+    render()
 
-      expect(instance.slots.default()).toMatchObject([])
-      expect(instance.slots.default()).not.toMatchObject([])
+    expect(instance.slots).toHaveProperty('one')
+    expect(instance.slots).not.toHaveProperty('two')
 
-      flag1.value = false
-      await nextTick()
+    flag1.value = false
+    await nextTick()
 
-      expect(instance.slots.default()).not.toMatchObject([])
-      expect(instance.slots.default()).toMatchObject([])
-    },
-  )
+    expect(instance.slots).not.toHaveProperty('one')
+    expect(instance.slots).toHaveProperty('two')
+  })
 
-  // TODO: dynamic slots
   test.todo(
     'updateSlots: instance.slots should be updated correctly',
     async () => {
@@ -164,16 +150,15 @@ describe('component: slots', () => {
       }
 
       const { render } = define({
-        render() {
-          const t0 = template('<div></div>')
-          const n0 = t0()
-          // renderComponent(
-          //   Child,
-          //   {},
-          //   createSlots(flag1.value ? oldSlots : newSlots),
-          //   n0 as ParentNode,
-          // )
-          return []
+        setup() {
+          return (() => {
+            return createComponent(
+              Child,
+              {},
+              // TODO: maybe it is not supported
+              createSlots(flag1.value ? oldSlots : newSlots),
+            )
+          })()
         },
       })
 
@@ -188,56 +173,78 @@ describe('component: slots', () => {
     },
   )
 
-  test.todo(
-    'updateSlots: instance.slots should be update correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)',
-    async () => {
-      // TODO: dynamic slots
-    },
-  )
+  // TODO: test case name
+  test('updateSlots: instance.slots should be update correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)', async () => {
+    const flag1 = ref(true)
+
+    let instance: any
+    const Child = () => {
+      instance = getCurrentInstance()
+      return template('child')()
+    }
+
+    const { render } = define({
+      setup() {
+        return createComponent(
+          Child,
+          {},
+          createSlots({}, () => [
+            flag1.value
+              ? [{ name: 'header', fn: () => template('header')() }]
+              : [{ name: 'footer', fn: () => template('footer')() }],
+          ]),
+        )
+      },
+    })
+    render()
+
+    expect(instance.slots).toHaveProperty('header')
+    flag1.value = false
+    await nextTick()
+
+    // expect(
+    //   '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.',
+    // ).toHaveBeenWarned()
+
+    expect(instance.slots).toHaveProperty('footer')
+  })
 
   test.todo('should respect $stable flag', async () => {
-    // TODO: $stable flag
+    // TODO: $stable flag?
   })
 
   test.todo('should not warn when mounting another app in setup', () => {
-    // TODO: warning and createApp fn
-    // const Comp = {
-    //   render() {
-    //     const i = getCurrentInstance()
-    //     return i?.slots.default?.()
-    //   },
-    // }
-    // const mountComp = () => {
-    //   createApp({
-    //     render() {
-    //       const t0 = template('<div></div>')
-    //       const n0 = t0()
-    //       renderComponent(
-    //         Comp,
-    //         {},
-    //         createSlots({
-    //           default: () => {
-    //             const t0 = template('msg')
-    //             return [t0()]
-    //           },
-    //         }),
-    //         n0,
-    //       )
-    //       return n0
-    //     },
-    //   })
-    // }
-    // const App = {
-    //   setup() {
-    //     mountComp()
-    //   },
-    //   render() {
-    //     return null
-    //   },
-    // }
-    // createApp(App).mount(document.createElement('div'))
-    // expect(
-    //   'Slot "default" invoked outside of the render function',
-    // ).not.toHaveBeenWarned()
+    // TODO: warning
+    const Comp = defineComponent({
+      render() {
+        const i = getCurrentInstance()
+        return i!.slots.default!()
+      },
+    })
+    const mountComp = () => {
+      createVaporApp({
+        render() {
+          return createComponent(
+            Comp,
+            {},
+            createSlots({
+              default: () => template('msg')(),
+            }),
+          )!
+        },
+      })
+    }
+    const App = {
+      setup() {
+        mountComp()
+      },
+      render() {
+        return null!
+      },
+    }
+    createVaporApp(App).mount(document.createElement('div'))
+    expect(
+      'Slot "default" invoked outside of the render function',
+    ).not.toHaveBeenWarned()
   })
 })
diff --git a/packages/runtime-vapor/src/apiCreateSlots.ts b/packages/runtime-vapor/src/apiCreateSlots.ts
new file mode 100644
index 000000000..af619da0a
--- /dev/null
+++ b/packages/runtime-vapor/src/apiCreateSlots.ts
@@ -0,0 +1,65 @@
+// NOTE: this filed is based on `runtime-core/src/helpers/createSlots.ts`
+
+import { EMPTY_ARR, isArray } from '@vue/shared'
+import { renderWatch } from './renderWatch'
+import type { InternalSlots, Slot } from './componentSlots'
+
+// TODO: SSR
+
+interface CompiledSlotDescriptor {
+  name: string
+  fn: Slot
+  key?: string
+}
+
+export const createSlots = (
+  slots: InternalSlots,
+  dynamicSlotsGetter?: () => (
+    | CompiledSlotDescriptor
+    | CompiledSlotDescriptor[]
+    | undefined
+  )[],
+): InternalSlots => {
+  const dynamicSlotKeys: Record<string, true> = {}
+  renderWatch(
+    () => dynamicSlotsGetter?.() ?? EMPTY_ARR,
+    dynamicSlots => {
+      for (let i = 0; i < dynamicSlots.length; i++) {
+        const slot = dynamicSlots[i]
+        // array of dynamic slot generated by <template v-for="..." #[...]>
+        if (isArray(slot)) {
+          for (let j = 0; j < slot.length; j++) {
+            slots[slot[j].name] = slot[j].fn
+            dynamicSlotKeys[slot[j].name] = true
+          }
+        } else if (slot) {
+          // conditional single slot generated by <template v-if="..." #foo>
+          slots[slot.name] = slot.key
+            ? (...args: any[]) => {
+                const res = slot.fn(...args)
+                // attach branch key so each conditional branch is considered a
+                // different fragment
+                if (res) (res as any).key = slot.key
+                return res
+              }
+            : slot.fn
+          dynamicSlotKeys[slot.name] = true
+        }
+      }
+
+      // delete stale slots
+      for (const key in dynamicSlotKeys) {
+        if (
+          // TODO: type (renderWatch)
+          !dynamicSlots.some((slot: any) =>
+            isArray(slot) ? slot.some(s => s.name === key) : slot?.name === key,
+          )
+        ) {
+          delete slots[key]
+        }
+      }
+    },
+    { immediate: true },
+  )
+  return slots
+}
diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
index 5bacab2f0..d5b9f7081 100644
--- a/packages/runtime-vapor/src/componentSlots.ts
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -4,7 +4,7 @@ import type { Block } from './apiRender'
 
 export type Slot<T extends any = any> = (
   ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
-) => Block[]
+) => Block
 
 export type InternalSlots = {
   [name: string]: Slot | undefined
@@ -14,11 +14,9 @@ export type Slots = Readonly<InternalSlots>
 
 export const initSlots = (
   instance: ComponentInternalInstance,
-  slots: Slots | null,
+  slots: InternalSlots | null,
 ) => {
   if (!slots) slots = {}
   // TODO: normalize?
   instance.slots = slots
 }
-
-// TODO: $stable ?
diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts
index d6fc21a37..d71f739e6 100644
--- a/packages/runtime-vapor/src/index.ts
+++ b/packages/runtime-vapor/src/index.ts
@@ -110,7 +110,7 @@ export {
 export { createIf } from './apiCreateIf'
 export { createFor } from './apiCreateFor'
 export { createComponent } from './apiCreateComponent'
-export { createSlots } from './slot'
+export { createSlots } from './apiCreateSlots'
 export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
 
 // **Internal** DOM-only runtime directive helpers
diff --git a/packages/runtime-vapor/src/slot.ts b/packages/runtime-vapor/src/slot.ts
deleted file mode 100644
index 46db4c631..000000000
--- a/packages/runtime-vapor/src/slot.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { Slots } from './componentSlots'
-
-// TODO: intercept?
-export const createSlots = (slots: Slots) => {
-  return slots
-}

From a5d99454eae67ef639cc95929dcf604fafcc772e Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 17 Mar 2024 19:45:43 +0900
Subject: [PATCH 10/33] chore: fmt

---
 packages/runtime-vapor/__tests__/_utils.ts | 1 -
 packages/runtime-vapor/src/index.ts        | 1 +
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/runtime-vapor/__tests__/_utils.ts b/packages/runtime-vapor/__tests__/_utils.ts
index befb3dff3..795b9656d 100644
--- a/packages/runtime-vapor/__tests__/_utils.ts
+++ b/packages/runtime-vapor/__tests__/_utils.ts
@@ -36,7 +36,6 @@ export function makeRender<Component = ObjectComponent | SetupFn>(
     ) => {
       app = createVaporApp(component, props, slots)
       instance = app.mount(container)
-
       return res()
     }
     const res = () => ({
diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts
index d71f739e6..14d1a3b9c 100644
--- a/packages/runtime-vapor/src/index.ts
+++ b/packages/runtime-vapor/src/index.ts
@@ -111,6 +111,7 @@ export { createIf } from './apiCreateIf'
 export { createFor } from './apiCreateFor'
 export { createComponent } from './apiCreateComponent'
 export { createSlots } from './apiCreateSlots'
+
 export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
 
 // **Internal** DOM-only runtime directive helpers

From 3e0d646a97bdcfcc510e92835b502cf58c661be7 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 17 Mar 2024 19:50:33 +0900
Subject: [PATCH 11/33] chore: slot playground

---
 playground/src/slots.js | 47 ++++++++++++++++++++---------------------
 1 file changed, 23 insertions(+), 24 deletions(-)

diff --git a/playground/src/slots.js b/playground/src/slots.js
index 2cd6982b1..2e7a4924c 100644
--- a/playground/src/slots.js
+++ b/playground/src/slots.js
@@ -13,8 +13,6 @@ import {
   template,
 } from '@vue/vapor'
 
-const t0 = template('<div class="parnet-container"></div>')
-
 // <template #mySlot="{ message, changeMessage }">
 //   <div clas="slotted">
 //     <h1>{{ message }}</h1>
@@ -27,30 +25,31 @@ const t1 = template(
 
 const Parent = defineComponent({
   vapor: true,
-
-  setup(props) {
+  setup(_) {
     return (() => {
-      const n0 = /** @type {any} */ (t0())
-      const s0 = createSlots({
-        mySlot: scope => {
-          const n1 = t1()
-          const n2 = /** @type {any} */ (children(n1, 0))
-          const n3 = /** @type {any} */ (children(n1, 1))
-          renderEffect(() => {
-            setText(n2, scope.message())
-          })
-          on(n3, 'click', scope.changeMessage)
-          return [n1]
-        },
-        // e.g. default slot
-        // default: () => {
-        //   const n1 = t1()
-        //   return [n1]
-        // }
-      })
       /** @type {any} */
-      const n1 = createComponent(Child, {}, s0)
-      return [n0, n1]
+      const n0 = createComponent(
+        Child,
+        {},
+        createSlots({
+          mySlot: ({ message, changeMessage }) => {
+            const n1 = t1()
+            const n2 = /** @type {any} */ (children(n1, 0))
+            const n3 = /** @type {any} */ (children(n1, 1))
+            renderEffect(() => {
+              setText(n2, message())
+            })
+            on(n3, 'click', changeMessage)
+            return n1
+          },
+          // e.g. default slot
+          // default: () => {
+          //   const n1 = t1()
+          //   return n1
+          // }
+        }),
+      )
+      return n0
     })()
   },
 })

From ab91662d7427dcb8fb2689e08f4fd1e1738c2a23 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 17 Mar 2024 19:53:23 +0900
Subject: [PATCH 12/33] chore: remove dead comments

---
 playground/src/slots.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/playground/src/slots.js b/playground/src/slots.js
index 2e7a4924c..fa6673297 100644
--- a/playground/src/slots.js
+++ b/playground/src/slots.js
@@ -1,4 +1,3 @@
-// @ts-check
 import {
   children,
   createComponent,

From 0159af955a44bb7f08283eb6bf404ca5318ff694 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 17 Mar 2024 20:08:13 +0900
Subject: [PATCH 13/33] chore: remove unnecessary diffs

---
 packages/runtime-vapor/__tests__/componentSlots.spec.ts | 2 +-
 packages/runtime-vapor/src/component.ts                 | 2 +-
 packages/runtime-vapor/src/index.ts                     | 1 -
 3 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
index 8484482cf..6108f62ca 100644
--- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
@@ -2,6 +2,7 @@
 
 import {
   createComponent,
+  createSlots,
   createVaporApp,
   defineComponent,
   getCurrentInstance,
@@ -9,7 +10,6 @@ import {
   ref,
   template,
 } from '../src'
-import { createSlots } from '../src/apiCreateSlots'
 import { makeRender } from './_utils'
 
 const define = makeRender<any>()
diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 54c755754..91ff6c0d9 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -19,6 +19,7 @@ import {
 } from './componentEmits'
 import { type InternalSlots, type Slots, initSlots } from './componentSlots'
 import { VaporLifecycleHooks } from './apiLifecycle'
+
 import type { Data } from '@vue/shared'
 
 export type Component = FunctionalComponent | ObjectComponent
@@ -166,7 +167,6 @@ export function createComponentInstance(
 
     // state
     setupState: EMPTY_OBJ,
-
     props: EMPTY_OBJ,
     emit: null!,
     emitted: null,
diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts
index 14d1a3b9c..15c2e16c2 100644
--- a/packages/runtime-vapor/src/index.ts
+++ b/packages/runtime-vapor/src/index.ts
@@ -100,7 +100,6 @@ export {
   onErrorCaptured,
   // onServerPrefetch,
 } from './apiLifecycle'
-
 export {
   createVaporApp,
   type App,

From 1e00d6c0f0a617bffb2a27632eb6b90bdb2663d4 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 17 Mar 2024 20:13:39 +0900
Subject: [PATCH 14/33] chore: add comments

---
 README.md                                           | 1 +
 packages/compiler-vapor/src/generators/component.ts | 1 +
 2 files changed, 2 insertions(+)

diff --git a/README.md b/README.md
index 0937d7dfd..93f6ca0ff 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,7 @@ The code provided here is a duplicate from `runtime-core` as Vapor cannot import
 - packages/runtime-vapor/src/component.ts
 - packages/runtime-vapor/src/componentEmits.ts
 - packages/runtime-vapor/src/componentProps.ts
+- packages/runtime-vapor/src/componentSlots.ts
 - packages/runtime-vapor/src/enums.ts
 - packages/runtime-vapor/src/errorHandling.ts
 - packages/runtime-vapor/src/scheduler.ts
diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts
index ad54d884f..d36c9e842 100644
--- a/packages/compiler-vapor/src/generators/component.ts
+++ b/packages/compiler-vapor/src/generators/component.ts
@@ -12,6 +12,7 @@ import {
 import { genExpression } from './expression'
 import { genPropKey } from './prop'
 
+// TODO: generate component slots
 export function genCreateComponent(
   oper: CreateComponentIRNode,
   context: CodegenContext,

From 560964439ea62c1c2c36fed3f5866cb15c47f329 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Mon, 18 Mar 2024 23:19:58 +0900
Subject: [PATCH 15/33] chore: renderWatch -> renderEffect

---
 packages/runtime-vapor/src/apiCreateSlots.ts | 74 +++++++++-----------
 1 file changed, 35 insertions(+), 39 deletions(-)

diff --git a/packages/runtime-vapor/src/apiCreateSlots.ts b/packages/runtime-vapor/src/apiCreateSlots.ts
index af619da0a..b9b439306 100644
--- a/packages/runtime-vapor/src/apiCreateSlots.ts
+++ b/packages/runtime-vapor/src/apiCreateSlots.ts
@@ -1,8 +1,8 @@
 // NOTE: this filed is based on `runtime-core/src/helpers/createSlots.ts`
 
-import { EMPTY_ARR, isArray } from '@vue/shared'
-import { renderWatch } from './renderWatch'
+import { isArray } from '@vue/shared'
 import type { InternalSlots, Slot } from './componentSlots'
+import { renderEffect } from './renderEffect'
 
 // TODO: SSR
 
@@ -21,45 +21,41 @@ export const createSlots = (
   )[],
 ): InternalSlots => {
   const dynamicSlotKeys: Record<string, true> = {}
-  renderWatch(
-    () => dynamicSlotsGetter?.() ?? EMPTY_ARR,
-    dynamicSlots => {
-      for (let i = 0; i < dynamicSlots.length; i++) {
-        const slot = dynamicSlots[i]
-        // array of dynamic slot generated by <template v-for="..." #[...]>
-        if (isArray(slot)) {
-          for (let j = 0; j < slot.length; j++) {
-            slots[slot[j].name] = slot[j].fn
-            dynamicSlotKeys[slot[j].name] = true
-          }
-        } else if (slot) {
-          // conditional single slot generated by <template v-if="..." #foo>
-          slots[slot.name] = slot.key
-            ? (...args: any[]) => {
-                const res = slot.fn(...args)
-                // attach branch key so each conditional branch is considered a
-                // different fragment
-                if (res) (res as any).key = slot.key
-                return res
-              }
-            : slot.fn
-          dynamicSlotKeys[slot.name] = true
+  renderEffect(() => {
+    const dynamicSlots = dynamicSlotsGetter?.() ?? []
+    for (let i = 0; i < dynamicSlots.length; i++) {
+      const slot = dynamicSlots[i]
+      // array of dynamic slot generated by <template v-for="..." #[...]>
+      if (isArray(slot)) {
+        for (let j = 0; j < slot.length; j++) {
+          slots[slot[j].name] = slot[j].fn
+          dynamicSlotKeys[slot[j].name] = true
         }
+      } else if (slot) {
+        // conditional single slot generated by <template v-if="..." #foo>
+        slots[slot.name] = slot.key
+          ? (...args: any[]) => {
+              const res = slot.fn(...args)
+              // attach branch key so each conditional branch is considered a
+              // different fragment
+              if (res) (res as any).key = slot.key
+              return res
+            }
+          : slot.fn
+        dynamicSlotKeys[slot.name] = true
       }
-
-      // delete stale slots
-      for (const key in dynamicSlotKeys) {
-        if (
-          // TODO: type (renderWatch)
-          !dynamicSlots.some((slot: any) =>
-            isArray(slot) ? slot.some(s => s.name === key) : slot?.name === key,
-          )
-        ) {
-          delete slots[key]
-        }
+    }
+    // delete stale slots
+    for (const key in dynamicSlotKeys) {
+      if (
+        // TODO: type (renderWatch)
+        !dynamicSlots.some((slot: any) =>
+          isArray(slot) ? slot.some(s => s.name === key) : slot?.name === key,
+        )
+      ) {
+        delete slots[key]
       }
-    },
-    { immediate: true },
-  )
+    }
+  })
   return slots
 }

From 6a7957dc48a6c8cbc630ad38280c60885e746fef Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Wed, 20 Mar 2024 02:46:35 +0900
Subject: [PATCH 16/33] chore(runtime-vapor): remove slot arg of createVaporApp

---
 packages/runtime-vapor/__tests__/_utils.ts    |  4 +---
 .../__tests__/componentSlots.spec.ts          | 22 +++++++++++++------
 .../runtime-vapor/src/apiCreateVaporApp.ts    |  4 +---
 packages/runtime-vapor/src/component.ts       |  2 +-
 4 files changed, 18 insertions(+), 14 deletions(-)

diff --git a/packages/runtime-vapor/__tests__/_utils.ts b/packages/runtime-vapor/__tests__/_utils.ts
index 795b9656d..d9169061d 100644
--- a/packages/runtime-vapor/__tests__/_utils.ts
+++ b/packages/runtime-vapor/__tests__/_utils.ts
@@ -6,7 +6,6 @@ import {
   createVaporApp,
   defineComponent,
 } from '../src'
-import type { Slots } from '../src/componentSlots'
 import type { RawProps } from '../src/componentProps'
 
 export function makeRender<Component = ObjectComponent | SetupFn>(
@@ -31,10 +30,9 @@ export function makeRender<Component = ObjectComponent | SetupFn>(
     let app: App
     const render = (
       props: RawProps = {},
-      slots: Slots = {},
       container: string | ParentNode = '#host',
     ) => {
-      app = createVaporApp(component, props, slots)
+      app = createVaporApp(component, props)
       instance = app.mount(container)
       return res()
     }
diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
index 6108f62ca..ea741c5e3 100644
--- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
@@ -58,7 +58,7 @@ describe('component: slots', () => {
 
   test('initSlots: instance.slots should be set correctly', () => {
     let instance: any
-    const { render } = define({
+    const Comp = defineComponent({
       render() {
         const t0 = template('<div></div>')
         const n0 = t0()
@@ -67,12 +67,20 @@ describe('component: slots', () => {
       },
     })
 
-    render(
-      {},
-      createSlots({
-        header: () => template('header')(),
-      }),
-    )
+    const { render } = define({
+      render() {
+        return createComponent(
+          Comp,
+          {},
+          createSlots({
+            header: () => template('header')(),
+          }),
+        )
+      },
+    })
+
+    render()
+
     expect(instance.slots.header()).toMatchObject(
       document.createTextNode('header'),
     )
diff --git a/packages/runtime-vapor/src/apiCreateVaporApp.ts b/packages/runtime-vapor/src/apiCreateVaporApp.ts
index 084514fb6..af8e480fd 100644
--- a/packages/runtime-vapor/src/apiCreateVaporApp.ts
+++ b/packages/runtime-vapor/src/apiCreateVaporApp.ts
@@ -8,12 +8,10 @@ import { warn } from './warning'
 import { version } from '.'
 import { render, setupComponent, unmountComponent } from './apiRender'
 import type { RawProps } from './componentProps'
-import type { Slots } from './componentSlots'
 
 export function createVaporApp(
   rootComponent: Component,
   rootProps: RawProps | null = null,
-  rootSlots: Slots | null = null,
 ): App {
   if (rootProps != null && !isObject(rootProps)) {
     __DEV__ && warn(`root props passed to app.mount() must be an object.`)
@@ -40,7 +38,7 @@ export function createVaporApp(
 
     mount(rootContainer): any {
       if (!instance) {
-        instance = createComponentInstance(rootComponent, rootProps, rootSlots)
+        instance = createComponentInstance(rootComponent, rootProps)
         setupComponent(instance)
         render(instance, rootContainer)
         return instance
diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 11f9687dc..6eb5caf2d 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -147,7 +147,7 @@ let uid = 0
 export function createComponentInstance(
   component: ObjectComponent | FunctionalComponent,
   rawProps: RawProps | null,
-  slots: Slots | null,
+  slots: Slots | null = null,
 ): ComponentInternalInstance {
   const instance: ComponentInternalInstance = {
     [componentKey]: true,

From d12c2acc9f62056b0f1244e91b9679f29b878d64 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Wed, 20 Mar 2024 02:53:27 +0900
Subject: [PATCH 17/33] chore(runtime-vapor): provide slots to setup context

---
 packages/runtime-vapor/src/apiRender.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/runtime-vapor/src/apiRender.ts b/packages/runtime-vapor/src/apiRender.ts
index 3ddaeb5cc..e0354ee8e 100644
--- a/packages/runtime-vapor/src/apiRender.ts
+++ b/packages/runtime-vapor/src/apiRender.ts
@@ -26,8 +26,8 @@ export function setupComponent(
 ): void {
   const reset = setCurrentInstance(instance)
   instance.scope.run(() => {
-    const { component, props, emit, attrs } = instance
-    const ctx = { expose: () => {}, emit, attrs }
+    const { component, props, emit, attrs, slots } = instance
+    const ctx = { expose: () => {}, emit, attrs, slots }
 
     const setupFn = isFunction(component) ? component : component.setup
     const stateOrNode = setupFn && setupFn(props, ctx)

From 9dfc966fce7588a540cbc319a3443e4edd81ec0d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?=
 <sxzz@sxzz.moe>
Date: Sat, 23 Mar 2024 01:41:36 +0800
Subject: [PATCH 18/33] refactor: tidy

---
 playground/src/slots.js | 23 ++++++++++-------------
 1 file changed, 10 insertions(+), 13 deletions(-)

diff --git a/playground/src/slots.js b/playground/src/slots.js
index fa6673297..35116ffc2 100644
--- a/playground/src/slots.js
+++ b/playground/src/slots.js
@@ -1,9 +1,9 @@
+// @ts-check
 import {
   children,
   createComponent,
   createSlots,
   defineComponent,
-  getCurrentInstance,
   insert,
   on,
   ref,
@@ -24,7 +24,7 @@ const t1 = template(
 
 const Parent = defineComponent({
   vapor: true,
-  setup(_) {
+  setup() {
     return (() => {
       /** @type {any} */
       const n0 = createComponent(
@@ -35,9 +35,7 @@ const Parent = defineComponent({
             const n1 = t1()
             const n2 = /** @type {any} */ (children(n1, 0))
             const n3 = /** @type {any} */ (children(n1, 1))
-            renderEffect(() => {
-              setText(n2, message())
-            })
+            renderEffect(() => setText(n2, message()))
             on(n3, 'click', changeMessage)
             return n1
           },
@@ -59,16 +57,13 @@ const t2 = template(
 
 const Child = defineComponent({
   vapor: true,
-  setup(_, { expose: __expose }) {
-    __expose()
+  setup(_, { slots }) {
     const message = ref('Hello World!')
     function changeMessage() {
       message.value += '!'
     }
 
     return (() => {
-      const instance = /** @type {any} */ (getCurrentInstance())
-      const { slots } = instance
       // <div>
       //   <slot name="mySlot" :message="msg" :changeMessage="changeMessage" />
       //   <button @click="changeMessage">button in child</button>
@@ -76,10 +71,12 @@ const Child = defineComponent({
       const n0 = /** @type {any} */ (t2())
       const n1 = /** @type {any} */ (children(n0, 0))
       on(n1, 'click', () => changeMessage)
-      const s0 = slots.mySlot({
-        message: () => message.value,
-        changeMessage: () => changeMessage,
-      })
+      const s0 = /** @type {any} */ (
+        slots.mySlot?.({
+          message: () => message.value,
+          changeMessage: () => changeMessage,
+        })
+      )
       insert(s0, n0, n1)
       return n0
     })()

From 9c122eb4849c94ad768a95899aa82406bc7a2a20 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sat, 23 Mar 2024 13:01:02 +0900
Subject: [PATCH 19/33] refactor(runtime-vapor): CompiledSlotDescriptor ->
 DynamicSlot

---
 packages/runtime-vapor/src/apiCreateSlots.ts | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/packages/runtime-vapor/src/apiCreateSlots.ts b/packages/runtime-vapor/src/apiCreateSlots.ts
index b9b439306..90f2613c2 100644
--- a/packages/runtime-vapor/src/apiCreateSlots.ts
+++ b/packages/runtime-vapor/src/apiCreateSlots.ts
@@ -6,7 +6,7 @@ import { renderEffect } from './renderEffect'
 
 // TODO: SSR
 
-interface CompiledSlotDescriptor {
+interface DynamicSlot {
   name: string
   fn: Slot
   key?: string
@@ -14,11 +14,7 @@ interface CompiledSlotDescriptor {
 
 export const createSlots = (
   slots: InternalSlots,
-  dynamicSlotsGetter?: () => (
-    | CompiledSlotDescriptor
-    | CompiledSlotDescriptor[]
-    | undefined
-  )[],
+  dynamicSlotsGetter?: () => (DynamicSlot | DynamicSlot[] | undefined)[],
 ): InternalSlots => {
   const dynamicSlotKeys: Record<string, true> = {}
   renderEffect(() => {

From 9479dbaad4a4132b88fb3d846a5c35d5ce72ecde Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sat, 23 Mar 2024 13:05:26 +0900
Subject: [PATCH 20/33] refactor(runtime-vapor): renderEffect -> baseWatch

---
 packages/runtime-vapor/src/apiCreateSlots.ts | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/packages/runtime-vapor/src/apiCreateSlots.ts b/packages/runtime-vapor/src/apiCreateSlots.ts
index 90f2613c2..a67505a78 100644
--- a/packages/runtime-vapor/src/apiCreateSlots.ts
+++ b/packages/runtime-vapor/src/apiCreateSlots.ts
@@ -1,8 +1,8 @@
 // NOTE: this filed is based on `runtime-core/src/helpers/createSlots.ts`
 
 import { isArray } from '@vue/shared'
+import { baseWatch } from '@vue/reactivity'
 import type { InternalSlots, Slot } from './componentSlots'
-import { renderEffect } from './renderEffect'
 
 // TODO: SSR
 
@@ -17,7 +17,7 @@ export const createSlots = (
   dynamicSlotsGetter?: () => (DynamicSlot | DynamicSlot[] | undefined)[],
 ): InternalSlots => {
   const dynamicSlotKeys: Record<string, true> = {}
-  renderEffect(() => {
+  baseWatch(() => {
     const dynamicSlots = dynamicSlotsGetter?.() ?? []
     for (let i = 0; i < dynamicSlots.length; i++) {
       const slot = dynamicSlots[i]
@@ -44,8 +44,7 @@ export const createSlots = (
     // delete stale slots
     for (const key in dynamicSlotKeys) {
       if (
-        // TODO: type (renderWatch)
-        !dynamicSlots.some((slot: any) =>
+        !dynamicSlots.some(slot =>
           isArray(slot) ? slot.some(s => s.name === key) : slot?.name === key,
         )
       ) {

From a54617c6fc0a96f7f3bf4e4d6c05f6e583cf6d51 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sat, 23 Mar 2024 13:06:04 +0900
Subject: [PATCH 21/33] refactor(runtime-vapor): no export createSlots

---
 packages/runtime-vapor/src/index.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts
index eaba6f7f2..cb8b28daf 100644
--- a/packages/runtime-vapor/src/index.ts
+++ b/packages/runtime-vapor/src/index.ts
@@ -119,7 +119,6 @@ export {
 export { createIf } from './apiCreateIf'
 export { createFor } from './apiCreateFor'
 export { createComponent } from './apiCreateComponent'
-export { createSlots } from './apiCreateSlots'
 
 export { resolveComponent, resolveDirective } from './helpers/resolveAssets'
 

From 082f6a1eef5739abf43159b6a9f5118a18ebbbef Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sat, 23 Mar 2024 13:46:48 +0900
Subject: [PATCH 22/33] refactor(runtime-vapor): create component slots

---
 .../__tests__/componentAttrs.spec.ts          |  4 ++
 .../__tests__/componentProps.spec.ts          |  1 +
 .../__tests__/componentSlots.spec.ts          | 52 +++++-----------
 .../runtime-vapor/src/apiCreateComponent.ts   |  4 +-
 packages/runtime-vapor/src/apiCreateSlots.ts  | 56 -----------------
 .../runtime-vapor/src/apiCreateVaporApp.ts    |  1 +
 packages/runtime-vapor/src/component.ts       | 10 +++-
 packages/runtime-vapor/src/componentSlots.ts  | 60 ++++++++++++++++++-
 playground/src/slots.js                       |  5 +-
 9 files changed, 92 insertions(+), 101 deletions(-)
 delete mode 100644 packages/runtime-vapor/src/apiCreateSlots.ts

diff --git a/packages/runtime-vapor/__tests__/componentAttrs.spec.ts b/packages/runtime-vapor/__tests__/componentAttrs.spec.ts
index 42a0a2995..46a472b03 100644
--- a/packages/runtime-vapor/__tests__/componentAttrs.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentAttrs.spec.ts
@@ -41,6 +41,7 @@ describe('attribute fallthrough', () => {
             },
           ],
           null,
+          null,
           true,
         )
       },
@@ -87,6 +88,7 @@ describe('attribute fallthrough', () => {
             },
           ],
           null,
+          null,
           true,
         )
       },
@@ -126,6 +128,7 @@ describe('attribute fallthrough', () => {
             },
           ],
           null,
+          null,
           true,
         )
         return n0
@@ -148,6 +151,7 @@ describe('attribute fallthrough', () => {
             },
           ],
           null,
+          null,
           true,
         )
       },
diff --git a/packages/runtime-vapor/__tests__/componentProps.spec.ts b/packages/runtime-vapor/__tests__/componentProps.spec.ts
index 10a21f8f9..4487c65b1 100644
--- a/packages/runtime-vapor/__tests__/componentProps.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentProps.spec.ts
@@ -245,6 +245,7 @@ describe('component: props', () => {
             id: () => _ctx.id,
           },
           null,
+          null,
           true,
         )
       },
diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
index ea741c5e3..fa3852de0 100644
--- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
@@ -2,7 +2,6 @@
 
 import {
   createComponent,
-  createSlots,
   createVaporApp,
   defineComponent,
   getCurrentInstance,
@@ -69,13 +68,7 @@ describe('component: slots', () => {
 
     const { render } = define({
       render() {
-        return createComponent(
-          Comp,
-          {},
-          createSlots({
-            header: () => template('header')(),
-          }),
-        )
+        return createComponent(Comp, {}, { header: () => template('header')() })
       },
     })
 
@@ -88,12 +81,9 @@ describe('component: slots', () => {
 
   // TODO: test case name
   test('initSlots: instance.slots should be set correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)', () => {
-    const { slots } = renderWithSlots(
-      createSlots({
-        // TODO: normalize from array?
-        default: () => template('<span></span>')(),
-      }),
-    )
+    const { slots } = renderWithSlots({
+      default: () => template('<span></span>')(),
+    })
 
     // expect(
     //   '[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.',
@@ -113,15 +103,11 @@ describe('component: slots', () => {
 
     const { render } = define({
       render() {
-        return createComponent(
-          Child,
-          {},
-          createSlots({ _: 2 as any }, () => [
-            flag1.value
-              ? { name: 'one', fn: () => template('<span></span>')() }
-              : { name: 'two', fn: () => template('<div></div>')() },
-          ]),
-        )
+        return createComponent(Child, {}, { _: 2 as any }, () => [
+          flag1.value
+            ? { name: 'one', fn: () => template('<span></span>')() }
+            : { name: 'two', fn: () => template('<div></div>')() },
+        ])
       },
     })
 
@@ -164,7 +150,7 @@ describe('component: slots', () => {
               Child,
               {},
               // TODO: maybe it is not supported
-              createSlots(flag1.value ? oldSlots : newSlots),
+              flag1.value ? oldSlots : newSlots,
             )
           })()
         },
@@ -193,15 +179,11 @@ describe('component: slots', () => {
 
     const { render } = define({
       setup() {
-        return createComponent(
-          Child,
-          {},
-          createSlots({}, () => [
-            flag1.value
-              ? [{ name: 'header', fn: () => template('header')() }]
-              : [{ name: 'footer', fn: () => template('footer')() }],
-          ]),
-        )
+        return createComponent(Child, {}, {}, () => [
+          flag1.value
+            ? [{ name: 'header', fn: () => template('header')() }]
+            : [{ name: 'footer', fn: () => template('footer')() }],
+        ])
       },
     })
     render()
@@ -235,9 +217,7 @@ describe('component: slots', () => {
           return createComponent(
             Comp,
             {},
-            createSlots({
-              default: () => template('msg')(),
-            }),
+            { default: () => template('msg')() },
           )!
         },
       })
diff --git a/packages/runtime-vapor/src/apiCreateComponent.ts b/packages/runtime-vapor/src/apiCreateComponent.ts
index 3a41f6329..dc693c0a1 100644
--- a/packages/runtime-vapor/src/apiCreateComponent.ts
+++ b/packages/runtime-vapor/src/apiCreateComponent.ts
@@ -5,13 +5,14 @@ import {
 } from './component'
 import { setupComponent } from './apiRender'
 import type { RawProps } from './componentProps'
-import type { Slots } from './componentSlots'
+import type { DinamicSlotsGetter, Slots } from './componentSlots'
 import { withAttrs } from './componentAttrs'
 
 export function createComponent(
   comp: Component,
   rawProps: RawProps | null = null,
   slots: Slots | null = null,
+  dynamicSlotsGetter: DinamicSlotsGetter | null = null,
   singleRoot: boolean = false,
 ) {
   const current = currentInstance!
@@ -19,6 +20,7 @@ export function createComponent(
     comp,
     singleRoot ? withAttrs(rawProps) : rawProps,
     slots,
+    dynamicSlotsGetter,
   )
   setupComponent(instance, singleRoot)
 
diff --git a/packages/runtime-vapor/src/apiCreateSlots.ts b/packages/runtime-vapor/src/apiCreateSlots.ts
deleted file mode 100644
index a67505a78..000000000
--- a/packages/runtime-vapor/src/apiCreateSlots.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-// NOTE: this filed is based on `runtime-core/src/helpers/createSlots.ts`
-
-import { isArray } from '@vue/shared'
-import { baseWatch } from '@vue/reactivity'
-import type { InternalSlots, Slot } from './componentSlots'
-
-// TODO: SSR
-
-interface DynamicSlot {
-  name: string
-  fn: Slot
-  key?: string
-}
-
-export const createSlots = (
-  slots: InternalSlots,
-  dynamicSlotsGetter?: () => (DynamicSlot | DynamicSlot[] | undefined)[],
-): InternalSlots => {
-  const dynamicSlotKeys: Record<string, true> = {}
-  baseWatch(() => {
-    const dynamicSlots = dynamicSlotsGetter?.() ?? []
-    for (let i = 0; i < dynamicSlots.length; i++) {
-      const slot = dynamicSlots[i]
-      // array of dynamic slot generated by <template v-for="..." #[...]>
-      if (isArray(slot)) {
-        for (let j = 0; j < slot.length; j++) {
-          slots[slot[j].name] = slot[j].fn
-          dynamicSlotKeys[slot[j].name] = true
-        }
-      } else if (slot) {
-        // conditional single slot generated by <template v-if="..." #foo>
-        slots[slot.name] = slot.key
-          ? (...args: any[]) => {
-              const res = slot.fn(...args)
-              // attach branch key so each conditional branch is considered a
-              // different fragment
-              if (res) (res as any).key = slot.key
-              return res
-            }
-          : slot.fn
-        dynamicSlotKeys[slot.name] = true
-      }
-    }
-    // delete stale slots
-    for (const key in dynamicSlotKeys) {
-      if (
-        !dynamicSlots.some(slot =>
-          isArray(slot) ? slot.some(s => s.name === key) : slot?.name === key,
-        )
-      ) {
-        delete slots[key]
-      }
-    }
-  })
-  return slots
-}
diff --git a/packages/runtime-vapor/src/apiCreateVaporApp.ts b/packages/runtime-vapor/src/apiCreateVaporApp.ts
index 7356ba006..2d07bbba3 100644
--- a/packages/runtime-vapor/src/apiCreateVaporApp.ts
+++ b/packages/runtime-vapor/src/apiCreateVaporApp.ts
@@ -45,6 +45,7 @@ export function createVaporApp(
           rootComponent,
           rootProps,
           null,
+          null,
           context,
         )
         setupComponent(instance)
diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index c3e996c06..18c090ba9 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -17,7 +17,12 @@ import {
   emit,
   normalizeEmitsOptions,
 } from './componentEmits'
-import { type InternalSlots, type Slots, initSlots } from './componentSlots'
+import {
+  type DinamicSlotsGetter,
+  type InternalSlots,
+  type Slots,
+  initSlots,
+} from './componentSlots'
 import { VaporLifecycleHooks } from './apiLifecycle'
 import { warn } from './warning'
 import { type AppContext, createAppContext } from './apiCreateVaporApp'
@@ -196,6 +201,7 @@ export function createComponentInstance(
   component: ObjectComponent | FunctionalComponent,
   rawProps: RawProps | null,
   slots: Slots | null = null,
+  dynamicSlotsGetter: DinamicSlotsGetter | null = null,
   // application root node only
   appContext: AppContext | null = null,
 ): ComponentInternalInstance {
@@ -293,7 +299,7 @@ export function createComponentInstance(
     // [VaporLifecycleHooks.SERVER_PREFETCH]: null,
   }
   initProps(instance, rawProps, !isFunction(component))
-  initSlots(instance, slots)
+  initSlots(instance, slots, dynamicSlotsGetter)
   instance.emit = emit.bind(null, instance)
 
   return instance
diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
index d5b9f7081..e74440bfc 100644
--- a/packages/runtime-vapor/src/componentSlots.ts
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -1,7 +1,10 @@
-import type { IfAny } from '@vue/shared'
+import { type IfAny, isArray } from '@vue/shared'
+import { baseWatch } from '@vue/reactivity'
 import type { ComponentInternalInstance } from './component'
 import type { Block } from './apiRender'
 
+// TODO: SSR
+
 export type Slot<T extends any = any> = (
   ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
 ) => Block
@@ -12,11 +15,62 @@ export type InternalSlots = {
 
 export type Slots = Readonly<InternalSlots>
 
+export interface DynamicSlot {
+  name: string
+  fn: Slot
+  key?: string
+}
+
+export type DinamicSlotsGetter = () => (DynamicSlot | DynamicSlot[])[]
+
 export const initSlots = (
   instance: ComponentInternalInstance,
   slots: InternalSlots | null,
+  dynamicSlotsGetter: DinamicSlotsGetter | null = null,
 ) => {
   if (!slots) slots = {}
-  // TODO: normalize?
-  instance.slots = slots
+  instance.slots = createSlots(slots, dynamicSlotsGetter)
+}
+
+const createSlots = (
+  slots: InternalSlots,
+  dynamicSlotsGetter: DinamicSlotsGetter | null = null,
+): InternalSlots => {
+  const dynamicSlotKeys: Record<string, true> = {}
+  baseWatch(() => {
+    const dynamicSlots = dynamicSlotsGetter?.() ?? []
+    for (let i = 0; i < dynamicSlots.length; i++) {
+      const slot = dynamicSlots[i]
+      // array of dynamic slot generated by <template v-for="..." #[...]>
+      if (isArray(slot)) {
+        for (let j = 0; j < slot.length; j++) {
+          slots[slot[j].name] = slot[j].fn
+          dynamicSlotKeys[slot[j].name] = true
+        }
+      } else if (slot) {
+        // conditional single slot generated by <template v-if="..." #foo>
+        slots[slot.name] = slot.key
+          ? (...args: any[]) => {
+              const res = slot.fn(...args)
+              // attach branch key so each conditional branch is considered a
+              // different fragment
+              if (res) (res as any).key = slot.key
+              return res
+            }
+          : slot.fn
+        dynamicSlotKeys[slot.name] = true
+      }
+    }
+    // delete stale slots
+    for (const key in dynamicSlotKeys) {
+      if (
+        !dynamicSlots.some(slot =>
+          isArray(slot) ? slot.some(s => s.name === key) : slot?.name === key,
+        )
+      ) {
+        delete slots[key]
+      }
+    }
+  })
+  return slots
 }
diff --git a/playground/src/slots.js b/playground/src/slots.js
index fa6673297..ede7b7081 100644
--- a/playground/src/slots.js
+++ b/playground/src/slots.js
@@ -1,7 +1,6 @@
 import {
   children,
   createComponent,
-  createSlots,
   defineComponent,
   getCurrentInstance,
   insert,
@@ -30,7 +29,7 @@ const Parent = defineComponent({
       const n0 = createComponent(
         Child,
         {},
-        createSlots({
+        {
           mySlot: ({ message, changeMessage }) => {
             const n1 = t1()
             const n2 = /** @type {any} */ (children(n1, 0))
@@ -46,7 +45,7 @@ const Parent = defineComponent({
           //   const n1 = t1()
           //   return n1
           // }
-        }),
+        },
       )
       return n0
     })()

From cf18747ddce7e661b20c0943c273ed358fe8e35f Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sat, 23 Mar 2024 13:58:04 +0900
Subject: [PATCH 23/33] chore(runtime-vapor): component slots tests

---
 .../__tests__/componentSlots.spec.ts          | 30 ++++++++-----------
 1 file changed, 13 insertions(+), 17 deletions(-)

diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
index fa3852de0..37584a549 100644
--- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
@@ -41,19 +41,15 @@ describe('component: slots', () => {
     expect(slots).toMatchObject({ _: 1 })
   })
 
-  test.todo(
-    'initSlots: should normalize object slots (when value is null, string, array)',
-    () => {
-      // TODO: normalize
-    },
-  )
-
-  test.todo(
-    'initSlots: should normalize object slots (when value is function)',
-    () => {
-      // TODO: normalize
-    },
-  )
+  // NOTE: slot normalization is not supported
+  // test.todo(
+  //   'initSlots: should normalize object slots (when value is null, string, array)',
+  //   () => {},
+  // )
+  // test.todo(
+  //   'initSlots: should normalize object slots (when value is function)',
+  //   () => {},
+  // )
 
   test('initSlots: instance.slots should be set correctly', () => {
     let instance: any
@@ -79,8 +75,8 @@ describe('component: slots', () => {
     )
   })
 
-  // TODO: test case name
-  test('initSlots: instance.slots should be set correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)', () => {
+  // runtime-core's "initSlots: instance.slots should be set correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)"
+  test('initSlots: instance.slots should be set correctly', () => {
     const { slots } = renderWithSlots({
       default: () => template('<span></span>')(),
     })
@@ -167,8 +163,8 @@ describe('component: slots', () => {
     },
   )
 
-  // TODO: test case name
-  test('updateSlots: instance.slots should be update correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)', async () => {
+  // runtime-core's "updateSlots: instance.slots should be update correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)"
+  test('updateSlots: instance.slots should be update correctly', async () => {
     const flag1 = ref(true)
 
     let instance: any

From 38af9aac1545b8ff484b47a5ea5c3560e913d8a5 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sat, 23 Mar 2024 14:04:03 +0900
Subject: [PATCH 24/33] feat(runtime-vapor): set dynamic slots updation
 scheduler

---
 packages/runtime-vapor/src/componentSlots.ts | 72 +++++++++++---------
 1 file changed, 39 insertions(+), 33 deletions(-)

diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
index e74440bfc..dd1c772d8 100644
--- a/packages/runtime-vapor/src/componentSlots.ts
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -2,6 +2,7 @@ import { type IfAny, isArray } from '@vue/shared'
 import { baseWatch } from '@vue/reactivity'
 import type { ComponentInternalInstance } from './component'
 import type { Block } from './apiRender'
+import { createVaporPreScheduler } from './scheduler'
 
 // TODO: SSR
 
@@ -29,48 +30,53 @@ export const initSlots = (
   dynamicSlotsGetter: DinamicSlotsGetter | null = null,
 ) => {
   if (!slots) slots = {}
-  instance.slots = createSlots(slots, dynamicSlotsGetter)
+  instance.slots = createSlots(instance, slots, dynamicSlotsGetter)
 }
 
 const createSlots = (
+  instance: ComponentInternalInstance,
   slots: InternalSlots,
   dynamicSlotsGetter: DinamicSlotsGetter | null = null,
 ): InternalSlots => {
   const dynamicSlotKeys: Record<string, true> = {}
-  baseWatch(() => {
-    const dynamicSlots = dynamicSlotsGetter?.() ?? []
-    for (let i = 0; i < dynamicSlots.length; i++) {
-      const slot = dynamicSlots[i]
-      // array of dynamic slot generated by <template v-for="..." #[...]>
-      if (isArray(slot)) {
-        for (let j = 0; j < slot.length; j++) {
-          slots[slot[j].name] = slot[j].fn
-          dynamicSlotKeys[slot[j].name] = true
+  baseWatch(
+    () => {
+      const dynamicSlots = dynamicSlotsGetter?.() ?? []
+      for (let i = 0; i < dynamicSlots.length; i++) {
+        const slot = dynamicSlots[i]
+        // array of dynamic slot generated by <template v-for="..." #[...]>
+        if (isArray(slot)) {
+          for (let j = 0; j < slot.length; j++) {
+            slots[slot[j].name] = slot[j].fn
+            dynamicSlotKeys[slot[j].name] = true
+          }
+        } else if (slot) {
+          // conditional single slot generated by <template v-if="..." #foo>
+          slots[slot.name] = slot.key
+            ? (...args: any[]) => {
+                const res = slot.fn(...args)
+                // attach branch key so each conditional branch is considered a
+                // different fragment
+                if (res) (res as any).key = slot.key
+                return res
+              }
+            : slot.fn
+          dynamicSlotKeys[slot.name] = true
         }
-      } else if (slot) {
-        // conditional single slot generated by <template v-if="..." #foo>
-        slots[slot.name] = slot.key
-          ? (...args: any[]) => {
-              const res = slot.fn(...args)
-              // attach branch key so each conditional branch is considered a
-              // different fragment
-              if (res) (res as any).key = slot.key
-              return res
-            }
-          : slot.fn
-        dynamicSlotKeys[slot.name] = true
       }
-    }
-    // delete stale slots
-    for (const key in dynamicSlotKeys) {
-      if (
-        !dynamicSlots.some(slot =>
-          isArray(slot) ? slot.some(s => s.name === key) : slot?.name === key,
-        )
-      ) {
-        delete slots[key]
+      // delete stale slots
+      for (const key in dynamicSlotKeys) {
+        if (
+          !dynamicSlots.some(slot =>
+            isArray(slot) ? slot.some(s => s.name === key) : slot?.name === key,
+          )
+        ) {
+          delete slots[key]
+        }
       }
-    }
-  })
+    },
+    undefined,
+    { scheduler: createVaporPreScheduler(instance) },
+  )
   return slots
 }

From 13cc5978ac82eb921783128edd965771c9ca9ef5 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sat, 23 Mar 2024 20:30:02 +0900
Subject: [PATCH 25/33] refactor(runtime-vapor): dynamic slots

---
 .../runtime-vapor/src/apiCreateComponent.ts   |   6 +-
 packages/runtime-vapor/src/component.ts       |   6 +-
 packages/runtime-vapor/src/componentSlots.ts  | 102 +++++++++---------
 3 files changed, 59 insertions(+), 55 deletions(-)

diff --git a/packages/runtime-vapor/src/apiCreateComponent.ts b/packages/runtime-vapor/src/apiCreateComponent.ts
index dc693c0a1..fc6e62e17 100644
--- a/packages/runtime-vapor/src/apiCreateComponent.ts
+++ b/packages/runtime-vapor/src/apiCreateComponent.ts
@@ -5,14 +5,14 @@ import {
 } from './component'
 import { setupComponent } from './apiRender'
 import type { RawProps } from './componentProps'
-import type { DinamicSlotsGetter, Slots } from './componentSlots'
+import type { DinamicSlots, Slots } from './componentSlots'
 import { withAttrs } from './componentAttrs'
 
 export function createComponent(
   comp: Component,
   rawProps: RawProps | null = null,
   slots: Slots | null = null,
-  dynamicSlotsGetter: DinamicSlotsGetter | null = null,
+  dynamicSlots: DinamicSlots | null = null,
   singleRoot: boolean = false,
 ) {
   const current = currentInstance!
@@ -20,7 +20,7 @@ export function createComponent(
     comp,
     singleRoot ? withAttrs(rawProps) : rawProps,
     slots,
-    dynamicSlotsGetter,
+    dynamicSlots,
   )
   setupComponent(instance, singleRoot)
 
diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 18c090ba9..527eb38c1 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -18,7 +18,7 @@ import {
   normalizeEmitsOptions,
 } from './componentEmits'
 import {
-  type DinamicSlotsGetter,
+  type DinamicSlots,
   type InternalSlots,
   type Slots,
   initSlots,
@@ -201,7 +201,7 @@ export function createComponentInstance(
   component: ObjectComponent | FunctionalComponent,
   rawProps: RawProps | null,
   slots: Slots | null = null,
-  dynamicSlotsGetter: DinamicSlotsGetter | null = null,
+  dynamicSlots: DinamicSlots | null = null,
   // application root node only
   appContext: AppContext | null = null,
 ): ComponentInternalInstance {
@@ -299,7 +299,7 @@ export function createComponentInstance(
     // [VaporLifecycleHooks.SERVER_PREFETCH]: null,
   }
   initProps(instance, rawProps, !isFunction(component))
-  initSlots(instance, slots, dynamicSlotsGetter)
+  initSlots(instance, slots, dynamicSlots)
   instance.emit = emit.bind(null, instance)
 
   return instance
diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
index dd1c772d8..6c06aaf20 100644
--- a/packages/runtime-vapor/src/componentSlots.ts
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -22,61 +22,65 @@ export interface DynamicSlot {
   key?: string
 }
 
-export type DinamicSlotsGetter = () => (DynamicSlot | DynamicSlot[])[]
+export type DinamicSlots = () => (DynamicSlot | DynamicSlot[])[]
 
 export const initSlots = (
   instance: ComponentInternalInstance,
-  slots: InternalSlots | null,
-  dynamicSlotsGetter: DinamicSlotsGetter | null = null,
+  rawSlots: InternalSlots | null = null,
+  dynamicSlots: DinamicSlots | null = null,
 ) => {
-  if (!slots) slots = {}
-  instance.slots = createSlots(instance, slots, dynamicSlotsGetter)
-}
+  const slots: InternalSlots = {}
+  for (const key in rawSlots) {
+    Object.defineProperty(slots, key, {
+      get: () => rawSlots[key],
+      enumerable: true,
+    })
+  }
 
-const createSlots = (
-  instance: ComponentInternalInstance,
-  slots: InternalSlots,
-  dynamicSlotsGetter: DinamicSlotsGetter | null = null,
-): InternalSlots => {
-  const dynamicSlotKeys: Record<string, true> = {}
-  baseWatch(
-    () => {
-      const dynamicSlots = dynamicSlotsGetter?.() ?? []
-      for (let i = 0; i < dynamicSlots.length; i++) {
-        const slot = dynamicSlots[i]
-        // array of dynamic slot generated by <template v-for="..." #[...]>
-        if (isArray(slot)) {
-          for (let j = 0; j < slot.length; j++) {
-            slots[slot[j].name] = slot[j].fn
-            dynamicSlotKeys[slot[j].name] = true
+  if (dynamicSlots) {
+    const dynamicSlotKeys: Record<string, true> = {}
+    baseWatch(
+      () => {
+        const _dynamicSlots = dynamicSlots()
+        for (let i = 0; i < _dynamicSlots.length; i++) {
+          const slot = _dynamicSlots[i]
+          // array of dynamic slot generated by <template v-for="..." #[...]>
+          if (isArray(slot)) {
+            for (let j = 0; j < slot.length; j++) {
+              slots[slot[j].name] = slot[j].fn
+              dynamicSlotKeys[slot[j].name] = true
+            }
+          } else if (slot) {
+            // conditional single slot generated by <template v-if="..." #foo>
+            slots[slot.name] = slot.key
+              ? (...args: any[]) => {
+                  const res = slot.fn(...args)
+                  // attach branch key so each conditional branch is considered a
+                  // different fragment
+                  if (res) (res as any).key = slot.key
+                  return res
+                }
+              : slot.fn
+            dynamicSlotKeys[slot.name] = true
           }
-        } else if (slot) {
-          // conditional single slot generated by <template v-if="..." #foo>
-          slots[slot.name] = slot.key
-            ? (...args: any[]) => {
-                const res = slot.fn(...args)
-                // attach branch key so each conditional branch is considered a
-                // different fragment
-                if (res) (res as any).key = slot.key
-                return res
-              }
-            : slot.fn
-          dynamicSlotKeys[slot.name] = true
         }
-      }
-      // delete stale slots
-      for (const key in dynamicSlotKeys) {
-        if (
-          !dynamicSlots.some(slot =>
-            isArray(slot) ? slot.some(s => s.name === key) : slot?.name === key,
-          )
-        ) {
-          delete slots[key]
+        // delete stale slots
+        for (const key in dynamicSlotKeys) {
+          if (
+            !_dynamicSlots.some(slot =>
+              isArray(slot)
+                ? slot.some(s => s.name === key)
+                : slot?.name === key,
+            )
+          ) {
+            delete slots[key]
+          }
         }
-      }
-    },
-    undefined,
-    { scheduler: createVaporPreScheduler(instance) },
-  )
-  return slots
+      },
+      undefined,
+      { scheduler: createVaporPreScheduler(instance) },
+    )
+  }
+
+  instance.slots = slots
 }

From f1f8b4213b5d88a192afb3b3e1594232c9ed1d52 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sat, 23 Mar 2024 20:32:25 +0900
Subject: [PATCH 26/33] chore(runtime-vapor): remove dead test

---
 .../runtime-vapor/__tests__/apiInject.spec.ts |  1 -
 .../__tests__/componentSlots.spec.ts          | 45 +------------------
 2 files changed, 2 insertions(+), 44 deletions(-)

diff --git a/packages/runtime-vapor/__tests__/apiInject.spec.ts b/packages/runtime-vapor/__tests__/apiInject.spec.ts
index 1b0b35cd3..3414f136c 100644
--- a/packages/runtime-vapor/__tests__/apiInject.spec.ts
+++ b/packages/runtime-vapor/__tests__/apiInject.spec.ts
@@ -6,7 +6,6 @@ import {
   createComponent,
   createTextNode,
   createVaporApp,
-  getCurrentInstance,
   hasInjectionContext,
   inject,
   nextTick,
diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
index 37584a549..d0252f57c 100644
--- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
@@ -119,49 +119,8 @@ describe('component: slots', () => {
     expect(instance.slots).toHaveProperty('two')
   })
 
-  test.todo(
-    'updateSlots: instance.slots should be updated correctly',
-    async () => {
-      const flag1 = ref(true)
-
-      let instance: any
-      const Child = () => {
-        instance = getCurrentInstance()
-        return template('child')()
-      }
-
-      const oldSlots = {
-        header: () => template('header')(),
-        footer: undefined,
-      }
-      const newSlots = {
-        header: undefined,
-        footer: () => template('footer')(),
-      }
-
-      const { render } = define({
-        setup() {
-          return (() => {
-            return createComponent(
-              Child,
-              {},
-              // TODO: maybe it is not supported
-              flag1.value ? oldSlots : newSlots,
-            )
-          })()
-        },
-      })
-
-      render()
-
-      expect(instance.slots).toMatchObject({ _: null })
-
-      flag1.value = false
-      await nextTick()
-
-      expect(instance.slots).toMatchObject({ _: null })
-    },
-  )
+  // NOTE: it is not supported
+  // test('updateSlots: instance.slots should be updated correctly (when slotType is null)', () => {})
 
   // runtime-core's "updateSlots: instance.slots should be update correctly (when vnode.shapeFlag is not SLOTS_CHILDREN)"
   test('updateSlots: instance.slots should be update correctly', async () => {

From e04e00aa601d40d8e2f2eff6de87109f0c6c52aa Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 24 Mar 2024 00:16:05 +0900
Subject: [PATCH 27/33] chore: remove tracking $slots

---
 packages/runtime-vapor/src/component.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 527eb38c1..41b8396b0 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -341,7 +341,6 @@ function getSlotsProxy(instance: ComponentInternalInstance): Slots {
     instance.slotsProxy ||
     (instance.slotsProxy = new Proxy(instance.slots, {
       get(target, key: string) {
-        track(instance, TrackOpTypes.GET, '$slots')
         return target[key]
       },
     }))

From f6afe50bbd7f6115be7f0903ef91759d55653f2f Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 24 Mar 2024 00:17:13 +0900
Subject: [PATCH 28/33] refactor: for -> extend

---
 packages/runtime-vapor/src/componentSlots.ts | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
index 6c06aaf20..29b84f4a1 100644
--- a/packages/runtime-vapor/src/componentSlots.ts
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -1,4 +1,4 @@
-import { type IfAny, isArray } from '@vue/shared'
+import { type IfAny, extend, isArray } from '@vue/shared'
 import { baseWatch } from '@vue/reactivity'
 import type { ComponentInternalInstance } from './component'
 import type { Block } from './apiRender'
@@ -29,13 +29,7 @@ export const initSlots = (
   rawSlots: InternalSlots | null = null,
   dynamicSlots: DinamicSlots | null = null,
 ) => {
-  const slots: InternalSlots = {}
-  for (const key in rawSlots) {
-    Object.defineProperty(slots, key, {
-      get: () => rawSlots[key],
-      enumerable: true,
-    })
-  }
+  const slots: InternalSlots = extend({}, rawSlots)
 
   if (dynamicSlots) {
     const dynamicSlotKeys: Record<string, true> = {}

From a8a7e4797a6009d7df8cf4a9d2ce256bcaa935fe Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 24 Mar 2024 00:18:56 +0900
Subject: [PATCH 29/33] chore: typo (DinamicSlots -> DynamicSlots)

---
 packages/runtime-vapor/src/apiCreateComponent.ts | 4 ++--
 packages/runtime-vapor/src/component.ts          | 4 ++--
 packages/runtime-vapor/src/componentSlots.ts     | 4 ++--
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/packages/runtime-vapor/src/apiCreateComponent.ts b/packages/runtime-vapor/src/apiCreateComponent.ts
index fc6e62e17..cf282706b 100644
--- a/packages/runtime-vapor/src/apiCreateComponent.ts
+++ b/packages/runtime-vapor/src/apiCreateComponent.ts
@@ -5,14 +5,14 @@ import {
 } from './component'
 import { setupComponent } from './apiRender'
 import type { RawProps } from './componentProps'
-import type { DinamicSlots, Slots } from './componentSlots'
+import type { DynamicSlots, Slots } from './componentSlots'
 import { withAttrs } from './componentAttrs'
 
 export function createComponent(
   comp: Component,
   rawProps: RawProps | null = null,
   slots: Slots | null = null,
-  dynamicSlots: DinamicSlots | null = null,
+  dynamicSlots: DynamicSlots | null = null,
   singleRoot: boolean = false,
 ) {
   const current = currentInstance!
diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 41b8396b0..683138339 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -18,7 +18,7 @@ import {
   normalizeEmitsOptions,
 } from './componentEmits'
 import {
-  type DinamicSlots,
+  type DynamicSlots,
   type InternalSlots,
   type Slots,
   initSlots,
@@ -201,7 +201,7 @@ export function createComponentInstance(
   component: ObjectComponent | FunctionalComponent,
   rawProps: RawProps | null,
   slots: Slots | null = null,
-  dynamicSlots: DinamicSlots | null = null,
+  dynamicSlots: DynamicSlots | null = null,
   // application root node only
   appContext: AppContext | null = null,
 ): ComponentInternalInstance {
diff --git a/packages/runtime-vapor/src/componentSlots.ts b/packages/runtime-vapor/src/componentSlots.ts
index 29b84f4a1..4eba7abf7 100644
--- a/packages/runtime-vapor/src/componentSlots.ts
+++ b/packages/runtime-vapor/src/componentSlots.ts
@@ -22,12 +22,12 @@ export interface DynamicSlot {
   key?: string
 }
 
-export type DinamicSlots = () => (DynamicSlot | DynamicSlot[])[]
+export type DynamicSlots = () => (DynamicSlot | DynamicSlot[])[]
 
 export const initSlots = (
   instance: ComponentInternalInstance,
   rawSlots: InternalSlots | null = null,
-  dynamicSlots: DinamicSlots | null = null,
+  dynamicSlots: DynamicSlots | null = null,
 ) => {
   const slots: InternalSlots = extend({}, rawSlots)
 

From 08cc2bc41ecb24ecdaf2156ccf273856f30f5bd6 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 24 Mar 2024 00:31:11 +0900
Subject: [PATCH 30/33] chore: remove dead codes

---
 packages/runtime-vapor/src/component.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 683138339..f2b2b9388 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -1,4 +1,4 @@
-import { EffectScope, TrackOpTypes, track } from '@vue/reactivity'
+import { EffectScope } from '@vue/reactivity'
 import { EMPTY_OBJ, NOOP, isFunction } from '@vue/shared'
 import type { Block } from './apiRender'
 import type { DirectiveBinding } from './directives'

From c6335341520bd085d326bfdf14f65d0978afc49b Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 24 Mar 2024 00:54:36 +0900
Subject: [PATCH 31/33] chore(runtime-vapor): make slotsProxy optional

---
 packages/runtime-vapor/src/component.ts | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index f2b2b9388..70d465392 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -116,7 +116,7 @@ export interface ComponentInternalInstance {
   refs: Data
 
   attrsProxy: Data | null
-  slotsProxy: Slots | null
+  slotsProxy?: Slots
 
   // lifecycle
   isMounted: boolean
@@ -242,7 +242,6 @@ export function createComponentInstance(
     refs: EMPTY_OBJ,
 
     attrsProxy: null,
-    slotsProxy: null,
 
     // lifecycle
     isMounted: false,

From 5b7620844bfa44653436c58f357fadaf83f49060 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 24 Mar 2024 02:39:34 +0900
Subject: [PATCH 32/33] chore(runtime-vapor): make attrsProxy optional

---
 packages/runtime-vapor/src/component.ts | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 70d465392..482be5661 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -115,7 +115,7 @@ export interface ComponentInternalInstance {
   slots: InternalSlots
   refs: Data
 
-  attrsProxy: Data | null
+  attrsProxy?: Data
   slotsProxy?: Slots
 
   // lifecycle
@@ -241,8 +241,6 @@ export function createComponentInstance(
     slots: EMPTY_OBJ,
     refs: EMPTY_OBJ,
 
-    attrsProxy: null,
-
     // lifecycle
     isMounted: false,
     isUnmounted: false,

From 9510748eed55184b21d49e1efa7b4e38cbae6834 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?=
 <sxzz@sxzz.moe>
Date: Sun, 24 Mar 2024 20:28:41 +0800
Subject: [PATCH 33/33] chore: update

---
 .../__tests__/componentSlots.spec.ts          | 56 +++++++++----------
 1 file changed, 27 insertions(+), 29 deletions(-)

diff --git a/packages/runtime-vapor/__tests__/componentSlots.spec.ts b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
index d0252f57c..ec8788060 100644
--- a/packages/runtime-vapor/__tests__/componentSlots.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentSlots.spec.ts
@@ -12,44 +12,42 @@ import {
 import { makeRender } from './_utils'
 
 const define = makeRender<any>()
+function renderWithSlots(slots: any): any {
+  let instance: any
+  const Comp = defineComponent({
+    render() {
+      const t0 = template('<div></div>')
+      const n0 = t0()
+      instance = getCurrentInstance()
+      return n0
+    },
+  })
 
-describe('component: slots', () => {
-  function renderWithSlots(slots: any): any {
-    let instance: any
-    const Comp = defineComponent({
-      vapor: true,
-      render() {
-        const t0 = template('<div></div>')
-        const n0 = t0()
-        instance = getCurrentInstance()
-        return n0
-      },
-    })
-
-    const { render } = define({
-      render() {
-        return createComponent(Comp, {}, slots)
-      },
-    })
+  const { render } = define({
+    render() {
+      return createComponent(Comp, {}, slots)
+    },
+  })
 
-    render()
-    return instance
-  }
+  render()
+  return instance
+}
 
+describe('component: slots', () => {
   test('initSlots: instance.slots should be set correctly', () => {
     const { slots } = renderWithSlots({ _: 1 })
     expect(slots).toMatchObject({ _: 1 })
   })
 
   // NOTE: slot normalization is not supported
-  // test.todo(
-  //   'initSlots: should normalize object slots (when value is null, string, array)',
-  //   () => {},
-  // )
-  // test.todo(
-  //   'initSlots: should normalize object slots (when value is function)',
-  //   () => {},
-  // )
+  test.todo(
+    'initSlots: should normalize object slots (when value is null, string, array)',
+    () => {},
+  )
+  test.todo(
+    'initSlots: should normalize object slots (when value is function)',
+    () => {},
+  )
 
   test('initSlots: instance.slots should be set correctly', () => {
     let instance: any