Skip to content

Commit

Permalink
feat(runtime-vapor): implement expose (#181)
Browse files Browse the repository at this point in the history
Co-authored-by: Kevin Deng 三咲智子 <[email protected]>
  • Loading branch information
Doctor-wu and sxzz committed Apr 20, 2024
1 parent e67e643 commit d490bf2
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 4 deletions.
49 changes: 49 additions & 0 deletions packages/runtime-vapor/__tests__/componentExpose.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, expect } from 'vitest'
import { makeRender } from './_utils'
import { type Ref, ref } from '@vue/reactivity'

const define = makeRender()

describe('component expose', () => {
test('should work', async () => {
const expxosedObj = { foo: 1 }
const { render } = define({
setup(_, { expose }) {
expose(expxosedObj)
},
})
const { instance } = render()
expect(instance.exposed).toEqual(expxosedObj)
})

test('should warn when called multiple times', async () => {
const { render } = define({
setup(_, { expose }) {
expose()
expose()
},
})
render()
expect(
'expose() should be called only once per setup().',
).toHaveBeenWarned()
})

test('should warn when passed non-object', async () => {
const exposedRef = ref<number[] | Ref>([1, 2, 3])
const { render } = define({
setup(_, { expose }) {
expose(exposedRef.value)
},
})
render()
expect(
'expose() should be passed a plain object, received array.',
).toHaveBeenWarned()
exposedRef.value = ref(1)
render()
expect(
'expose() should be passed a plain object, received ref.',
).toHaveBeenWarned()
})
})
35 changes: 31 additions & 4 deletions packages/runtime-vapor/src/component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EffectScope } from '@vue/reactivity'
import { EMPTY_OBJ, NOOP, isFunction } from '@vue/shared'
import { EffectScope, isRef } from '@vue/reactivity'
import { EMPTY_OBJ, isArray, isFunction } from '@vue/shared'
import type { Block } from './apiRender'
import type { DirectiveBinding } from './directives'
import {
Expand Down Expand Up @@ -45,6 +45,30 @@ export type SetupContext<E = EmitsOptions> = E extends any
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)
Expand All @@ -58,7 +82,7 @@ export function createSetupContext(
get emit() {
return (event: string, ...args: any[]) => instance.emit(event, ...args)
},
expose: NOOP,
expose,
})
} else {
return {
Expand All @@ -67,7 +91,7 @@ export function createSetupContext(
},
emit: instance.emit,
slots: instance.slots,
expose: NOOP,
expose,
}
}
}
Expand Down Expand Up @@ -114,9 +138,12 @@ export interface ComponentInternalInstance {
attrs: Data
slots: InternalSlots
refs: Data
// exposed properties via expose()
exposed?: Record<string, any>

attrsProxy?: Data
slotsProxy?: Slots
exposeProxy?: Record<string, any>

// lifecycle
isMounted: boolean
Expand Down

0 comments on commit d490bf2

Please sign in to comment.