]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test(vapor): api expose (partial)
authorEvan You <evan@vuejs.org>
Tue, 10 Dec 2024 09:00:35 +0000 (17:00 +0800)
committerEvan You <evan@vuejs.org>
Tue, 10 Dec 2024 09:00:35 +0000 (17:00 +0800)
packages/runtime-core/src/component.ts
packages/runtime-core/src/index.ts
packages/runtime-vapor/__tests__/_utils.ts
packages/runtime-vapor/__tests__/apiExpose.spec.ts
packages/runtime-vapor/__tests__/apiSetupHelpers.spec.ts
packages/runtime-vapor/src/apiCreateApp.ts
packages/runtime-vapor/src/component.ts

index db717f84e9adc71ccd0c4f61dcb3dc3efdb4860d..f91af6c73996315955ff34e2b5b9d1736549628c 100644 (file)
@@ -380,16 +380,13 @@ export interface GenericComponentInstance {
 
   // exposed properties via expose()
   exposed: Record<string, any> | null
+  exposeProxy: Record<string, any> | null
 
   /**
    * setup related
    * @internal
    */
   setupState?: Data
-  /**
-   * @internal
-   */
-  setupContext?: any
   /**
    * devtools access to additional info
    * @internal
@@ -603,6 +600,10 @@ export interface ComponentInternalInstance extends GenericComponentInstance {
    * @internal
    */
   setupState: Data
+  /**
+   * @internal
+   */
+  setupContext?: SetupContext | null
 
   // main proxy that serves as the public instance (`this`)
   proxy: ComponentPublicInstance | null
@@ -1131,30 +1132,6 @@ function getSlotsProxy(instance: ComponentInternalInstance): Slots {
 export function createSetupContext(
   instance: ComponentInternalInstance,
 ): SetupContext {
-  const expose: SetupContext['expose'] = exposed => {
-    if (__DEV__) {
-      if (instance.exposed) {
-        warn(`expose() should be called only once per setup().`)
-      }
-      if (exposed != null) {
-        let exposedType: string = typeof exposed
-        if (exposedType === 'object') {
-          if (isArray(exposed)) {
-            exposedType = 'array'
-          } else if (isRef(exposed)) {
-            exposedType = 'ref'
-          }
-        }
-        if (exposedType !== 'object') {
-          warn(
-            `expose() should be passed a plain object, received ${exposedType}.`,
-          )
-        }
-      }
-    }
-    instance.exposed = exposed || {}
-  }
-
   if (__DEV__) {
     // We use getters in dev in case libs like test-utils overwrite instance
     // properties (overwrites should not be done in prod)
@@ -1173,46 +1150,69 @@ export function createSetupContext(
       get emit() {
         return (event: string, ...args: any[]) => instance.emit(event, ...args)
       },
-      expose,
+      expose: exposed => expose(instance, exposed as any),
     })
   } else {
     return {
       attrs: new Proxy(instance.attrs, attrsProxyHandlers),
       slots: instance.slots,
       emit: instance.emit,
-      expose,
+      expose: exposed => expose(instance, exposed as any),
     }
   }
 }
 
-export function getComponentPublicInstance(
+/**
+ * @internal
+ */
+export function expose(
   instance: GenericComponentInstance,
+  exposed: Record<string, any>,
+): void {
+  if (__DEV__) {
+    if (instance.exposed) {
+      warn(`expose() should be called only once per setup().`)
+    }
+    if (exposed != null) {
+      let exposedType: string = typeof exposed
+      if (exposedType === 'object') {
+        if (isArray(exposed)) {
+          exposedType = 'array'
+        } else if (isRef(exposed)) {
+          exposedType = 'ref'
+        }
+      }
+      if (exposedType !== 'object') {
+        warn(
+          `expose() should be passed a plain object, received ${exposedType}.`,
+        )
+      }
+    }
+  }
+  instance.exposed = exposed || {}
+}
+
+export function getComponentPublicInstance(
+  instance: ComponentInternalInstance,
 ): ComponentPublicInstance | ComponentInternalInstance['exposed'] | null {
   if (instance.exposed) {
-    if ('exposeProxy' in instance) {
-      return (
-        instance.exposeProxy ||
-        (instance.exposeProxy = new Proxy(
-          proxyRefs(markRaw(instance.exposed)),
-          {
-            get(target, key: string) {
-              if (key in target) {
-                return target[key]
-              } else if (key in publicPropertiesMap) {
-                return publicPropertiesMap[key](
-                  instance as ComponentInternalInstance,
-                )
-              }
-            },
-            has(target, key: string) {
-              return key in target || key in publicPropertiesMap
-            },
-          },
-        ))
-      )
-    } else {
-      return instance.exposed
-    }
+    return (
+      instance.exposeProxy ||
+      (instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {
+        get(target, key: string) {
+          if (key in target) {
+            return target[key]
+          } else if (key in publicPropertiesMap) {
+            return publicPropertiesMap[key](
+              instance as ComponentInternalInstance,
+            )
+          }
+        },
+        has(target, key: string) {
+          return key in target || key in publicPropertiesMap
+        },
+      }))
+    )
   } else {
     return instance.proxy
   }
index 0a0313e1d79681157ad12dd18b8cc388ffe2bbdb..d6a56d5876e03424c4a1f55205e5dfc05467f3e1 100644 (file)
@@ -499,6 +499,7 @@ export {
   type ComponentInternalOptions,
   type GenericComponentInstance,
   type LifecycleHook,
+  expose,
   nextUid,
   validateComponentName,
 } from './component'
index 6d0f0ee11ac0d2242c650e1c465793d4ec51cffa..626764881dab58b5c736cff61dbf016f15555e7a 100644 (file)
@@ -6,7 +6,7 @@ import type { RawProps } from '../src/componentProps'
 export interface RenderContext {
   component: VaporComponent
   host: HTMLElement
-  instance: VaporComponentInstance | undefined
+  instance: Record<string, any> | undefined
   app: App
   create: (props?: RawProps) => RenderContext
   mount: (container?: string | ParentNode) => RenderContext
index 91f2b76afa090dc9a2892a544cc0b319ac02fe4a..8431d2c07f91029538882210ddc17c38e6151706 100644 (file)
@@ -6,25 +6,22 @@ import { currentInstance } from '@vue/runtime-dom'
 import { defineVaporComponent } from '../src/apiDefineComponent'
 
 const define = makeRender()
-describe.todo('api: expose', () => {
-  test('via setup context', () => {
+
+describe('api: expose', () => {
+  test.todo('via setup context + template ref', () => {
     const Child = defineVaporComponent({
       setup(_, { expose }) {
         expose({
           foo: 1,
           bar: ref(2),
         })
-        return {
-          bar: ref(3),
-          baz: ref(4),
-        }
+        return []
       },
     })
     const childRef = ref()
     define({
       render: () => {
         const n0 = createComponent(Child)
-        setRef(n0, childRef)
         return n0
       },
     }).render()
@@ -35,7 +32,7 @@ describe.todo('api: expose', () => {
     expect(childRef.value.baz).toBeUndefined()
   })
 
-  test('via setup context (expose empty)', () => {
+  test.todo('via setup context + template ref (expose empty)', () => {
     let childInstance: VaporComponentInstance | null = null
     const Child = defineVaporComponent({
       setup(_) {
@@ -62,13 +59,11 @@ describe.todo('api: expose', () => {
         expose({
           foo: 1,
         })
-        return {
-          bar: 2,
-        }
+        return []
       },
     }).render()
-    expect(instance!.exposed!.foo).toBe(1)
-    expect(instance!.exposed!.bar).toBe(undefined)
+    expect(instance!.foo).toBe(1)
+    expect(instance!.bar).toBe(undefined)
   })
 
   test('warning for ref', () => {
index 42c7c3e060ddbbf7264ac4f2817e0565e465de32..d4f7404487d179e2b957ebf6406b7b05045ffdba 100644 (file)
@@ -1,13 +1,7 @@
-import type { SetupContext } from '../src/component'
-import {
-  createComponent,
-  defineComponent,
-  ref,
-  template,
-  useAttrs,
-  useSlots,
-} from '../src'
+import { createComponent, defineVaporComponent, template } from '../src'
+import { ref, useAttrs, useSlots } from '@vue/runtime-dom'
 import { makeRender } from './_utils'
+import type { VaporComponentInstance } from '../src/component'
 
 const define = makeRender<any>()
 
@@ -15,15 +9,17 @@ describe.todo('SFC <script setup> helpers', () => {
   test.todo('should warn runtime usage', () => {})
 
   test('useSlots / useAttrs (no args)', () => {
-    let slots: SetupContext['slots'] | undefined
-    let attrs: SetupContext['attrs'] | undefined
+    let slots: VaporComponentInstance['slots'] | undefined
+    let attrs: VaporComponentInstance['attrs'] | undefined
 
-    const Comp = {
+    const Comp = defineVaporComponent({
       setup() {
+        // @ts-expect-error
         slots = useSlots()
         attrs = useAttrs()
+        return []
       },
-    }
+    })
     const count = ref(0)
     const passedAttrs = { id: () => count.value }
     const passedSlots = {
@@ -45,14 +41,16 @@ describe.todo('SFC <script setup> helpers', () => {
   })
 
   test('useSlots / useAttrs (with args)', () => {
-    let slots: SetupContext['slots'] | undefined
-    let attrs: SetupContext['attrs'] | undefined
-    let ctx: SetupContext | undefined
-    const Comp = defineComponent({
+    let slots: VaporComponentInstance['slots'] | undefined
+    let attrs: VaporComponentInstance['attrs'] | undefined
+    let ctx: VaporComponentInstance | undefined
+    const Comp = defineVaporComponent({
       setup(_, _ctx) {
+        // @ts-expect-error
         slots = useSlots()
         attrs = useAttrs()
-        ctx = _ctx
+        ctx = _ctx as VaporComponentInstance
+        return []
       },
     })
     const { render } = define({ render: () => createComponent(Comp) })
index 7c4db89618a0c1bfb14d64965bc0f4437e2a2d75..74910af36afd33b58916d6b52807f95e7abcfde8 100644 (file)
@@ -2,6 +2,7 @@ import {
   type VaporComponent,
   type VaporComponentInstance,
   createComponent,
+  getExposed,
   mountComponent,
   unmountComponent,
 } from './component'
@@ -41,7 +42,7 @@ export const createVaporApp: CreateAppFunction<ParentNode, VaporComponent> = (
   comp,
   props,
 ) => {
-  if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp, i => i)
+  if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp, getExposed)
   const app = _createApp(comp, props)
 
   if (__DEV__) {
index 18fe296e2d39507cf8df647812fffefca71f6f75..a51d0d6923485274d9add871b9ceb714fbbe7aef 100644 (file)
@@ -14,6 +14,7 @@ import {
   callWithErrorHandling,
   currentInstance,
   endMeasure,
+  expose,
   nextUid,
   popWarningContext,
   pushWarningContext,
@@ -24,7 +25,12 @@ import {
   warn,
 } from '@vue/runtime-dom'
 import { type Block, insert, isBlock, remove } from './block'
-import { pauseTracking, proxyRefs, resetTracking } from '@vue/reactivity'
+import {
+  markRaw,
+  pauseTracking,
+  proxyRefs,
+  resetTracking,
+} from '@vue/reactivity'
 import { EMPTY_OBJ, invokeArrayFns, isFunction, isString } from '@vue/shared'
 import {
   type DynamicPropsSource,
@@ -55,7 +61,7 @@ export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent
 
 export type VaporSetupFn = (
   props: any,
-  ctx: SetupContext,
+  ctx: Pick<VaporComponentInstance, 'slots' | 'attrs' | 'emit' | 'expose'>,
 ) => Block | Record<string, any> | undefined
 
 export type FunctionalVaporComponent = VaporSetupFn &
@@ -156,12 +162,10 @@ export function createComponent(
   pauseTracking()
 
   const setupFn = isFunction(component) ? component : component.setup
-  const setupContext = (instance.setupContext =
-    setupFn && setupFn.length > 1 ? new SetupContext(instance) : null)
   const setupResult = setupFn
     ? callWithErrorHandling(setupFn, instance, ErrorCodes.SETUP_FUNCTION, [
         instance.props,
-        setupContext,
+        instance,
       ]) || EMPTY_OBJ
     : EMPTY_OBJ
 
@@ -252,17 +256,22 @@ export class VaporComponentInstance implements GenericComponentInstance {
 
   block: Block
   scope: EffectScope
-  props: Record<string, any>
-  attrs: Record<string, any>
-  slots: StaticSlots
-  exposed: Record<string, any> | null
 
   rawProps: RawProps
   rawSlots: RawSlots
 
+  props: Record<string, any>
+  attrs: Record<string, any>
+  propsDefaults: Record<string, any> | null
+
+  slots: StaticSlots
+
   emit: EmitFn
   emitted: Record<string, boolean> | null
-  propsDefaults: Record<string, any> | null
+
+  expose: (exposed: Record<string, any>) => void
+  exposed: Record<string, any> | null
+  exposeProxy: Record<string, any> | null
 
   // for useTemplateRef()
   refs: Record<string, any>
@@ -296,8 +305,6 @@ export class VaporComponentInstance implements GenericComponentInstance {
   ec?: LifecycleHook // LifecycleHooks.ERROR_CAPTURED
   sp?: LifecycleHook<() => Promise<unknown>> // LifecycleHooks.SERVER_PREFETCH
 
-  setupContext?: SetupContext | null
-
   // dev only
   setupState?: Record<string, any>
   devtoolsRawSetupState?: any
@@ -336,8 +343,15 @@ export class VaporComponentInstance implements GenericComponentInstance {
     this.scope = new EffectScope(true)
 
     this.emit = emit.bind(null, this)
+    this.expose = expose.bind(null, this)
     this.refs = EMPTY_OBJ
-    this.emitted = this.exposed = this.propsDefaults = this.suspense = null
+    this.emitted =
+      this.exposed =
+      this.exposeProxy =
+      this.propsDefaults =
+      this.suspense =
+        null
+
     this.isMounted =
       this.isUnmounted =
       this.isUpdating =
@@ -383,22 +397,6 @@ export function isVaporComponent(
   return value instanceof VaporComponentInstance
 }
 
-export class SetupContext {
-  attrs: Record<string, any>
-  emit: EmitFn
-  slots: Readonly<StaticSlots>
-  expose: (exposed?: Record<string, any>) => void
-
-  constructor(instance: VaporComponentInstance) {
-    this.attrs = instance.attrs
-    this.emit = instance.emit
-    this.slots = instance.slots
-    this.expose = (exposed = {}) => {
-      instance.exposed = exposed
-    }
-  }
-}
-
 /**
  * Used when a component cannot be resolved at compile time
  * and needs rely on runtime resolution - where it might fallback to a plain
@@ -489,3 +487,14 @@ export function unmountComponent(
     remove(instance.block, parent)
   }
 }
+
+export function getExposed(
+  instance: GenericComponentInstance,
+): Record<string, any> | undefined {
+  if (instance.exposed) {
+    return (
+      instance.exposeProxy ||
+      (instance.exposeProxy = proxyRefs(markRaw(instance.exposed)))
+    )
+  }
+}