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: Added expose to component instances #75

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion packages/runtime-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export {

// For getting a hold of the internal instance in setup() - useful for advanced
// plugins
export { getCurrentInstance } from './component'
export { getCurrentInstance, getExposeProxy } from './component'

// For raw render function users
export { h } from './h'
Expand Down
61 changes: 59 additions & 2 deletions packages/runtime-vapor/src/component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EffectScope, Ref, ref } from '@vue/reactivity'
import { EffectScope, isRef, Ref, ref } from '@vue/reactivity'

import { EMPTY_OBJ } from '@vue/shared'
import { EMPTY_OBJ, isArray } from '@vue/shared'
import { Block } from './render'
import { type DirectiveBinding } from './directive'
import {
Expand All @@ -11,6 +11,7 @@ import {

import type { Data } from '@vue/shared'
import { VaporLifecycleHooks } from './apiLifecycle'
import { warn } from '@vue/runtime-core'

export type Component = FunctionalComponent | ObjectComponent

Expand Down Expand Up @@ -42,6 +43,10 @@ export interface ComponentInternalInstance {
props: Data
setupState: Data

// exposed properties via expose()
exposed: Record<string, any> | null
exposeProxy: Record<string, any> | null

/** directives */
dirs: Map<Node, DirectiveBinding[]>

Expand Down Expand Up @@ -123,6 +128,54 @@ export const unsetCurrentInstance = () => {
currentInstance = null
}

// TODO: Maybe it can be reused
// TODO: https://github.com/vuejs/core/blob/04d2c05054c26b02fbc1d84839b0ed5cd36455b6/packages/runtime-core/src/component.ts#L186C1-L186C1
export type SetupContext = {
expose: (exposed?: Record<string, any>) => void
}

// TODO: Maybe it can be reused
// TODO: https://github.com/vuejs/core/blob/04d2c05054c26b02fbc1d84839b0ed5cd36455b6/packages/runtime-core/src/component.ts#L983
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)
return Object.freeze({
expose,
})
} else {
return {
expose,
}
}
}

let uid = 0
export const createComponentInstance = (
component: ObjectComponent | FunctionalComponent,
Expand All @@ -145,6 +198,10 @@ export const createComponentInstance = (
props: EMPTY_OBJ,
setupState: EMPTY_OBJ,

// exposed properties via expose()
exposed: null,
exposeProxy: null,

dirs: new Map(),

// lifecycle
Expand Down
7 changes: 6 additions & 1 deletion packages/runtime-vapor/src/directive.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isFunction } from '@vue/shared'
import { currentInstance, type ComponentInternalInstance } from './component'
import { effect } from './scheduler'
import { getExposeProxy } from '@vue/runtime-core'
sxzz marked this conversation as resolved.
Show resolved Hide resolved

export type DirectiveModifiers<M extends string = string> = Record<M, boolean>

Expand Down Expand Up @@ -69,6 +70,10 @@ export function withDirectives<T extends Node>(
}

const instance = currentInstance
// TODO: Wait for the component instance type to be completed before deleting `any`
const instanceProxy =
(getExposeProxy(currentInstance as any) as ComponentInternalInstance) ||
currentInstance.proxy
if (!instance.dirs.has(node)) instance.dirs.set(node, [])
const bindings = instance.dirs.get(node)!

Expand All @@ -84,7 +89,7 @@ export function withDirectives<T extends Node>(

const binding: DirectiveBinding = {
dir,
instance,
instance: instanceProxy,
source,
value: null, // set later
oldValue: null,
Expand Down
3 changes: 2 additions & 1 deletion packages/runtime-vapor/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
type Component,
type ComponentInternalInstance,
createComponentInstance,
createSetupContext,
setCurrentInstance,
unsetCurrentInstance,
} from './component'
Expand Down Expand Up @@ -42,7 +43,7 @@ export function mountComponent(
setCurrentInstance(instance)
const block = instance.scope.run(() => {
const { component, props } = instance
const ctx = { expose: () => {} }
const ctx = createSetupContext(instance)

const setupFn =
typeof component === 'function' ? component : component.setup
Expand Down