From e2edd62f663fd611e048cdcb0644a78e4d48adc6 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Tue, 5 Dec 2023 00:50:34 +0900
Subject: [PATCH 01/15] wip: component props

---
 packages/runtime-vapor/src/component.ts      |  36 ++-
 packages/runtime-vapor/src/componentProps.ts | 270 +++++++++++++++++++
 packages/runtime-vapor/src/render.ts         |  30 ++-
 playground/src/main.ts                       |  21 +-
 playground/src/props.js                      |  94 +++++++
 5 files changed, 433 insertions(+), 18 deletions(-)
 create mode 100644 packages/runtime-vapor/src/componentProps.ts
 create mode 100644 playground/src/props.js

diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index c6195f36e..da30df343 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -1,6 +1,18 @@
 import { EffectScope } from '@vue/reactivity'
+import { EMPTY_OBJ } from '@vue/shared'
 import { Block, BlockFn } from './render'
 import { DirectiveBinding } from './directives'
+import {
+  ComponentPropsOptions,
+  NormalizedPropsOptions,
+  normalizePropsOptions,
+} from './componentProps'
+
+// Conventional ConcreteComponent
+export interface Component<P = {}> {
+  props?: ComponentPropsOptions<P>
+  blockFn: BlockFn
+}
 
 export interface ComponentInternalInstance {
   uid: number
@@ -8,11 +20,17 @@ export interface ComponentInternalInstance {
   block: Block | null
   scope: EffectScope
 
-  component: BlockFn
-  isMounted: boolean
+  blockFn: BlockFn
+  propsOptions: NormalizedPropsOptions
+
+  // state
+  props: Data
 
   /** directives */
   dirs: Map<Node, DirectiveBinding[]>
+
+  // lifecycle
+  isMounted: boolean
   // TODO: registory of provides, appContext, lifecycles, ...
 }
 
@@ -36,18 +54,26 @@ export interface ComponentPublicInstance {}
 
 let uid = 0
 export const createComponentInstance = (
-  component: BlockFn,
+  component: Component,
 ): ComponentInternalInstance => {
   const instance: ComponentInternalInstance = {
     uid: uid++,
     block: null,
     container: null!, // set on mount
     scope: new EffectScope(true /* detached */)!,
+    blockFn: component.blockFn,
 
-    component,
-    isMounted: false,
+    // resolved props and emits options
+    propsOptions: normalizePropsOptions(component),
+    // emitsOptions: normalizeEmitsOptions(type, appContext), // TODO:
+
+    // state
+    props: EMPTY_OBJ,
 
     dirs: new Map(),
+
+    // lifecycle hooks
+    isMounted: false,
     // TODO: registory of provides, appContext, lifecycles, ...
   }
   return instance
diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts
new file mode 100644
index 000000000..eb4c21bf8
--- /dev/null
+++ b/packages/runtime-vapor/src/componentProps.ts
@@ -0,0 +1,270 @@
+// NOTE: runtime-core/src/componentProps.ts
+
+import {
+  EMPTY_ARR,
+  EMPTY_OBJ,
+  camelize,
+  extend,
+  hasOwn,
+  hyphenate,
+  isArray,
+  isFunction,
+  isReservedProp,
+} from '@vue/shared'
+import { shallowReactive, toRaw } from '@vue/reactivity'
+import { Component, ComponentInternalInstance, Data } from './component'
+
+export type ComponentPropsOptions<P = Data> =
+  | ComponentObjectPropsOptions<P>
+  | string[]
+
+export type ComponentObjectPropsOptions<P = Data> = {
+  [K in keyof P]: Prop<P[K]> | null
+}
+
+export type Prop<T, D = T> = PropOptions<T, D> | PropType<T>
+
+type DefaultFactory<T> = (props: Data) => T | null | undefined
+
+export interface PropOptions<T = any, D = T> {
+  type?: PropType<T> | true | null
+  required?: boolean
+  default?: D | DefaultFactory<D> | null | undefined | object
+  validator?(value: unknown): boolean
+  /**
+   * @internal
+   */
+  skipCheck?: boolean
+  /**
+   * @internal
+   */
+  skipFactory?: boolean
+}
+
+export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
+
+type PropConstructor<T = any> =
+  | { new (...args: any[]): T & {} }
+  | { (): T }
+  | PropMethod<T>
+
+type PropMethod<T, TConstructor = any> = [T] extends [
+  ((...args: any) => any) | undefined,
+] // if is function with args, allowing non-required functions
+  ? { new (): TConstructor; (): T; readonly prototype: TConstructor } // Create Function like constructor
+  : never
+
+enum BooleanFlags {
+  shouldCast,
+  shouldCastTrue,
+}
+
+type NormalizedProp =
+  | null
+  | (PropOptions & {
+      [BooleanFlags.shouldCast]?: boolean
+      [BooleanFlags.shouldCastTrue]?: boolean
+    })
+
+export type NormalizedProps = Record<string, NormalizedProp>
+export type NormalizedPropsOptions = [NormalizedProps, string[]] | []
+
+export function initProps(
+  instance: ComponentInternalInstance,
+  rawProps: Data | null,
+) {
+  const props: Data = {}
+
+  const [options, needCastKeys] = instance.propsOptions
+  let rawCastValues: Data | undefined
+  if (rawProps) {
+    for (let key in rawProps) {
+      // key, ref are reserved and never passed down
+      if (isReservedProp(key)) {
+        continue
+      }
+
+      const valueGetter = () => rawProps[key]
+      let camelKey
+      if (options && hasOwn(options, (camelKey = camelize(key)))) {
+        if (!needCastKeys || !needCastKeys.includes(camelKey)) {
+          // NOTE: must getter
+          // props[camelKey] = value
+          Object.defineProperty(props, camelKey, {
+            get() {
+              return valueGetter()
+            },
+          })
+        } else {
+          // NOTE: must getter
+          // ;(rawCastValues || (rawCastValues = {}))[camelKey] = value
+          rawCastValues || (rawCastValues = {})
+          Object.defineProperty(rawCastValues, camelKey, {
+            get() {
+              return valueGetter()
+            },
+          })
+        }
+      } else {
+        // TODO:
+      }
+    }
+  }
+
+  if (needCastKeys) {
+    const rawCurrentProps = toRaw(props)
+    const castValues = rawCastValues || EMPTY_OBJ
+    for (let i = 0; i < needCastKeys.length; i++) {
+      const key = needCastKeys[i]
+
+      // NOTE: must getter
+      // props[key] = resolvePropValue(
+      //   options!,
+      //   rawCurrentProps,
+      //   key,
+      //   castValues[key],
+      //   instance,
+      //   !hasOwn(castValues, key),
+      // )
+      Object.defineProperty(props, key, {
+        get() {
+          return resolvePropValue(
+            options!,
+            rawCurrentProps,
+            key,
+            castValues[key],
+            instance,
+            !hasOwn(castValues, key),
+          )
+        },
+      })
+    }
+  }
+
+  instance.props = shallowReactive(props)
+}
+
+function resolvePropValue(
+  options: NormalizedProps,
+  props: Data,
+  key: string,
+  value: unknown,
+  instance: ComponentInternalInstance,
+  isAbsent: boolean,
+) {
+  const opt = options[key]
+  if (opt != null) {
+    const hasDefault = hasOwn(opt, 'default')
+    // default values
+    if (hasDefault && value === undefined) {
+      const defaultValue = opt.default
+      if (
+        opt.type !== Function &&
+        !opt.skipFactory &&
+        isFunction(defaultValue)
+      ) {
+        // TODO: caching?
+        // const { propsDefaults } = instance
+        // if (key in propsDefaults) {
+        //   value = propsDefaults[key]
+        // } else {
+        //   setCurrentInstance(instance)
+        //   value = propsDefaults[key] = defaultValue.call(
+        //     __COMPAT__ &&
+        //       isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
+        //       ? createPropsDefaultThis(instance, props, key)
+        //       : null,
+        //     props,
+        //   )
+        //   unsetCurrentInstance()
+        // }
+      } else {
+        value = defaultValue
+      }
+    }
+    // boolean casting
+    if (opt[BooleanFlags.shouldCast]) {
+      if (isAbsent && !hasDefault) {
+        value = false
+      } else if (
+        opt[BooleanFlags.shouldCastTrue] &&
+        (value === '' || value === hyphenate(key))
+      ) {
+        value = true
+      }
+    }
+  }
+  return value
+}
+
+export function normalizePropsOptions(comp: Component): NormalizedPropsOptions {
+  // TODO: cahching?
+
+  const raw = comp.props as any
+  const normalized: NormalizedPropsOptions[0] = {}
+  const needCastKeys: NormalizedPropsOptions[1] = []
+
+  if (!raw) {
+    return EMPTY_ARR as any
+  }
+
+  if (isArray(raw)) {
+    for (let i = 0; i < raw.length; i++) {
+      const normalizedKey = camelize(raw[i])
+      if (validatePropName(normalizedKey)) {
+        normalized[normalizedKey] = EMPTY_OBJ
+      }
+    }
+  } else if (raw) {
+    for (const key in raw) {
+      const normalizedKey = camelize(key)
+      if (validatePropName(normalizedKey)) {
+        const opt = raw[key]
+        const prop: NormalizedProp = (normalized[normalizedKey] =
+          isArray(opt) || isFunction(opt) ? { type: opt } : extend({}, opt))
+        if (prop) {
+          const booleanIndex = getTypeIndex(Boolean, prop.type)
+          const stringIndex = getTypeIndex(String, prop.type)
+          prop[BooleanFlags.shouldCast] = booleanIndex > -1
+          prop[BooleanFlags.shouldCastTrue] =
+            stringIndex < 0 || booleanIndex < stringIndex
+          // if the prop needs boolean casting or default value
+          if (booleanIndex > -1 || hasOwn(prop, 'default')) {
+            needCastKeys.push(normalizedKey)
+          }
+        }
+      }
+    }
+  }
+
+  const res: NormalizedPropsOptions = [normalized, needCastKeys]
+  return res
+}
+
+function validatePropName(key: string) {
+  if (key[0] !== '$') {
+    return true
+  }
+  return false
+}
+
+function getType(ctor: Prop<any>): string {
+  const match = ctor && ctor.toString().match(/^\s*(function|class) (\w+)/)
+  return match ? match[2] : ctor === null ? 'null' : ''
+}
+
+function isSameType(a: Prop<any>, b: Prop<any>): boolean {
+  return getType(a) === getType(b)
+}
+
+function getTypeIndex(
+  type: Prop<any>,
+  expectedTypes: PropType<any> | void | null | true,
+): number {
+  if (isArray(expectedTypes)) {
+    return expectedTypes.findIndex((t) => isSameType(t, type))
+  } else if (isFunction(expectedTypes)) {
+    return isSameType(expectedTypes, type) ? 0 : -1
+  }
+  return -1
+}
diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts
index 82a3bfde7..c033d5518 100644
--- a/packages/runtime-vapor/src/render.ts
+++ b/packages/runtime-vapor/src/render.ts
@@ -6,10 +6,13 @@ import {
 } from '@vue/shared'
 
 import {
+  Component,
   ComponentInternalInstance,
   createComponentInstance,
   setCurrentInstance,
+  unsetCurrentInstance,
 } from './component'
+import { initProps } from './componentProps'
 
 export type Block = Node | Fragment | Block[]
 export type ParentBlock = ParentNode | Node[]
@@ -17,13 +20,15 @@ export type Fragment = { nodes: Block; anchor: Node }
 export type BlockFn = (props?: any) => Block
 
 export function render(
-  comp: BlockFn,
+  comp: Component,
+  props: any,
   container: string | ParentNode,
 ): ComponentInternalInstance {
-  const instance = createComponentInstance(comp)
-  setCurrentInstance(instance)
-  mountComponent(instance, (container = normalizeContainer(container)))
-  return instance
+  return mountComponent(
+    comp,
+    props,
+    (container = normalizeContainer(container)),
+  )
 }
 
 export function normalizeContainer(container: string | ParentNode): ParentNode {
@@ -33,18 +38,27 @@ export function normalizeContainer(container: string | ParentNode): ParentNode {
 }
 
 export const mountComponent = (
-  instance: ComponentInternalInstance,
+  comp: Component,
+  props: any,
   container: ParentNode,
-) => {
+): ComponentInternalInstance => {
+  const instance = createComponentInstance(comp)
+  initProps(instance, props)
+
+  setCurrentInstance(instance)
   instance.container = container
   const block = instance.scope.run(
-    () => (instance.block = instance.component()),
+    () => (instance.block = instance.blockFn(instance.props)),
   )!
   insert(block, instance.container)
   instance.isMounted = true
+  unsetCurrentInstance()
+
   // TODO: lifecycle hooks (mounted, ...)
   // const { m } = instance
   // m && invoke(m)
+
+  return instance
 }
 
 export const unmountComponent = (instance: ComponentInternalInstance) => {
diff --git a/playground/src/main.ts b/playground/src/main.ts
index 06b2c60ad..0c7a34b16 100644
--- a/playground/src/main.ts
+++ b/playground/src/main.ts
@@ -1,11 +1,22 @@
+import { extend } from '@vue/shared'
 import { render } from 'vue/vapor'
 
-const modules = import.meta.glob<any>('./*.vue')
+const modules = import.meta.glob<any>('./*.(vue|js)')
 const mod = (modules['.' + location.pathname] || modules['./App.vue'])()
 
 mod.then(({ default: m }) => {
-  render(() => {
-    const returned = m.setup?.({}, { expose() {} })
-    return m.render(returned)
-  }, '#app')
+  render(
+    {
+      props: m.props,
+      blockFn: props => {
+        const returned = m.setup?.(props, { expose() {} })
+        const ctx = extend(props, returned) // TODO: merge
+        return m.render(ctx)
+      }
+    },
+    {
+      /* TODO: raw props */
+    },
+    '#app'
+  )
 })
diff --git a/playground/src/props.js b/playground/src/props.js
new file mode 100644
index 000000000..2aa692f84
--- /dev/null
+++ b/playground/src/props.js
@@ -0,0 +1,94 @@
+import { extend } from '@vue/shared'
+import { watch } from 'vue'
+import {
+  children,
+  on,
+  ref,
+  template,
+  effect,
+  setText,
+  render as renderComponent // TODO:
+} from '@vue/vapor'
+
+export default {
+  props: undefined,
+
+  setup(_, {}) {
+    const count = ref(1)
+    const handleClick = () => {
+      count.value++
+    }
+    return { count, handleClick }
+  },
+
+  render(_ctx) {
+    const t0 = template('<button></button>')
+    const n0 = t0()
+    const {
+      0: [n1]
+    } = children(n0)
+    on(n1, 'click', _ctx.handleClick)
+    effect(() => {
+      setText(n1, void 0, _ctx.count.value)
+    })
+
+    // TODO: create component fn?
+    // const c0 = createComponent(...)
+    // insert(n0, c0)
+    renderComponent(
+      {
+        props: child.props,
+        blockFn: props => {
+          const returned = child.setup?.(props, { expose() {} })
+          const ctx = extend(props, returned) // TODO: merge
+          return child.render(ctx)
+        }
+      },
+      // TODO: proxy??
+      {
+        /* <Comp :count="count" /> */
+        get count() {
+          return _ctx.count.value
+        },
+
+        /* <Comp :inline-double="count * 2" /> */
+        get inlineDouble() {
+          return _ctx.count.value * 2
+        }
+      },
+      n0
+    )
+
+    return n0
+  }
+}
+
+const child = {
+  props: {
+    count: { type: Number, default: 1 },
+    inlineDouble: { type: Number, default: 2 }
+  },
+
+  setup(props) {
+    watch(
+      () => props.count,
+      v => console.log('count changed', v)
+    )
+    watch(
+      () => props.inlineDouble,
+      v => console.log('inlineDouble changed', v)
+    )
+  },
+
+  render(_ctx) {
+    const t0 = template('<p></p>')
+    const n0 = t0()
+    const {
+      0: [n1]
+    } = children(n0)
+    effect(() => {
+      setText(n1, void 0, _ctx.count + ' * 2 = ' + _ctx.inlineDouble)
+    })
+    return n0
+  }
+}

From 79aa1d9d38048390409d68080273888ab4f9800e Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Thu, 7 Dec 2023 23:59:15 +0900
Subject: [PATCH 02/15] refactor(runtime-vapor): remove dead console.log

---
 packages/runtime-vapor/src/render.ts | 5 -----
 playground/src/props.js              | 1 -
 2 files changed, 6 deletions(-)

diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts
index c1c8c6615..55c09c796 100644
--- a/packages/runtime-vapor/src/render.ts
+++ b/packages/runtime-vapor/src/render.ts
@@ -68,11 +68,6 @@ export function mountComponent(
 
     const setupFn =
       typeof component === 'function' ? component : component.setup
-    console.log(
-      '🚀 ~ file: render.ts:70 ~ block ~ setupFn:',
-      component,
-      setupFn,
-    )
 
     const state = setupFn(props, ctx)
 
diff --git a/playground/src/props.js b/playground/src/props.js
index 4ac1750cc..be43c6dd2 100644
--- a/playground/src/props.js
+++ b/playground/src/props.js
@@ -1,4 +1,3 @@
-import { extend } from '@vue/shared'
 import { watch } from 'vue'
 import {
   children,

From b6e699a1d11f6b188701bd3fdbc8058ff52eeff8 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Fri, 8 Dec 2023 00:17:43 +0900
Subject: [PATCH 03/15] feat(runtime-vapor): componentPublicInstance

---
 packages/runtime-vapor/src/component.ts       |  7 ++++
 .../src/componentPublicInstance.ts            | 22 +++++++++++
 packages/runtime-vapor/src/render.ts          | 37 ++++---------------
 playground/src/props.js                       |  6 +--
 4 files changed, 40 insertions(+), 32 deletions(-)
 create mode 100644 packages/runtime-vapor/src/componentPublicInstance.ts

diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 972210309..ad9f116e4 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -31,8 +31,12 @@ export interface ComponentInternalInstance {
   component: FunctionalComponent | ObjectComponent
   propsOptions: NormalizedPropsOptions
 
+  // TODO: type
+  proxy: Data | null
+
   // state
   props: Data
+  setupState: Data
 
   /** directives */
   dirs: Map<Node, DirectiveBinding[]>
@@ -71,8 +75,11 @@ export const createComponentInstance = (
     propsOptions: normalizePropsOptions(component),
     // emitsOptions: normalizeEmitsOptions(type, appContext), // TODO:
 
+    proxy: null,
+
     // state
     props: EMPTY_OBJ,
+    setupState: EMPTY_OBJ,
 
     dirs: new Map(),
 
diff --git a/packages/runtime-vapor/src/componentPublicInstance.ts b/packages/runtime-vapor/src/componentPublicInstance.ts
new file mode 100644
index 000000000..8bfacf981
--- /dev/null
+++ b/packages/runtime-vapor/src/componentPublicInstance.ts
@@ -0,0 +1,22 @@
+import { hasOwn } from '@vue/shared'
+import { type ComponentInternalInstance } from './component'
+
+export interface ComponentRenderContext {
+  [key: string]: any
+  _: ComponentInternalInstance
+}
+
+export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
+  get({ _: instance }: ComponentRenderContext, key: string) {
+    let normalizedProps
+    const { setupState, props } = instance
+    if (hasOwn(setupState, key)) {
+      return setupState[key]
+    } else if (
+      (normalizedProps = instance.propsOptions[0]) &&
+      hasOwn(normalizedProps, key)
+    ) {
+      return props![key]
+    }
+  },
+}
diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts
index 55c09c796..9d0a08d8e 100644
--- a/packages/runtime-vapor/src/render.ts
+++ b/packages/runtime-vapor/src/render.ts
@@ -1,5 +1,5 @@
-import { reactive } from '@vue/reactivity'
-import { Data, extend } from '@vue/shared'
+import { markRaw, proxyRefs } from '@vue/reactivity'
+import { Data } from '@vue/shared'
 
 import {
   type Component,
@@ -13,6 +13,7 @@ import { initProps } from './componentProps'
 
 import { invokeDirectiveHook } from './directives'
 import { insert, remove } from './dom'
+import { PublicInstanceProxyHandlers } from './componentPublicInstance'
 
 export type Block = Node | Fragment | Block[]
 export type ParentBlock = ParentNode | Node[]
@@ -35,26 +36,6 @@ export function normalizeContainer(container: string | ParentNode): ParentNode {
     : container
 }
 
-// export const mountComponent = (
-//   comp: Component,
-//   props: any,
-//   container: ParentNode,
-// ): ComponentInternalInstance => {
-//   const instance = createComponentInstance(comp)
-//   initProps(instance, props)
-
-//   setCurrentInstance(instance)
-//   instance.container = container
-//   const block = instance.scope.run(
-//     () => (instance.block = instance.blockFn(instance.props)),
-//   )!
-//   insert(block, instance.container)
-//   instance.isMounted = true
-//   unsetCurrentInstance()
-
-//   return instance
-// }
-
 export function mountComponent(
   instance: ComponentInternalInstance,
   container: ParentNode,
@@ -70,14 +51,12 @@ export function mountComponent(
       typeof component === 'function' ? component : component.setup
 
     const state = setupFn(props, ctx)
-
+    instance.proxy = markRaw(
+      new Proxy({ _: instance }, PublicInstanceProxyHandlers),
+    )
     if (state && '__isScriptSetup' in state) {
-      return (instance.block = component.render(
-        reactive(
-          // TODO: merge
-          extend(props, state),
-        ),
-      ))
+      instance.setupState = proxyRefs(state)
+      return (instance.block = component.render(instance.proxy))
     } else {
       return (instance.block = state as Block)
     }
diff --git a/playground/src/props.js b/playground/src/props.js
index be43c6dd2..b80768dcc 100644
--- a/playground/src/props.js
+++ b/playground/src/props.js
@@ -36,7 +36,7 @@ export default {
     } = children(n0)
     on(n1, 'click', _ctx.handleClick)
     effect(() => {
-      setText(n1, void 0, _ctx.count.value)
+      setText(n1, void 0, _ctx.count)
     })
 
     // TODO: create component fn?
@@ -49,12 +49,12 @@ export default {
       {
         /* <Comp :count="count" /> */
         get count() {
-          return _ctx.count.value
+          return _ctx.count
         },
 
         /* <Comp :inline-double="count * 2" /> */
         get inlineDouble() {
-          return _ctx.count.value * 2
+          return _ctx.count * 2
         }
       },
       n0

From 2460435eaedbb16ebc9bbbf7739e2324e29a08ad Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Fri, 8 Dec 2023 00:36:23 +0900
Subject: [PATCH 04/15] chore(runtime-vapor): fix type import

---
 packages/runtime-vapor/src/component.ts      | 6 +++---
 packages/runtime-vapor/src/componentProps.ts | 2 +-
 packages/runtime-vapor/src/render.ts         | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index ad9f116e4..608acc6a1 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -2,10 +2,10 @@ import { EffectScope } from '@vue/reactivity'
 
 import { EMPTY_OBJ } from '@vue/shared'
 import { Block } from './render'
-import { DirectiveBinding } from './directives'
+import { type DirectiveBinding } from './directives'
 import {
-  ComponentPropsOptions,
-  NormalizedPropsOptions,
+  type ComponentPropsOptions,
+  type NormalizedPropsOptions,
   normalizePropsOptions,
 } from './componentProps'
 import type { Data } from '@vue/shared'
diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts
index 693de1378..c12ce1f13 100644
--- a/packages/runtime-vapor/src/componentProps.ts
+++ b/packages/runtime-vapor/src/componentProps.ts
@@ -13,7 +13,7 @@ import {
   isReservedProp,
 } from '@vue/shared'
 import { shallowReactive, toRaw } from '@vue/reactivity'
-import { ComponentInternalInstance, Component } from './component'
+import { type ComponentInternalInstance, type Component } from './component'
 
 export type ComponentPropsOptions<P = Data> =
   | ComponentObjectPropsOptions<P>
diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts
index 9d0a08d8e..bc1756363 100644
--- a/packages/runtime-vapor/src/render.ts
+++ b/packages/runtime-vapor/src/render.ts
@@ -1,5 +1,5 @@
 import { markRaw, proxyRefs } from '@vue/reactivity'
-import { Data } from '@vue/shared'
+import { type Data } from '@vue/shared'
 
 import {
   type Component,

From 551e5c10301822736e017ab18cb6637fc7407665 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Fri, 8 Dec 2023 19:43:30 +0900
Subject: [PATCH 05/15] chore(runtime-vapor): remove props type check option

---
 packages/runtime-vapor/src/componentProps.ts | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts
index c12ce1f13..5cd0f1d21 100644
--- a/packages/runtime-vapor/src/componentProps.ts
+++ b/packages/runtime-vapor/src/componentProps.ts
@@ -32,10 +32,6 @@ export interface PropOptions<T = any, D = T> {
   required?: boolean
   default?: D | DefaultFactory<D> | null | undefined | object
   validator?(value: unknown): boolean
-  /**
-   * @internal
-   */
-  skipCheck?: boolean
   /**
    * @internal
    */

From 7bcb160c0253c4464a87896b96590e592cc10a69 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Fri, 8 Dec 2023 19:45:36 +0900
Subject: [PATCH 06/15] chore(runtime-vapor): remove dead props

---
 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 ac8746f4d..5ff3b86b8 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -45,7 +45,6 @@ export interface ComponentInternalInstance {
   // lifecycle
   get isMounted(): boolean
   isMountedRef: Ref<boolean>
-  isMounted: boolean
   // TODO: registory of provides, appContext, lifecycles, ...
 }
 

From 4b55722c0c5fb689459f16900dcaebfb7a39c2f9 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sat, 9 Dec 2023 00:49:28 +0900
Subject: [PATCH 07/15] feat(runtime-vapor): component emits

---
 packages/runtime-vapor/src/component.ts       | 39 +++++++-
 packages/runtime-vapor/src/componentEmits.ts  | 95 +++++++++++++++++++
 .../src/componentPublicInstance.ts            |  2 +
 packages/runtime-vapor/src/render.ts          |  7 +-
 playground/src/emits.js                       | 85 +++++++++++++++++
 5 files changed, 222 insertions(+), 6 deletions(-)
 create mode 100644 packages/runtime-vapor/src/componentEmits.ts
 create mode 100644 playground/src/emits.js

diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 5ff3b86b8..fb068539e 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -1,6 +1,7 @@
 import { EffectScope, Ref, ref } from '@vue/reactivity'
-
+import type { Data } from '@vue/shared'
 import { EMPTY_OBJ } from '@vue/shared'
+
 import { Block } from './render'
 import { type DirectiveBinding } from './directive'
 import {
@@ -9,17 +10,25 @@ import {
   normalizePropsOptions,
 } from './componentProps'
 
-import type { Data } from '@vue/shared'
+import {
+  type EmitFn,
+  type EmitsOptions,
+  type ObjectEmitsOptions,
+  emit,
+  normalizeEmitsOptions,
+} from './componentEmits'
 
 export type Component = FunctionalComponent | ObjectComponent
 
 export type SetupFn = (props: any, ctx: any) => Block | Data
 export type FunctionalComponent = SetupFn & {
   props: ComponentPropsOptions
+  emits: EmitsOptions
   render(ctx: any): Block
 }
 export interface ObjectComponent {
   props: ComponentPropsOptions
+  emits: EmitsOptions
   setup: SetupFn
   render(ctx: any): Block
 }
@@ -29,8 +38,14 @@ export interface ComponentInternalInstance {
   container: ParentNode
   block: Block | null
   scope: EffectScope
+
+  // conventional vnode.type
   component: FunctionalComponent | ObjectComponent
+  rawProps: Data
+
+  // normalized options
   propsOptions: NormalizedPropsOptions
+  emitsOptions: ObjectEmitsOptions | null
 
   // TODO: type
   proxy: Data | null
@@ -38,6 +53,8 @@ export interface ComponentInternalInstance {
   // state
   props: Data
   setupState: Data
+  emit: EmitFn
+  emitted: Record<string, boolean> | null
 
   /** directives */
   dirs: Map<Node, DirectiveBinding[]>
@@ -45,6 +62,8 @@ export interface ComponentInternalInstance {
   // lifecycle
   get isMounted(): boolean
   isMountedRef: Ref<boolean>
+  get isUnmounted(): boolean
+  isUnmountedRef: Ref<boolean>
   // TODO: registory of provides, appContext, lifecycles, ...
 }
 
@@ -65,18 +84,25 @@ export const unsetCurrentInstance = () => {
 let uid = 0
 export const createComponentInstance = (
   component: ObjectComponent | FunctionalComponent,
+  rawProps: Data,
 ): ComponentInternalInstance => {
   const isMountedRef = ref(false)
+  const isUnmountedRef = ref(false)
   const instance: ComponentInternalInstance = {
     uid: uid++,
     block: null,
     container: null!, // set on mount
     scope: new EffectScope(true /* detached */)!,
     component,
+    rawProps,
 
     // resolved props and emits options
     propsOptions: normalizePropsOptions(component),
-    // emitsOptions: normalizeEmitsOptions(type, appContext), // TODO:
+    emitsOptions: normalizeEmitsOptions(component),
+
+    // emit
+    emit: null!, // to be set immediately
+    emitted: null,
 
     proxy: null,
 
@@ -91,7 +117,14 @@ export const createComponentInstance = (
       return isMountedRef.value
     },
     isMountedRef,
+    get isUnmounted() {
+      return isUnmountedRef.value
+    },
+    isUnmountedRef,
     // TODO: registory of provides, appContext, lifecycles, ...
   }
+
+  instance.emit = emit.bind(null, instance)
+
   return instance
 }
diff --git a/packages/runtime-vapor/src/componentEmits.ts b/packages/runtime-vapor/src/componentEmits.ts
new file mode 100644
index 000000000..fcd863e2f
--- /dev/null
+++ b/packages/runtime-vapor/src/componentEmits.ts
@@ -0,0 +1,95 @@
+// NOTE: runtime-core/src/componentEmits.ts
+
+import {
+  type UnionToIntersection,
+  camelize,
+  extend,
+  hyphenate,
+  isArray,
+  isFunction,
+  toHandlerKey,
+} from '@vue/shared'
+import { type Component, type ComponentInternalInstance } from './component'
+
+export type ObjectEmitsOptions = Record<
+  string,
+  ((...args: any[]) => any) | null
+>
+
+export type EmitsOptions = ObjectEmitsOptions | string[]
+
+export type EmitFn<
+  Options = ObjectEmitsOptions,
+  Event extends keyof Options = keyof Options,
+> = Options extends Array<infer V>
+  ? (event: V, ...args: any[]) => void
+  : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function
+    ? (event: string, ...args: any[]) => void
+    : UnionToIntersection<
+        {
+          [key in Event]: Options[key] extends (...args: infer Args) => any
+            ? (event: key, ...args: Args) => void
+            : (event: key, ...args: any[]) => void
+        }[Event]
+      >
+
+export function emit(
+  instance: ComponentInternalInstance,
+  event: string,
+  ...rawArgs: any[]
+) {
+  if (instance.isUnmounted) return
+  const props = instance.rawProps // TODO: raw props
+
+  let args = rawArgs
+
+  // TODO: modelListener
+
+  let handlerName
+  let handler =
+    props[(handlerName = toHandlerKey(event))] ||
+    // also try camelCase event handler (#2249)
+    props[(handlerName = toHandlerKey(camelize(event)))]
+  // for v-model update:xxx events, also trigger kebab-case equivalent
+  // for props passed via kebab-case
+  if (!handler) {
+    handler = props[(handlerName = toHandlerKey(hyphenate(event)))]
+  }
+
+  if (handler && isFunction(handler)) {
+    // TODO: callWithAsyncErrorHandling
+    handler(...args)
+  }
+
+  const onceHandler = props[handlerName + `Once`]
+  if (onceHandler) {
+    if (!instance.emitted) {
+      instance.emitted = {}
+    } else if (instance.emitted[handlerName]) {
+      return
+    }
+
+    if (isFunction(onceHandler)) {
+      instance.emitted[handlerName] = true
+      // TODO: callWithAsyncErrorHandling
+      onceHandler(...args)
+    }
+  }
+}
+
+export function normalizeEmitsOptions(
+  comp: Component,
+): ObjectEmitsOptions | null {
+  // TODO: caching?
+
+  const raw = comp.emits
+  let normalized: ObjectEmitsOptions = {}
+
+  if (isArray(raw)) {
+    raw.forEach((key) => (normalized[key] = null))
+  } else {
+    extend(normalized, raw)
+  }
+
+  return normalized
+}
diff --git a/packages/runtime-vapor/src/componentPublicInstance.ts b/packages/runtime-vapor/src/componentPublicInstance.ts
index 8bfacf981..2adcc3402 100644
--- a/packages/runtime-vapor/src/componentPublicInstance.ts
+++ b/packages/runtime-vapor/src/componentPublicInstance.ts
@@ -20,3 +20,5 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
     }
   },
 }
+
+// TODO: publicPropertiesMap
diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts
index 422d5e689..62fb595aa 100644
--- a/packages/runtime-vapor/src/render.ts
+++ b/packages/runtime-vapor/src/render.ts
@@ -26,7 +26,7 @@ export function render(
   props: Data,
   container: string | ParentNode,
 ): ComponentInternalInstance {
-  const instance = createComponentInstance(comp)
+  const instance = createComponentInstance(comp, props)
   initProps(instance, props)
   return mountComponent(instance, (container = normalizeContainer(container)))
 }
@@ -45,8 +45,8 @@ export function mountComponent(
 
   setCurrentInstance(instance)
   const block = instance.scope.run(() => {
-    const { component, props } = instance
-    const ctx = { expose: () => {} }
+    const { component, props, emit } = instance
+    const ctx = { emit, expose: () => {} }
 
     const setupFn =
       typeof component === 'function' ? component : component.setup
@@ -82,6 +82,7 @@ export function unmountComponent(instance: ComponentInternalInstance) {
   scope.stop()
   block && remove(block, container)
   instance.isMountedRef.value = false
+  instance.isUnmountedRef.value = true
   invokeDirectiveHook(instance, 'unmounted')
   unsetCurrentInstance()
 
diff --git a/playground/src/emits.js b/playground/src/emits.js
new file mode 100644
index 000000000..8f4b389cc
--- /dev/null
+++ b/playground/src/emits.js
@@ -0,0 +1,85 @@
+import {
+  children,
+  on,
+  ref,
+  template,
+  effect,
+  setText,
+  render as renderComponent // TODO:
+} from '@vue/vapor'
+
+export default {
+  props: undefined,
+
+  setup(_, {}) {
+    const count = ref(1)
+    const setCount = v => {
+      count.value = v
+    }
+
+    const __returned__ = { count, setCount }
+
+    Object.defineProperty(__returned__, '__isScriptSetup', {
+      enumerable: false,
+      value: true
+    })
+
+    return __returned__
+  },
+
+  render(_ctx) {
+    const t0 = template('<p></p>')
+    const n0 = t0()
+    const {
+      0: [n1]
+    } = children(n0)
+
+    effect(() => {
+      setText(n1, void 0, _ctx.count)
+    })
+
+    renderComponent(
+      child,
+      {
+        get count() {
+          return _ctx.count
+        },
+        'onClick:child': _ctx.setCount
+      },
+      n0
+    )
+
+    return n0
+  }
+}
+
+const child = {
+  props: {
+    count: { type: Number, default: 1 }
+  },
+
+  setup(props, { emit }) {
+    const handleClick = () => {
+      emit('click:child', props.count * 2)
+    }
+
+    const __returned__ = { handleClick }
+
+    Object.defineProperty(__returned__, '__isScriptSetup', {
+      enumerable: false,
+      value: true
+    })
+
+    return __returned__
+  },
+
+  render(_ctx) {
+    const t0 = template('<button>set count * 2</button>')
+    const n0 = t0()
+    const {
+      0: [n1]
+    } = children(n0)
+    on(n1, 'click', _ctx.handleClick)
+    return n0
+  }
+}

From 123108f54ff2b7719c6365d54343c9fd046533c1 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, 9 Dec 2023 03:24:25 +0800
Subject: [PATCH 08/15] refactor

---
 packages/runtime-vapor/src/component.ts      | 4 +---
 packages/runtime-vapor/src/componentProps.ts | 5 +++--
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index 5ff3b86b8..3cdffd04c 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -1,14 +1,12 @@
 import { EffectScope, Ref, ref } from '@vue/reactivity'
-
 import { EMPTY_OBJ } from '@vue/shared'
 import { Block } from './render'
-import { type DirectiveBinding } from './directive'
 import {
   type ComponentPropsOptions,
   type NormalizedPropsOptions,
   normalizePropsOptions,
 } from './componentProps'
-
+import type { DirectiveBinding } from './directive'
 import type { Data } from '@vue/shared'
 
 export type Component = FunctionalComponent | ObjectComponent
diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts
index 5cd0f1d21..0b7e42a6e 100644
--- a/packages/runtime-vapor/src/componentProps.ts
+++ b/packages/runtime-vapor/src/componentProps.ts
@@ -1,7 +1,7 @@
 // NOTE: runtime-core/src/componentProps.ts
 
 import {
-  Data,
+  type Data,
   EMPTY_ARR,
   EMPTY_OBJ,
   camelize,
@@ -13,7 +13,7 @@ import {
   isReservedProp,
 } from '@vue/shared'
 import { shallowReactive, toRaw } from '@vue/reactivity'
-import { type ComponentInternalInstance, type Component } from './component'
+import type { ComponentInternalInstance, Component } from './component'
 
 export type ComponentPropsOptions<P = Data> =
   | ComponentObjectPropsOptions<P>
@@ -77,6 +77,7 @@ export function initProps(
   if (rawProps) {
     for (let key in rawProps) {
       // key, ref are reserved and never passed down
+      // TODO: remove vnode
       if (isReservedProp(key)) {
         continue
       }

From 461c283f16ad56effd19a1c664e352f036ca21e2 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 10 Dec 2023 13:16:33 +0900
Subject: [PATCH 09/15] feat(vapor-playground): emit once

---
 playground/src/emits.js | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/playground/src/emits.js b/playground/src/emits.js
index 8f4b389cc..bdecb9de1 100644
--- a/playground/src/emits.js
+++ b/playground/src/emits.js
@@ -49,6 +49,17 @@ export default {
       n0
     )
 
+    renderComponent(
+      child,
+      {
+        get count() {
+          return _ctx.count
+        },
+        'onClick:childOnce': _ctx.setCount
+      },
+      n0
+    )
+
     return n0
   }
 }

From 095c3db2f017a3922b3043293fa48131cd44c836 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 10 Dec 2023 13:26:16 +0900
Subject: [PATCH 10/15] chore(runtime-vapor): add todo comment

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

diff --git a/packages/runtime-vapor/src/componentEmits.ts b/packages/runtime-vapor/src/componentEmits.ts
index fcd863e2f..d8afcd825 100644
--- a/packages/runtime-vapor/src/componentEmits.ts
+++ b/packages/runtime-vapor/src/componentEmits.ts
@@ -13,7 +13,7 @@ import { type Component, type ComponentInternalInstance } from './component'
 
 export type ObjectEmitsOptions = Record<
   string,
-  ((...args: any[]) => any) | null
+  ((...args: any[]) => any) | null // TODO: call validation?
 >
 
 export type EmitsOptions = ObjectEmitsOptions | string[]

From 64a50c0a714d93135b0e7880af346b2b3eca7f27 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 10 Dec 2023 13:29:27 +0900
Subject: [PATCH 11/15] feat(vapor-playground): props default

---
 playground/src/props.js | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/playground/src/props.js b/playground/src/props.js
index b80768dcc..6b7c4ab3c 100644
--- a/playground/src/props.js
+++ b/playground/src/props.js
@@ -60,14 +60,28 @@ export default {
       n0
     )
 
+    // test default value
+    renderComponent(
+      child,
+      {
+        get count() {
+          return _ctx.count % 2 === 0 ? _ctx.count : undefined
+        },
+        get inlineDouble() {
+          return _ctx.count % 2 === 0 ? undefined : _ctx.count * 2
+        }
+      },
+      n0
+    )
+
     return n0
   }
 }
 
 const child = {
   props: {
-    count: { type: Number, default: 1 },
-    inlineDouble: { type: Number, default: 2 }
+    count: { type: Number, default: 'opps!' },
+    inlineDouble: { type: Number, default: 'opps!' }
   },
 
   setup(props) {

From d8bd68a2fd49f8633f4cb995d0d9d3041646d12f Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Wed, 13 Dec 2023 23:45:44 +0900
Subject: [PATCH 12/15] fix: define getter of emit handler

---
 playground/src/emits.js | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/playground/src/emits.js b/playground/src/emits.js
index bdecb9de1..bc8e8022b 100644
--- a/playground/src/emits.js
+++ b/playground/src/emits.js
@@ -44,7 +44,9 @@ export default {
         get count() {
           return _ctx.count
         },
-        'onClick:child': _ctx.setCount
+        get ['onClick:child']() {
+          return _ctx.setCount
+        }
       },
       n0
     )
@@ -55,7 +57,9 @@ export default {
         get count() {
           return _ctx.count
         },
-        'onClick:childOnce': _ctx.setCount
+        get ['onClick:childOnce']() {
+          return _ctx.setCount
+        }
       },
       n0
     )

From 7931cf65c5f3b29e9bb731b82f94d0042d8f6833 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Tue, 26 Dec 2023 00:51:47 +0900
Subject: [PATCH 13/15] fix: conflict

---
 playground/src/emits.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/playground/src/emits.js b/playground/src/emits.js
index bc8e8022b..ca001bd13 100644
--- a/playground/src/emits.js
+++ b/playground/src/emits.js
@@ -3,7 +3,7 @@ import {
   on,
   ref,
   template,
-  effect,
+  watchEffect,
   setText,
   render as renderComponent // TODO:
 } from '@vue/vapor'
@@ -34,7 +34,7 @@ export default {
       0: [n1]
     } = children(n0)
 
-    effect(() => {
+    watchEffect(() => {
       setText(n1, void 0, _ctx.count)
     })
 

From 76418ca03e1f33032d71e47bfbb254fdfe9f1115 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Sun, 31 Dec 2023 13:27:58 +0900
Subject: [PATCH 14/15] fix: lint

---
 packages/runtime-vapor/src/componentEmits.ts  |  2 +-
 .../src/componentPublicInstance.ts            |  2 +-
 playground/src/emits.js                       | 26 +++++++++----------
 3 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/packages/runtime-vapor/src/componentEmits.ts b/packages/runtime-vapor/src/componentEmits.ts
index d8afcd825..c1e55b06e 100644
--- a/packages/runtime-vapor/src/componentEmits.ts
+++ b/packages/runtime-vapor/src/componentEmits.ts
@@ -9,7 +9,7 @@ import {
   isFunction,
   toHandlerKey,
 } from '@vue/shared'
-import { type Component, type ComponentInternalInstance } from './component'
+import type { Component, ComponentInternalInstance } from './component'
 
 export type ObjectEmitsOptions = Record<
   string,
diff --git a/packages/runtime-vapor/src/componentPublicInstance.ts b/packages/runtime-vapor/src/componentPublicInstance.ts
index 2adcc3402..5589dbbb2 100644
--- a/packages/runtime-vapor/src/componentPublicInstance.ts
+++ b/packages/runtime-vapor/src/componentPublicInstance.ts
@@ -1,5 +1,5 @@
 import { hasOwn } from '@vue/shared'
-import { type ComponentInternalInstance } from './component'
+import type { ComponentInternalInstance } from './component'
 
 export interface ComponentRenderContext {
   [key: string]: any
diff --git a/playground/src/emits.js b/playground/src/emits.js
index ca001bd13..4d019fc1a 100644
--- a/playground/src/emits.js
+++ b/playground/src/emits.js
@@ -2,10 +2,10 @@ import {
   children,
   on,
   ref,
+  render as renderComponent, // TODO:
+  setText,
   template,
   watchEffect,
-  setText,
-  render as renderComponent // TODO:
 } from '@vue/vapor'
 
 export default {
@@ -21,7 +21,7 @@ export default {
 
     Object.defineProperty(__returned__, '__isScriptSetup', {
       enumerable: false,
-      value: true
+      value: true,
     })
 
     return __returned__
@@ -31,7 +31,7 @@ export default {
     const t0 = template('<p></p>')
     const n0 = t0()
     const {
-      0: [n1]
+      0: [n1],
     } = children(n0)
 
     watchEffect(() => {
@@ -46,9 +46,9 @@ export default {
         },
         get ['onClick:child']() {
           return _ctx.setCount
-        }
+        },
       },
-      n0
+      n0,
     )
 
     renderComponent(
@@ -59,18 +59,18 @@ export default {
         },
         get ['onClick:childOnce']() {
           return _ctx.setCount
-        }
+        },
       },
-      n0
+      n0,
     )
 
     return n0
-  }
+  },
 }
 
 const child = {
   props: {
-    count: { type: Number, default: 1 }
+    count: { type: Number, default: 1 },
   },
 
   setup(props, { emit }) {
@@ -82,7 +82,7 @@ const child = {
 
     Object.defineProperty(__returned__, '__isScriptSetup', {
       enumerable: false,
-      value: true
+      value: true,
     })
 
     return __returned__
@@ -92,9 +92,9 @@ const child = {
     const t0 = template('<button>set count * 2</button>')
     const n0 = t0()
     const {
-      0: [n1]
+      0: [n1],
     } = children(n0)
     on(n1, 'click', _ctx.handleClick)
     return n0
-  }
+  },
 }

From 610450d82404b0e814d2263c81b473ec6d24a7f8 Mon Sep 17 00:00:00 2001
From: Ubugeeei <ubuge1122@gmail.com>
Date: Fri, 5 Jan 2024 02:22:16 +0900
Subject: [PATCH 15/15] chore: update readme (Codes Copied From `runtime-core`)

---
 README.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/README.md b/README.md
index d58a74bb2..c314d6db7 100644
--- a/README.md
+++ b/README.md
@@ -74,7 +74,9 @@ The code provided here is a duplicate from `runtime-core` as Vapor cannot import
 
 - packages/runtime-vapor/src/apiWatch.ts
 - packages/runtime-vapor/src/component.ts
+- packages/runtime-vapor/src/componentEmits.ts
 - packages/runtime-vapor/src/componentProps.ts
+- packages/runtime-vapor/src/componentPublicInstance.ts
 - packages/runtime-vapor/src/enums.ts
 - packages/runtime-vapor/src/errorHandling.ts
 - packages/runtime-vapor/src/scheduler.ts