diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 3c69ebce1..8a8129ffd 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -488,6 +488,10 @@ export interface GenericComponentInstance { * @internal vapor only */ hmrRerender?: () => void + /** + * @internal vapor only + */ + hmrReload?: () => void } /** diff --git a/packages/runtime-core/src/hmr.ts b/packages/runtime-core/src/hmr.ts index 2846c0222..b2c52ab3e 100644 --- a/packages/runtime-core/src/hmr.ts +++ b/packages/runtime-core/src/hmr.ts @@ -96,8 +96,7 @@ function rerender(id: string, newRender?: Function): void { // this flag forces child components with slot content to update isHmrUpdating = true if (instance.vapor) { - // @ts-expect-error TODO - instance.hmrRerender() + instance.hmrRerender!() } else { const i = instance as ComponentInternalInstance i.renderCache = [] @@ -119,7 +118,9 @@ function reload(id: string, newComp: HMRComponent): void { const instances = [...record.instances] if (newComp.vapor) { - // TODO + for (const instance of instances) { + instance.hmrReload!() + } } else { for (const instance of instances as ComponentInternalInstance[]) { const oldComp = normalizeClassComponent(instance.type as HMRComponent) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 69f4a7bb6..74e1f8127 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -20,7 +20,7 @@ import { } from '@vue/runtime-dom' import { type Block, isBlock } from './block' import { pauseTracking, proxyRefs, resetTracking } from '@vue/reactivity' -import { EMPTY_OBJ, isFunction, isString } from '@vue/shared' +import { EMPTY_OBJ, invokeArrayFns, isFunction, isString } from '@vue/shared' import { type RawProps, getPropsProxyHandlers, @@ -40,8 +40,8 @@ import { dynamicSlotsProxyHandlers, getSlot, } from './componentSlots' -import { insert } from './dom/node' -import { hmrRerender } from './hmr' +import { insert, remove } from './dom/node' +import { hmrReload, hmrRerender } from './hmr' export { currentInstance } from '@vue/runtime-dom' @@ -148,6 +148,7 @@ export function createComponent( // HMR registerHMR(instance) instance.hmrRerender = hmrRerender.bind(null, instance) + instance.hmrReload = hmrReload.bind(null, instance) } } else { // in prod result can only be block @@ -202,6 +203,7 @@ export class VaporComponentInstance implements GenericComponentInstance { uid: number type: VaporComponent parent: GenericComponentInstance | null + children: VaporComponentInstance[] // TODO handle vdom children appContext: GenericAppContext block: Block @@ -254,6 +256,7 @@ export class VaporComponentInstance implements GenericComponentInstance { setupState?: Record devtoolsRawSetupState?: any hmrRerender?: () => void + hmrReload?: () => void propsOptions?: NormalizedPropsOptions emitsOptions?: ObjectEmitsOptions | null @@ -266,19 +269,26 @@ export class VaporComponentInstance implements GenericComponentInstance { this.uid = nextUid() this.type = comp this.parent = currentInstance // TODO proper parent source when inside vdom instance - this.appContext = currentInstance - ? currentInstance.appContext - : emptyContext + this.children = [] + + if (currentInstance) { + if (isVaporComponent(currentInstance)) { + currentInstance.children.push(this) + } + this.appContext = currentInstance.appContext + this.provides = currentInstance.provides + this.ids = currentInstance.ids + } else { + this.appContext = emptyContext + this.provides = Object.create(this.appContext.provides) + this.ids = ['', 0, 0] + } this.block = null! // to be set this.scope = new EffectScope(true) this.emit = emit.bind(null, this) - this.provides = currentInstance - ? currentInstance.provides - : Object.create(this.appContext.provides) this.refs = EMPTY_OBJ - this.ids = currentInstance ? currentInstance.ids : ['', 0, 0] this.emitted = this.exposed = this.propsDefaults = this.suspense = null this.isMounted = this.isUnmounted = @@ -381,3 +391,35 @@ export function createComponentWithFallback( return el } + +export function mountComponent( + instance: VaporComponentInstance, + parent: ParentNode, + anchor: Node | null | 0, +): void { + if (!instance.isMounted) { + if (instance.bm) invokeArrayFns(instance.bm) + insert(instance.block, parent, anchor) + // queuePostFlushCb(() => { + if (instance.m) invokeArrayFns(instance.m) + instance.isMounted = true + // }) + } else { + insert(instance.block, parent, anchor) + } +} + +export function unmountComponent( + instance: VaporComponentInstance, + parent: ParentNode, +): void { + if (instance.isMounted && !instance.isUnmounted) { + if (instance.bum) invokeArrayFns(instance.bum) + // TODO invoke unmount recursively for children + remove(instance.block, parent) + // queuePostFlushCb(() => { + if (instance.um) invokeArrayFns(instance.um) + instance.isUnmounted = true + // }) + } +} diff --git a/packages/runtime-vapor/src/dom/node.ts b/packages/runtime-vapor/src/dom/node.ts index 246fdf8e3..e54966d83 100644 --- a/packages/runtime-vapor/src/dom/node.ts +++ b/packages/runtime-vapor/src/dom/node.ts @@ -1,8 +1,12 @@ -import { invokeArrayFns, isArray } from '@vue/shared' +import { isArray } from '@vue/shared' import { renderEffect } from '../renderEffect' import { setText } from './prop' -import { type Block, normalizeBlock } from '../block' -import { isVaporComponent } from '../component' +import type { Block } from '../block' +import { + isVaporComponent, + mountComponent, + unmountComponent, +} from '../component' export function insert( block: Block, @@ -12,14 +16,7 @@ export function insert( if (block instanceof Node) { parent.insertBefore(block, anchor === 0 ? parent.firstChild : anchor) } else if (isVaporComponent(block)) { - if (!block.isMounted) { - if (block.bm) invokeArrayFns(block.bm) - insert(block.block, parent, anchor) - if (block.m) invokeArrayFns(block.m) - block.isMounted = true - } else { - insert(block.block, parent, anchor) - } + mountComponent(block, parent, anchor) } else if (isArray(block)) { for (let i = 0; i < block.length; i++) { insert(block[i], parent, anchor) @@ -35,15 +32,23 @@ export function prepend(parent: ParentNode, ...blocks: Block[]): void { for (const b of blocks) insert(b, parent, 0) } -// TODO optimize +// TODO invoke unmount recursive export function remove(block: Block, parent: ParentNode): void { - const nodes = normalizeBlock(block) - for (let i = 0; i < nodes.length; i++) { - parent.removeChild(nodes[i]) + if (block instanceof Node) { + parent.removeChild(block) + } else if (isVaporComponent(block)) { + unmountComponent(block, parent) + } else if (isArray(block)) { + for (let i = 0; i < block.length; i++) { + remove(block[i], parent) + } + } else { + // fragment + remove(block.nodes, parent) + if (block.anchor) remove(block.anchor, parent) } } -// TODO optimize export function createTextNode(values?: any[] | (() => any[])): Text { // eslint-disable-next-line no-restricted-globals const node = document.createTextNode('') diff --git a/packages/runtime-vapor/src/hmr.ts b/packages/runtime-vapor/src/hmr.ts index 51b9fa224..1cdfda3fc 100644 --- a/packages/runtime-vapor/src/hmr.ts +++ b/packages/runtime-vapor/src/hmr.ts @@ -19,3 +19,8 @@ export function hmrRerender(instance: VaporComponentInstance): void { popWarningContext() insert(instance.block, parent, anchor) } + +export function hmrReload(instance: VaporComponentInstance): void { + // in parent block, find the corresponding block of this instance + // create new instance, replace +}