wip: prepare hmr for vapor

This commit is contained in:
Evan You 2024-12-08 16:32:29 +08:00
parent 6f5493c677
commit e8067f1cd9
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
2 changed files with 70 additions and 59 deletions

View File

@ -348,6 +348,10 @@ export interface GenericComponentInstance {
* @internal
*/
scope: EffectScope
/**
* render function will have different types between vdom and vapor
*/
render?: Function | null
/**
* SSR render function
* (they are the same between vdom and vapor components.)

View File

@ -4,7 +4,7 @@ import {
type ComponentInternalInstance,
type ComponentOptions,
type ConcreteComponent,
type InternalRenderFunction,
type GenericComponentInstance,
isClassComponent,
} from './component'
import { queueJob, queuePostFlushCb } from './scheduler'
@ -16,8 +16,8 @@ export let isHmrUpdating = false
export const hmrDirtyComponents: Map<
ConcreteComponent,
Set<ComponentInternalInstance>
> = new Map<ConcreteComponent, Set<ComponentInternalInstance>>()
Set<GenericComponentInstance>
> = new Map<ConcreteComponent, Set<GenericComponentInstance>>()
export interface HMRRuntime {
createRecord: typeof createRecord
@ -45,11 +45,11 @@ const map: Map<
// to apply hot updates to the component even when there are no actively
// rendered instance.
initialDef: ComponentOptions
instances: Set<ComponentInternalInstance>
instances: Set<GenericComponentInstance>
}
> = new Map()
export function registerHMR(instance: ComponentInternalInstance): void {
export function registerHMR(instance: GenericComponentInstance): void {
const id = instance.type.__hmrId!
let record = map.get(id)
if (!record) {
@ -59,7 +59,7 @@ export function registerHMR(instance: ComponentInternalInstance): void {
record.instances.add(instance)
}
export function unregisterHMR(instance: ComponentInternalInstance): void {
export function unregisterHMR(instance: GenericComponentInstance): void {
map.get(instance.type.__hmrId!)!.instances.delete(instance)
}
@ -90,13 +90,18 @@ function rerender(id: string, newRender?: Function): void {
// Create a snapshot which avoids the set being mutated during updates
;[...record.instances].forEach(instance => {
if (newRender) {
instance.render = newRender as InternalRenderFunction
instance.render = newRender
normalizeClassComponent(instance.type as HMRComponent).render = newRender
}
instance.renderCache = []
// this flag forces child components with slot content to update
isHmrUpdating = true
instance.update()
if (instance.vapor) {
// TODO
} else {
const i = instance as ComponentInternalInstance
i.renderCache = []
i.update()
}
isHmrUpdating = false
})
}
@ -112,62 +117,64 @@ function reload(id: string, newComp: HMRComponent): void {
// create a snapshot which avoids the set being mutated during updates
const instances = [...record.instances]
for (let i = 0; i < instances.length; i++) {
const instance = instances[i]
const oldComp = normalizeClassComponent(instance.type as HMRComponent)
if (newComp.vapor) {
// TODO
} else {
for (const instance of instances as ComponentInternalInstance[]) {
const oldComp = normalizeClassComponent(instance.type as HMRComponent)
let dirtyInstances = hmrDirtyComponents.get(oldComp)
if (!dirtyInstances) {
// 1. Update existing comp definition to match new one
if (oldComp !== record.initialDef) {
updateComponentDef(oldComp, newComp)
let dirtyInstances = hmrDirtyComponents.get(oldComp)
if (!dirtyInstances) {
// 1. Update existing comp definition to match new one
if (oldComp !== record.initialDef) {
updateComponentDef(oldComp, newComp)
}
// 2. mark definition dirty. This forces the renderer to replace the
// component on patch.
hmrDirtyComponents.set(oldComp, (dirtyInstances = new Set()))
}
// 2. mark definition dirty. This forces the renderer to replace the
// component on patch.
hmrDirtyComponents.set(oldComp, (dirtyInstances = new Set()))
}
dirtyInstances.add(instance)
// 3. invalidate options resolution cache
instance.appContext.propsCache.delete(instance.type as any)
instance.appContext.emitsCache.delete(instance.type as any)
instance.appContext.optionsCache.delete(instance.type as any)
// 4. actually update
if (instance.ceReload) {
// custom element
dirtyInstances.add(instance)
instance.ceReload((newComp as any).styles)
dirtyInstances.delete(instance)
} else if (instance.parent) {
// 4. Force the parent instance to re-render. This will cause all updated
// components to be unmounted and re-mounted. Queue the update so that we
// don't end up forcing the same parent to re-render multiple times.
queueJob(() => {
isHmrUpdating = true
instance.parent!.update()
isHmrUpdating = false
// #6930, #11248 avoid infinite recursion
dirtyInstances.delete(instance)
})
} else if (instance.appContext.reload) {
// root instance mounted via createApp() has a reload method
instance.appContext.reload()
} else if (typeof window !== 'undefined') {
// root instance inside tree created via raw render(). Force reload.
window.location.reload()
} else {
console.warn(
'[HMR] Root or manually mounted instance modified. Full reload required.',
)
}
// update custom element child style
if (instance.root.ce && instance !== instance.root) {
instance.root.ce._removeChildStyle(oldComp)
// 3. invalidate options resolution cache
instance.appContext.propsCache.delete(instance.type as any)
instance.appContext.emitsCache.delete(instance.type as any)
instance.appContext.optionsCache.delete(instance.type as any)
// 4. actually update
if (instance.ceReload) {
// custom element
dirtyInstances.add(instance)
instance.ceReload((newComp as any).styles)
dirtyInstances.delete(instance)
} else if (instance.parent) {
// 4. Force the parent instance to re-render. This will cause all updated
// components to be unmounted and re-mounted. Queue the update so that we
// don't end up forcing the same parent to re-render multiple times.
queueJob(() => {
isHmrUpdating = true
instance.parent!.update()
isHmrUpdating = false
// #6930, #11248 avoid infinite recursion
dirtyInstances.delete(instance)
})
} else if (instance.appContext.reload) {
// root instance mounted via createApp() has a reload method
instance.appContext.reload()
} else if (typeof window !== 'undefined') {
// root instance inside tree created via raw render(). Force reload.
window.location.reload()
} else {
console.warn(
'[HMR] Root or manually mounted instance modified. Full reload required.',
)
}
// update custom element child style
if (instance.root.ce && instance !== instance.root) {
instance.root.ce._removeChildStyle(oldComp)
}
}
}
// 5. make sure to cleanup dirty hmr components after update
queuePostFlushCb(() => {
hmrDirtyComponents.clear()