]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor: reuse props logic from core
authorEvan You <evan@vuejs.org>
Tue, 3 Dec 2024 08:48:28 +0000 (16:48 +0800)
committerEvan You <evan@vuejs.org>
Tue, 3 Dec 2024 08:48:28 +0000 (16:48 +0800)
12 files changed:
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentProps.ts
packages/runtime-core/src/index.ts
packages/runtime-vapor/src/_new/apiCreateApp.ts [new file with mode: 0644]
packages/runtime-vapor/src/_new/component.ts [new file with mode: 0644]
packages/runtime-vapor/src/_new/componentProps.ts [new file with mode: 0644]
packages/runtime-vapor/src/_new/index.ts [new file with mode: 0644]
packages/runtime-vapor/src/_new/renderEffect.ts [new file with mode: 0644]
packages/runtime-vapor/src/apiCreateComponentSimple.ts [deleted file]
packages/runtime-vapor/src/block.ts
packages/runtime-vapor/src/dom/element.ts
packages/runtime-vapor/src/index.ts

index a6e97aaa1093876ac84c6adcd69f7424a838b98b..c877ebd4b900136052b1b902a6e2a8a226dc2f6e 100644 (file)
@@ -470,7 +470,7 @@ export interface ComponentInternalInstance {
    * avoid unnecessary watcher trigger
    * @internal
    */
-  propsDefaults: Data
+  propsDefaults: Data | null
   /**
    * setup related
    * @internal
@@ -647,7 +647,7 @@ export function createComponentInstance(
     emitted: null,
 
     // props default value
-    propsDefaults: EMPTY_OBJ,
+    propsDefaults: null,
 
     // inheritAttrs
     inheritAttrs: type.inheritAttrs,
index bd5e4d9d07f05889f2b036c1d6fa971c8266dff4..85619244a92620b7451449f3d2cb4a662f04e705 100644 (file)
@@ -282,11 +282,10 @@ export function updateProps(
             const camelizedKey = camelize(key)
             props[camelizedKey] = resolvePropValue(
               options,
-              rawCurrentProps,
               camelizedKey,
               value,
               instance,
-              false /* isAbsent */,
+              baseResolveDefault,
             )
           }
         } else {
@@ -331,10 +330,10 @@ export function updateProps(
           ) {
             props[key] = resolvePropValue(
               options,
-              rawCurrentProps,
               key,
               undefined,
               instance,
+              baseResolveDefault,
               true /* isAbsent */,
             )
           }
@@ -428,16 +427,15 @@ function setFullProps(
   }
 
   if (needCastKeys) {
-    const rawCurrentProps = toRaw(props)
     const castValues = rawCastValues || EMPTY_OBJ
     for (let i = 0; i < needCastKeys.length; i++) {
       const key = needCastKeys[i]
       props[key] = resolvePropValue(
         options!,
-        rawCurrentProps,
         key,
         castValues[key],
         instance,
+        baseResolveDefault,
         !hasOwn(castValues, key),
       )
     }
@@ -446,14 +444,32 @@ function setFullProps(
   return hasAttrsChanged
 }
 
-function resolvePropValue(
+/**
+ * A type that allows both vdom and vapor instances
+ */
+type CommonInstance = Pick<
+  ComponentInternalInstance,
+  'props' | 'propsDefaults' | 'ce'
+>
+
+/**
+ * @internal for runtime-vapor
+ */
+export function resolvePropValue<T extends CommonInstance>(
   options: NormalizedProps,
-  props: Data,
   key: string,
   value: unknown,
-  instance: ComponentInternalInstance,
-  isAbsent: boolean,
-) {
+  instance: T,
+  /**
+   * Allow runtime-specific default resolution logic
+   */
+  resolveDefault: (
+    factory: (props: Data) => unknown,
+    instance: T,
+    key: string,
+  ) => unknown,
+  isAbsent = false,
+): unknown {
   const opt = options[key]
   if (opt != null) {
     const hasDefault = hasOwn(opt, 'default')
@@ -465,19 +481,16 @@ function resolvePropValue(
         !opt.skipFactory &&
         isFunction(defaultValue)
       ) {
-        const { propsDefaults } = instance
-        if (key in propsDefaults) {
-          value = propsDefaults[key]
+        const cachedDefaults =
+          instance.propsDefaults || (instance.propsDefaults = {})
+        if (hasOwn(cachedDefaults, key)) {
+          value = cachedDefaults[key]
         } else {
-          const reset = setCurrentInstance(instance)
-          value = propsDefaults[key] = defaultValue.call(
-            __COMPAT__ &&
-              isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
-              ? createPropsDefaultThis(instance, props, key)
-              : null,
-            props,
+          value = cachedDefaults[key] = resolveDefault(
+            defaultValue,
+            instance,
+            key,
           )
-          reset()
         }
       } else {
         value = defaultValue
@@ -502,6 +515,27 @@ function resolvePropValue(
   return value
 }
 
+/**
+ * runtime-dom-specific default resolving logic
+ */
+function baseResolveDefault(
+  factory: (props: Data) => unknown,
+  instance: ComponentInternalInstance,
+  key: string,
+) {
+  let value
+  const reset = setCurrentInstance(instance)
+  const props = toRaw(instance.props)
+  value = factory.call(
+    __COMPAT__ && isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
+      ? createPropsDefaultThis(instance, props, key)
+      : null,
+    props,
+  )
+  reset()
+  return value
+}
+
 const mixinPropsCache = new WeakMap<ConcreteComponent, NormalizedPropsOptions>()
 
 export function normalizePropsOptions(
@@ -550,6 +584,22 @@ export function normalizePropsOptions(
     return EMPTY_ARR as any
   }
 
+  baseNormalizePropsOptions(raw, normalized, needCastKeys)
+  const res: NormalizedPropsOptions = [normalized, needCastKeys]
+  if (isObject(comp)) {
+    cache.set(comp, res)
+  }
+  return res
+}
+
+/**
+ * @internal for runtime-vapor only
+ */
+export function baseNormalizePropsOptions(
+  raw: ComponentPropsOptions | undefined,
+  normalized: NonNullable<NormalizedPropsOptions[0]>,
+  needCastKeys: NonNullable<NormalizedPropsOptions[1]>,
+): void {
   if (isArray(raw)) {
     for (let i = 0; i < raw.length; i++) {
       if (__DEV__ && !isString(raw[i])) {
@@ -604,12 +654,6 @@ export function normalizePropsOptions(
       }
     }
   }
-
-  const res: NormalizedPropsOptions = [normalized, needCastKeys]
-  if (isObject(comp)) {
-    cache.set(comp, res)
-  }
-  return res
 }
 
 function validatePropName(key: string) {
index a24714b8ca164dcb5c439412783826f584d2064d..a0e13a2bc43152a95db86c7ffdec27b9759b1f49 100644 (file)
@@ -320,6 +320,7 @@ export type {
   ExtractPropTypes,
   ExtractPublicPropTypes,
   ExtractDefaultPropTypes,
+  NormalizedPropsOptions,
 } from './componentProps'
 export type {
   Directive,
@@ -480,3 +481,10 @@ export const compatUtils = (
 export const DeprecationTypes = (
   __COMPAT__ ? _DeprecationTypes : null
 ) as typeof _DeprecationTypes
+
+// VAPOR -----------------------------------------------------------------------
+
+// **IMPORTANT** These APIs are exposed solely for @vue/runtime-vapor and may
+// change without notice between versions. User code should never rely on them.
+
+export { baseNormalizePropsOptions, resolvePropValue } from './componentProps'
diff --git a/packages/runtime-vapor/src/_new/apiCreateApp.ts b/packages/runtime-vapor/src/_new/apiCreateApp.ts
new file mode 100644 (file)
index 0000000..f6a7c00
--- /dev/null
@@ -0,0 +1,18 @@
+import { normalizeContainer } from '../apiRender'
+import { insert } from '../dom/element'
+import { type Component, createComponent } from './component'
+
+export function createVaporApp(comp: Component): any {
+  return {
+    mount(container: string | ParentNode) {
+      container = normalizeContainer(container)
+      // clear content before mounting
+      if (container.nodeType === 1 /* Node.ELEMENT_NODE */) {
+        container.textContent = ''
+      }
+      const instance = createComponent(comp)
+      insert(instance.block, container)
+      return instance
+    },
+  }
+}
diff --git a/packages/runtime-vapor/src/_new/component.ts b/packages/runtime-vapor/src/_new/component.ts
new file mode 100644 (file)
index 0000000..670b731
--- /dev/null
@@ -0,0 +1,184 @@
+import {
+  type ComponentPropsOptions,
+  EffectScope,
+  type EmitsOptions,
+  type NormalizedPropsOptions,
+} from '@vue/runtime-core'
+import type { Block } from '../block'
+import type { Data } from '@vue/runtime-shared'
+import { pauseTracking, resetTracking } from '@vue/reactivity'
+import { isFunction } from '@vue/shared'
+import {
+  type RawProps,
+  getDynamicPropsHandlers,
+  initStaticProps,
+} from './componentProps'
+import { setDynamicProp } from '../dom/prop'
+import { renderEffect } from './renderEffect'
+
+export type Component = FunctionalComponent | ObjectComponent
+
+export type SetupFn = (
+  props: any,
+  ctx: SetupContext,
+) => Block | Data | undefined
+
+export type FunctionalComponent = SetupFn &
+  Omit<ObjectComponent, 'setup'> & {
+    displayName?: string
+  } & SharedInternalOptions
+
+export interface ObjectComponent
+  extends ComponentInternalOptions,
+    SharedInternalOptions {
+  setup?: SetupFn
+  inheritAttrs?: boolean
+  props?: ComponentPropsOptions
+  emits?: EmitsOptions
+  render?(ctx: any): Block
+
+  name?: string
+  vapor?: boolean
+}
+
+interface SharedInternalOptions {
+  __propsOptions?: NormalizedPropsOptions
+  __propsHandlers?: [ProxyHandler<any>, ProxyHandler<any>]
+}
+
+// Note: can't mark this whole interface internal because some public interfaces
+// extend it.
+interface ComponentInternalOptions {
+  /**
+   * @internal
+   */
+  __scopeId?: string
+  /**
+   * @internal
+   */
+  __cssModules?: Data
+  /**
+   * @internal
+   */
+  __hmrId?: string
+  /**
+   * Compat build only, for bailing out of certain compatibility behavior
+   */
+  __isBuiltIn?: boolean
+  /**
+   * This one should be exposed so that devtools can make use of it
+   */
+  __file?: string
+  /**
+   * name inferred from filename
+   */
+  __name?: string
+}
+
+export function createComponent(
+  component: Component,
+  rawProps?: RawProps,
+  isSingleRoot?: boolean,
+): ComponentInstance {
+  // check if we are the single root of the parent
+  // if yes, inject parent attrs as dynamic props source
+  if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) {
+    if (rawProps) {
+      ;(rawProps.$ || (rawProps.$ = [])).push(currentInstance.attrs)
+    } else {
+      rawProps = { $: [currentInstance.attrs] }
+    }
+  }
+
+  const instance = new ComponentInstance(component, rawProps)
+
+  pauseTracking()
+  let prevInstance = currentInstance
+  currentInstance = instance
+  instance.scope.on()
+
+  const setupFn = isFunction(component) ? component : component.setup
+  const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null
+  instance.block = setupFn!(
+    instance.props,
+    // @ts-expect-error
+    setupContext,
+  ) as Block // TODO handle return object
+
+  // single root, inherit attrs
+  if (
+    instance.hasFallthrough &&
+    component.inheritAttrs !== false &&
+    instance.block instanceof Element &&
+    Object.keys(instance.attrs).length
+  ) {
+    renderEffect(() => {
+      for (const key in instance.attrs) {
+        setDynamicProp(instance.block as Element, key, instance.attrs[key])
+      }
+    })
+  }
+
+  instance.scope.off()
+  currentInstance = prevInstance
+  resetTracking()
+  return instance
+}
+
+let uid = 0
+export let currentInstance: ComponentInstance | null = null
+
+export class ComponentInstance {
+  type: Component
+  uid: number = uid++
+  scope: EffectScope = new EffectScope(true)
+  props: Record<string, any>
+  propsDefaults: Record<string, any> | null
+  attrs: Record<string, any>
+  block: Block
+  exposed?: Record<string, any>
+  hasFallthrough: boolean
+
+  constructor(comp: Component, rawProps?: RawProps) {
+    this.type = comp
+    this.block = null! // to be set
+
+    // init props
+    this.propsDefaults = null
+    this.hasFallthrough = false
+    if (comp.props && rawProps && rawProps.$) {
+      // has dynamic props, use proxy
+      const handlers = getDynamicPropsHandlers(comp, this)
+      this.props = new Proxy(rawProps, handlers[0])
+      this.attrs = new Proxy(rawProps, handlers[1])
+      this.hasFallthrough = true
+    } else {
+      this.props = {}
+      this.attrs = {}
+      this.hasFallthrough = initStaticProps(comp, rawProps, this)
+    }
+
+    // TODO validate props
+    // TODO init slots
+  }
+}
+
+export function isVaporComponent(value: unknown): value is ComponentInstance {
+  return value instanceof ComponentInstance
+}
+
+export class SetupContext<E = EmitsOptions> {
+  attrs: Record<string, any>
+  // emit: EmitFn<E>
+  // slots: Readonly<StaticSlots>
+  expose: (exposed?: Record<string, any>) => void
+
+  constructor(instance: ComponentInstance) {
+    this.attrs = instance.attrs
+    // this.emit = instance.emit as EmitFn<E>
+    // this.slots = instance.slots
+    this.expose = (exposed = {}) => {
+      instance.exposed = exposed
+    }
+  }
+}
diff --git a/packages/runtime-vapor/src/_new/componentProps.ts b/packages/runtime-vapor/src/_new/componentProps.ts
new file mode 100644 (file)
index 0000000..03f6082
--- /dev/null
@@ -0,0 +1,214 @@
+import { EMPTY_ARR, NO, camelize, hasOwn, isFunction } from '@vue/shared'
+import type { Component, ComponentInstance } from './component'
+import {
+  type NormalizedPropsOptions,
+  baseNormalizePropsOptions,
+  resolvePropValue,
+} from '@vue/runtime-core'
+
+export interface RawProps {
+  [key: string]: PropSource
+  $?: DynamicPropsSource[]
+}
+
+type PropSource<T = any> = T | (() => T)
+
+type DynamicPropsSource = PropSource<Record<string, any>>
+
+export function initStaticProps(
+  comp: Component,
+  rawProps: RawProps | undefined,
+  instance: ComponentInstance,
+): boolean {
+  let hasAttrs = false
+  const { props, attrs } = instance
+  const [propsOptions, needCastKeys] = normalizePropsOptions(comp)
+  // TODO emits filtering
+  for (const key in rawProps) {
+    const normalizedKey = camelize(key)
+    const needCast = needCastKeys && needCastKeys.includes(normalizedKey)
+    const source = rawProps[key]
+    if (propsOptions && normalizedKey in propsOptions) {
+      if (isFunction(source)) {
+        Object.defineProperty(props, normalizedKey, {
+          enumerable: true,
+          get: needCast
+            ? () =>
+                resolvePropValue(
+                  propsOptions,
+                  normalizedKey,
+                  source(),
+                  instance,
+                  resolveDefault,
+                )
+            : source,
+        })
+      } else {
+        props[normalizedKey] = needCast
+          ? resolvePropValue(
+              propsOptions,
+              normalizedKey,
+              source,
+              instance,
+              resolveDefault,
+            )
+          : source
+      }
+    } else {
+      if (isFunction(source)) {
+        Object.defineProperty(attrs, key, {
+          enumerable: true,
+          get: source,
+        })
+      } else {
+        attrs[normalizedKey] = source
+      }
+      hasAttrs = true
+    }
+  }
+  for (const key in propsOptions) {
+    if (!(key in props)) {
+      props[key] = resolvePropValue(
+        propsOptions,
+        key,
+        undefined,
+        instance,
+        resolveDefault,
+        true,
+      )
+    }
+  }
+  return hasAttrs
+}
+
+function resolveDefault(
+  factory: (props: Record<string, any>) => unknown,
+  instance: ComponentInstance,
+) {
+  return factory.call(null, instance.props)
+}
+
+// TODO optimization: maybe convert functions into computeds
+function resolveSource(source: PropSource): Record<string, any> {
+  return isFunction(source) ? source() : source
+}
+
+export function getDynamicPropsHandlers(
+  comp: Component,
+  instance: ComponentInstance,
+): [ProxyHandler<RawProps>, ProxyHandler<RawProps>] {
+  if (comp.__propsHandlers) {
+    return comp.__propsHandlers
+  }
+  let normalizedKeys: string[] | undefined
+  const propsOptions = normalizePropsOptions(comp)[0]!
+  const isProp = (key: string | symbol) => hasOwn(propsOptions, key)
+
+  const getProp = (target: RawProps, key: string | symbol, asProp: boolean) => {
+    if (key !== '$' && (asProp ? isProp(key) : !isProp(key))) {
+      const castProp = (value: any, isAbsent = false) =>
+        asProp
+          ? resolvePropValue(
+              propsOptions,
+              key as string,
+              value,
+              instance,
+              resolveDefault,
+              isAbsent,
+            )
+          : value
+
+      if (key in target) {
+        // TODO default value, casting, etc.
+        return castProp(resolveSource(target[key as string]))
+      }
+      if (target.$) {
+        let i = target.$.length
+        let source
+        while (i--) {
+          source = resolveSource(target.$[i])
+          if (hasOwn(source, key)) {
+            return castProp(source[key])
+          }
+        }
+      }
+      return castProp(undefined, true)
+    }
+  }
+
+  const propsHandlers = {
+    get: (target, key) => getProp(target, key, true),
+    has: (_, key) => isProp(key),
+    getOwnPropertyDescriptor(target, key) {
+      if (isProp(key)) {
+        return {
+          configurable: true,
+          enumerable: true,
+          get: () => getProp(target, key, true),
+        }
+      }
+    },
+    ownKeys: () =>
+      normalizedKeys || (normalizedKeys = Object.keys(propsOptions)),
+    set: NO,
+    deleteProperty: NO,
+  } satisfies ProxyHandler<RawProps>
+
+  const hasAttr = (target: RawProps, key: string | symbol) => {
+    if (key === '$' || isProp(key)) return false
+    if (hasOwn(target, key)) return true
+    if (target.$) {
+      let i = target.$.length
+      while (i--) {
+        if (hasOwn(resolveSource(target.$[i]), key)) {
+          return true
+        }
+      }
+    }
+    return false
+  }
+
+  const attrsHandlers = {
+    get: (target, key) => getProp(target, key, false),
+    has: hasAttr,
+    getOwnPropertyDescriptor(target, key) {
+      if (hasAttr(target, key)) {
+        return {
+          configurable: true,
+          enumerable: true,
+          get: () => getProp(target, key, false),
+        }
+      }
+    },
+    ownKeys(target) {
+      const staticKeys = Object.keys(target).filter(
+        key => key !== '$' && !isProp(key),
+      )
+      if (target.$) {
+        let i = target.$.length
+        while (i--) {
+          staticKeys.push(...Object.keys(resolveSource(target.$[i])))
+        }
+      }
+      return staticKeys
+    },
+    set: NO,
+    deleteProperty: NO,
+  } satisfies ProxyHandler<RawProps>
+
+  return (comp.__propsHandlers = [propsHandlers, attrsHandlers])
+}
+
+function normalizePropsOptions(comp: Component): NormalizedPropsOptions {
+  const cached = comp.__propsOptions
+  if (cached) return cached
+
+  const raw = comp.props
+  if (!raw) return EMPTY_ARR as []
+
+  const normalized: NormalizedPropsOptions[0] = {}
+  const needCastKeys: NormalizedPropsOptions[1] = []
+  baseNormalizePropsOptions(raw, normalized, needCastKeys)
+
+  return (comp.__propsOptions = [normalized, needCastKeys])
+}
diff --git a/packages/runtime-vapor/src/_new/index.ts b/packages/runtime-vapor/src/_new/index.ts
new file mode 100644 (file)
index 0000000..ed98436
--- /dev/null
@@ -0,0 +1,3 @@
+export { createComponent as createComponentSimple } from './component'
+export { renderEffect as renderEffectSimple } from './renderEffect'
+export { createVaporApp as createVaporAppSimple } from './apiCreateApp'
diff --git a/packages/runtime-vapor/src/_new/renderEffect.ts b/packages/runtime-vapor/src/_new/renderEffect.ts
new file mode 100644 (file)
index 0000000..e8fd31e
--- /dev/null
@@ -0,0 +1,22 @@
+import { ReactiveEffect } from '@vue/reactivity'
+import {
+  type SchedulerJob,
+  queueJob,
+} from '../../../runtime-core/src/scheduler'
+import { currentInstance } from './component'
+
+export function renderEffect(fn: () => void): void {
+  const updateFn = () => {
+    fn()
+  }
+  const effect = new ReactiveEffect(updateFn)
+  const job: SchedulerJob = effect.runIfDirty.bind(effect)
+  job.i = currentInstance as any
+  job.id = currentInstance!.uid
+  effect.scheduler = () => queueJob(job)
+  effect.run()
+
+  // TODO lifecycle
+  // TODO recurse handling
+  // TODO measure
+}
diff --git a/packages/runtime-vapor/src/apiCreateComponentSimple.ts b/packages/runtime-vapor/src/apiCreateComponentSimple.ts
deleted file mode 100644 (file)
index 98af50c..0000000
+++ /dev/null
@@ -1,322 +0,0 @@
-import {
-  EffectScope,
-  ReactiveEffect,
-  pauseTracking,
-  resetTracking,
-} from '@vue/reactivity'
-import type { Component } from './component'
-import { NO, camelize, hasOwn, isFunction } from '@vue/shared'
-import { type SchedulerJob, queueJob } from '../../runtime-core/src/scheduler'
-import { insert } from './dom/element'
-import { normalizeContainer } from './apiRender'
-import { normalizePropsOptions, resolvePropValue } from './componentProps'
-import type { Block } from './block'
-import { EmitFn, type EmitsOptions } from './componentEmits'
-import { StaticSlots } from './componentSlots'
-import { setDynamicProp } from './dom/prop'
-
-interface RawProps {
-  [key: string]: PropSource
-  $?: DynamicPropsSource[]
-}
-
-type PropSource<T = any> = T | (() => T)
-
-type DynamicPropsSource = PropSource<Record<string, any>>
-
-export function createComponentSimple(
-  component: Component,
-  rawProps?: RawProps,
-  isSingleRoot?: boolean,
-): ComponentInstance {
-  // check if we are the single root of the parent
-  // if yes, inject parent attrs as dynamic props source
-  if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) {
-    if (rawProps) {
-      ;(rawProps.$ || (rawProps.$ = [])).push(currentInstance.attrs)
-    } else {
-      rawProps = { $: [currentInstance.attrs] }
-    }
-  }
-
-  const instance = new ComponentInstance(component, rawProps)
-
-  pauseTracking()
-  let prevInstance = currentInstance
-  currentInstance = instance
-  instance.scope.on()
-
-  const setupFn = isFunction(component) ? component : component.setup
-  const setupContext = setupFn!.length > 1 ? new SetupContext(instance) : null
-  instance.block = setupFn!(
-    instance.props,
-    // @ts-expect-error
-    setupContext,
-  ) as Block // TODO handle return object
-
-  // single root, inherit attrs
-  if (
-    instance.hasFallthrough &&
-    component.inheritAttrs !== false &&
-    instance.block instanceof Element &&
-    Object.keys(instance.attrs).length
-  ) {
-    renderEffectSimple(() => {
-      for (const key in instance.attrs) {
-        setDynamicProp(instance.block as Element, key, instance.attrs[key])
-      }
-    })
-  }
-
-  instance.scope.off()
-  currentInstance = prevInstance
-  resetTracking()
-  return instance
-}
-
-class SetupContext<E = EmitsOptions> {
-  attrs: Record<string, any>
-  // emit: EmitFn<E>
-  // slots: Readonly<StaticSlots>
-  expose: (exposed?: Record<string, any>) => void
-
-  constructor(instance: ComponentInstance) {
-    this.attrs = instance.attrs
-    // this.emit = instance.emit as EmitFn<E>
-    // this.slots = instance.slots
-    this.expose = (exposed = {}) => {
-      instance.exposed = exposed
-    }
-  }
-}
-
-let uid = 0
-let currentInstance: ComponentInstance | null = null
-
-export class ComponentInstance {
-  type: Component
-  uid: number = uid++
-  scope: EffectScope = new EffectScope(true)
-  props: Record<string, any>
-  attrs: Record<string, any>
-  block: Block
-  exposed?: Record<string, any>
-  hasFallthrough: boolean
-
-  constructor(comp: Component, rawProps?: RawProps) {
-    this.type = comp
-    this.block = null! // to be set
-
-    // init props
-    this.hasFallthrough = false
-    if (comp.props && rawProps && rawProps.$) {
-      // has dynamic props, use proxy
-      const handlers = getDynamicPropsHandlers(comp, this)
-      this.props = new Proxy(rawProps, handlers[0])
-      this.attrs = new Proxy(rawProps, handlers[1])
-      this.hasFallthrough = true
-    } else {
-      this.hasFallthrough = initStaticProps(
-        comp,
-        rawProps,
-        (this.props = {}),
-        (this.attrs = {}),
-      )
-    }
-
-    // TODO validate props
-    // TODO init slots
-  }
-}
-
-export function isVaporComponent(value: unknown): value is ComponentInstance {
-  return value instanceof ComponentInstance
-}
-
-function initStaticProps(
-  comp: Component,
-  rawProps: RawProps | undefined,
-  props: any,
-  attrs: any,
-): boolean {
-  let hasAttrs = false
-  const [propsOptions, needCastKeys] = normalizePropsOptions(comp)
-  for (const key in rawProps) {
-    const normalizedKey = camelize(key)
-    const needCast = needCastKeys && needCastKeys.includes(normalizedKey)
-    const source = rawProps[key]
-    if (propsOptions && normalizedKey in propsOptions) {
-      if (isFunction(source)) {
-        Object.defineProperty(props, normalizedKey, {
-          enumerable: true,
-          get: needCast
-            ? () =>
-                resolvePropValue(propsOptions, props, normalizedKey, source())
-            : source,
-        })
-      } else {
-        props[normalizedKey] = needCast
-          ? resolvePropValue(propsOptions, props, normalizedKey, source)
-          : source
-      }
-    } else {
-      if (isFunction(source)) {
-        Object.defineProperty(attrs, key, {
-          enumerable: true,
-          get: source,
-        })
-      } else {
-        attrs[normalizedKey] = source
-      }
-      hasAttrs = true
-    }
-  }
-  for (const key in propsOptions) {
-    if (!(key in props)) {
-      props[key] = resolvePropValue(propsOptions, props, key, undefined, true)
-    }
-  }
-  return hasAttrs
-}
-
-// TODO optimization: maybe convert functions into computeds
-function resolveSource(source: PropSource): Record<string, any> {
-  return isFunction(source) ? source() : source
-}
-
-function getDynamicPropsHandlers(
-  comp: Component,
-  instance: ComponentInstance,
-): [ProxyHandler<RawProps>, ProxyHandler<RawProps>] {
-  if (comp.__propsHandlers) {
-    return comp.__propsHandlers
-  }
-  let normalizedKeys: string[] | undefined
-  const propsOptions = normalizePropsOptions(comp)[0]!
-  const isProp = (key: string | symbol) => hasOwn(propsOptions, key)
-
-  const getProp = (target: RawProps, key: string | symbol, asProp: boolean) => {
-    if (key !== '$' && (asProp ? isProp(key) : !isProp(key))) {
-      const castProp = (value: any, isAbsent?: boolean) =>
-        asProp
-          ? resolvePropValue(
-              propsOptions,
-              instance.props,
-              key as string,
-              value,
-              isAbsent,
-            )
-          : value
-
-      if (key in target) {
-        // TODO default value, casting, etc.
-        return castProp(resolveSource(target[key as string]))
-      }
-      if (target.$) {
-        let i = target.$.length
-        let source
-        while (i--) {
-          source = resolveSource(target.$[i])
-          if (hasOwn(source, key)) {
-            return castProp(source[key])
-          }
-        }
-      }
-      return castProp(undefined, true)
-    }
-  }
-
-  const propsHandlers = {
-    get: (target, key) => getProp(target, key, true),
-    has: (_, key) => isProp(key),
-    getOwnPropertyDescriptor(target, key) {
-      if (isProp(key)) {
-        return {
-          configurable: true,
-          enumerable: true,
-          get: () => getProp(target, key, true),
-        }
-      }
-    },
-    ownKeys: () =>
-      normalizedKeys || (normalizedKeys = Object.keys(propsOptions)),
-    set: NO,
-    deleteProperty: NO,
-  } satisfies ProxyHandler<RawProps>
-
-  const hasAttr = (target: RawProps, key: string | symbol) => {
-    if (key === '$' || isProp(key)) return false
-    if (hasOwn(target, key)) return true
-    if (target.$) {
-      let i = target.$.length
-      while (i--) {
-        if (hasOwn(resolveSource(target.$[i]), key)) {
-          return true
-        }
-      }
-    }
-    return false
-  }
-
-  const attrsHandlers = {
-    get: (target, key) => getProp(target, key, false),
-    has: hasAttr,
-    getOwnPropertyDescriptor(target, key) {
-      if (hasAttr(target, key)) {
-        return {
-          configurable: true,
-          enumerable: true,
-          get: () => getProp(target, key, false),
-        }
-      }
-    },
-    ownKeys(target) {
-      const staticKeys = Object.keys(target).filter(
-        key => key !== '$' && !isProp(key),
-      )
-      if (target.$) {
-        let i = target.$.length
-        while (i--) {
-          staticKeys.push(...Object.keys(resolveSource(target.$[i])))
-        }
-      }
-      return staticKeys
-    },
-    set: NO,
-    deleteProperty: NO,
-  } satisfies ProxyHandler<RawProps>
-
-  return (comp.__propsHandlers = [propsHandlers, attrsHandlers])
-}
-
-export function renderEffectSimple(fn: () => void): void {
-  const updateFn = () => {
-    fn()
-  }
-  const effect = new ReactiveEffect(updateFn)
-  const job: SchedulerJob = effect.runIfDirty.bind(effect)
-  job.i = currentInstance as any
-  job.id = currentInstance!.uid
-  effect.scheduler = () => queueJob(job)
-  effect.run()
-
-  // TODO lifecycle
-  // TODO recurse handling
-  // TODO measure
-}
-
-// vapor app can be a subset of main app APIs
-// TODO refactor core createApp for reuse
-export function createVaporAppSimple(comp: Component): any {
-  return {
-    mount(container: string | ParentNode) {
-      container = normalizeContainer(container)
-      // clear content before mounting
-      if (container.nodeType === 1 /* Node.ELEMENT_NODE */) {
-        container.textContent = ''
-      }
-      const rootBlock = createComponentSimple(comp)
-      insert(rootBlock, container)
-    },
-  }
-}
index de5a301d1903f9787be7eb31703ca796106de0b8..0a3ce4c4e121585e8479ec7848e08f3a51335896 100644 (file)
@@ -1,8 +1,5 @@
 import { isArray } from '@vue/shared'
-import {
-  type ComponentInstance,
-  isVaporComponent,
-} from './apiCreateComponentSimple'
+import { type ComponentInstance, isVaporComponent } from './_new/component'
 
 export const fragmentKey: unique symbol = Symbol(__DEV__ ? `fragmentKey` : ``)
 
index 6f0fc27f89f56426753950b281e830db6c3f2dc7..51843844dc18ba187e73f77678cceb9347189e36 100644 (file)
@@ -2,7 +2,7 @@ import { isArray } from '@vue/shared'
 import { renderEffect } from '../renderEffect'
 import { setText } from './prop'
 import { type Block, normalizeBlock } from '../block'
-import { isVaporComponent } from '../apiCreateComponentSimple'
+import { isVaporComponent } from '../_new/component'
 
 // export function insert(
 //   block: Block,
index eb6306232e1ccc24d25978607d12b4f45467501f..b0277f0d80f5ae36932515496a45b19b54a5947e 100644 (file)
@@ -155,11 +155,6 @@ export {
 export { createBranch, createIf } from './apiCreateIf'
 export { createFor, createForSlots } from './apiCreateFor'
 export { createComponent } from './apiCreateComponent'
-export {
-  createComponentSimple,
-  renderEffectSimple,
-  createVaporAppSimple,
-} from './apiCreateComponentSimple'
 export { createSelector } from './apiCreateSelector'
 export { setInheritAttrs } from './componentAttrs'
 
@@ -195,3 +190,5 @@ export const devtools = (
 export const setDevtoolsHook = (
   __DEV__ || __ESM_BUNDLER__ ? _setDevtoolsHook : NOOP
 ) as typeof _setDevtoolsHook
+
+export * from './_new'