Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(runtime-vapor): implement expose #181

Merged
merged 8 commits into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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