// 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
* @internal
*/
setupState: Data
+ /**
+ * @internal
+ */
+ setupContext?: SetupContext | null
// main proxy that serves as the public instance (`this`)
proxy: ComponentPublicInstance | null
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)
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
}
type ComponentInternalOptions,
type GenericComponentInstance,
type LifecycleHook,
+ expose,
nextUid,
validateComponentName,
} from './component'
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
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()
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(_) {
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', () => {
-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>()
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 = {
})
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) })
type VaporComponent,
type VaporComponentInstance,
createComponent,
+ getExposed,
mountComponent,
unmountComponent,
} from './component'
comp,
props,
) => {
- if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp, i => i)
+ if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp, getExposed)
const app = _createApp(comp, props)
if (__DEV__) {
callWithErrorHandling,
currentInstance,
endMeasure,
+ expose,
nextUid,
popWarningContext,
pushWarningContext,
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,
export type VaporSetupFn = (
props: any,
- ctx: SetupContext,
+ ctx: Pick<VaporComponentInstance, 'slots' | 'attrs' | 'emit' | 'expose'>,
) => Block | Record<string, any> | undefined
export type FunctionalVaporComponent = VaporSetupFn &
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
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>
ec?: LifecycleHook // LifecycleHooks.ERROR_CAPTURED
sp?: LifecycleHook<() => Promise<unknown>> // LifecycleHooks.SERVER_PREFETCH
- setupContext?: SetupContext | null
-
// dev only
setupState?: Record<string, any>
devtoolsRawSetupState?: any
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 =
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
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)))
+ )
+ }
+}