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