| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- import {
- type App,
- type ComponentInternalInstance,
- type ConcreteComponent,
- MoveType,
- type Plugin,
- type RendererInternals,
- type ShallowRef,
- type Slots,
- type VNode,
- type VaporInteropInterface,
- createVNode,
- currentInstance,
- ensureRenderer,
- onScopeDispose,
- renderSlot,
- shallowRef,
- simpleSetCurrentInstance,
- } from '@vue/runtime-dom'
- import {
- type LooseRawProps,
- type LooseRawSlots,
- type VaporComponent,
- VaporComponentInstance,
- createComponent,
- mountComponent,
- unmountComponent,
- } from './component'
- import { type Block, VaporFragment, insert, remove } from './block'
- import { EMPTY_OBJ, extend, isFunction } from '@vue/shared'
- import { type RawProps, rawPropsProxyHandlers } from './componentProps'
- import type { RawSlots, VaporSlot } from './componentSlots'
- import { renderEffect } from './renderEffect'
- import { createTextNode } from './dom/node'
- import { optimizePropertyLookup } from './dom/prop'
- // mounting vapor components and slots in vdom
- const vaporInteropImpl: Omit<
- VaporInteropInterface,
- 'vdomMount' | 'vdomUnmount' | 'vdomSlot'
- > = {
- mount(vnode, container, anchor, parentComponent) {
- const selfAnchor = (vnode.el = vnode.anchor = createTextNode())
- container.insertBefore(selfAnchor, anchor)
- const prev = currentInstance
- simpleSetCurrentInstance(parentComponent)
- const propsRef = shallowRef(vnode.props)
- const slotsRef = shallowRef(vnode.children)
- // @ts-expect-error
- const instance = (vnode.component = createComponent(
- vnode.type as any as VaporComponent,
- {
- $: [() => propsRef.value],
- } as RawProps,
- {
- _: slotsRef, // pass the slots ref
- } as any as RawSlots,
- ))
- instance.rawPropsRef = propsRef
- instance.rawSlotsRef = slotsRef
- mountComponent(instance, container, selfAnchor)
- vnode.el = instance.block
- simpleSetCurrentInstance(prev)
- return instance
- },
- update(n1, n2, shouldUpdate) {
- n2.component = n1.component
- n2.el = n2.anchor = n1.anchor
- if (shouldUpdate) {
- const instance = n2.component as any as VaporComponentInstance
- instance.rawPropsRef!.value = n2.props
- instance.rawSlotsRef!.value = n2.children
- }
- },
- unmount(vnode, doRemove) {
- const container = doRemove ? vnode.anchor!.parentNode : undefined
- if (vnode.component) {
- unmountComponent(vnode.component as any, container)
- } else if (vnode.vb) {
- remove(vnode.vb, container)
- }
- remove(vnode.anchor as Node, container)
- },
- /**
- * vapor slot in vdom
- */
- slot(n1: VNode, n2: VNode, container, anchor) {
- if (!n1) {
- // mount
- const selfAnchor = (n2.el = n2.anchor = createTextNode())
- insert(selfAnchor, container, anchor)
- const { slot, fallback } = n2.vs!
- const propsRef = (n2.vs!.ref = shallowRef(n2.props))
- const slotBlock = slot(new Proxy(propsRef, vaporSlotPropsProxyHandler))
- // TODO fallback for slot with v-if content
- // fallback is a vnode slot function here, and slotBlock, if a DynamicFragment,
- // expects a Vapor BlockFn as fallback
- fallback
- insert((n2.vb = slotBlock), container, selfAnchor)
- } else {
- // update
- n2.el = n2.anchor = n1.anchor
- n2.vb = n1.vb
- ;(n2.vs!.ref = n1.vs!.ref)!.value = n2.props
- }
- },
- move(vnode, container, anchor) {
- insert(vnode.vb || (vnode.component as any), container, anchor)
- insert(vnode.anchor as any, container, anchor)
- },
- }
- const vaporSlotPropsProxyHandler: ProxyHandler<
- ShallowRef<Record<string, any>>
- > = {
- get(target, key: any) {
- return target.value[key]
- },
- has(target, key: any) {
- return target.value[key]
- },
- ownKeys(target) {
- return Object.keys(target.value)
- },
- }
- const vaporSlotsProxyHandler: ProxyHandler<any> = {
- get(target, key) {
- if (key === '_vapor') {
- return target
- } else {
- return target[key]
- }
- },
- }
- /**
- * Mount vdom component in vapor
- */
- function createVDOMComponent(
- internals: RendererInternals,
- component: ConcreteComponent,
- rawProps?: LooseRawProps | null,
- rawSlots?: LooseRawSlots | null,
- ): VaporFragment {
- const frag = new VaporFragment([])
- const vnode = createVNode(
- component,
- rawProps && new Proxy(rawProps, rawPropsProxyHandlers),
- )
- const wrapper = new VaporComponentInstance(
- { props: component.props },
- rawProps as RawProps,
- rawSlots as RawSlots,
- )
- // overwrite how the vdom instance handles props
- vnode.vi = (instance: ComponentInternalInstance) => {
- instance.props = wrapper.props
- instance.attrs = wrapper.attrs
- instance.slots =
- wrapper.slots === EMPTY_OBJ
- ? EMPTY_OBJ
- : new Proxy(wrapper.slots, vaporSlotsProxyHandler)
- }
- let isMounted = false
- const parentInstance = currentInstance as VaporComponentInstance
- const unmount = (parentNode?: ParentNode) => {
- internals.umt(vnode.component!, null, !!parentNode)
- }
- vnode.scopeId = parentInstance.type.__scopeId!
- frag.insert = (parentNode, anchor) => {
- if (!isMounted) {
- internals.mt(
- vnode,
- parentNode,
- anchor,
- parentInstance as any,
- null,
- undefined,
- false,
- )
- onScopeDispose(unmount, true)
- isMounted = true
- } else {
- // move
- internals.m(
- vnode,
- parentNode,
- anchor,
- MoveType.REORDER,
- parentInstance as any,
- )
- }
- // update the fragment nodes
- frag.nodes = vnode.el as Block
- }
- frag.remove = unmount
- return frag
- }
- /**
- * Mount vdom slot in vapor
- */
- function renderVDOMSlot(
- internals: RendererInternals,
- slotsRef: ShallowRef<Slots>,
- name: string | (() => string),
- props: Record<string, any>,
- parentComponent: VaporComponentInstance,
- fallback?: VaporSlot,
- ): VaporFragment {
- const frag = new VaporFragment([])
- let isMounted = false
- let fallbackNodes: Block | undefined
- let oldVNode: VNode | null = null
- frag.insert = (parentNode, anchor) => {
- if (!isMounted) {
- renderEffect(() => {
- const vnode = renderSlot(
- slotsRef.value,
- isFunction(name) ? name() : name,
- props,
- )
- if ((vnode.children as any[]).length) {
- if (fallbackNodes) {
- remove(fallbackNodes, parentNode)
- fallbackNodes = undefined
- }
- internals.p(
- oldVNode,
- vnode,
- parentNode,
- anchor,
- parentComponent as any,
- )
- oldVNode = vnode
- } else {
- if (fallback && !fallbackNodes) {
- // mount fallback
- if (oldVNode) {
- internals.um(oldVNode, parentComponent as any, null, true)
- }
- insert((fallbackNodes = fallback(props)), parentNode, anchor)
- }
- oldVNode = null
- }
- })
- isMounted = true
- } else {
- // move
- internals.m(
- oldVNode!,
- parentNode,
- anchor,
- MoveType.REORDER,
- parentComponent as any,
- )
- }
- frag.remove = parentNode => {
- if (fallbackNodes) {
- remove(fallbackNodes, parentNode)
- } else if (oldVNode) {
- internals.um(oldVNode, parentComponent as any, null)
- }
- }
- }
- return frag
- }
- export const vaporInteropPlugin: Plugin = app => {
- const internals = ensureRenderer().internals
- app._context.vapor = extend(vaporInteropImpl, {
- vdomMount: createVDOMComponent.bind(null, internals),
- vdomUnmount: internals.umt,
- vdomSlot: renderVDOMSlot.bind(null, internals),
- })
- const mount = app.mount
- app.mount = ((...args) => {
- optimizePropertyLookup()
- return mount(...args)
- }) satisfies App['mount']
- }
|