Skip to content

Commit

Permalink
fix(hmr): force update cached slots during HMR
Browse files Browse the repository at this point in the history
close #7155
close #7158
  • Loading branch information
yyx990803 committed Apr 20, 2023
1 parent 9b5a34b commit 94fa67a
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 27 deletions.
31 changes: 31 additions & 0 deletions packages/runtime-core/__tests__/hmr.spec.ts
Expand Up @@ -537,4 +537,35 @@ describe('hot module replacement', () => {
render(h(Foo), root)
expect(serializeInner(root)).toBe('bar')
})

// #7155 - force HMR on slots content update
test('force update slot content change', () => {
const root = nodeOps.createElement('div')
const parentId = 'test-force-computed-parent'
const childId = 'test-force-computed-child'

const Child: ComponentOptions = {
__hmrId: childId,
computed: {
slotContent() {
return this.$slots.default?.()
}
},
render: compileToFunction(`<component :is="() => slotContent" />`)
}
createRecord(childId, Child)

const Parent: ComponentOptions = {
__hmrId: parentId,
components: { Child },
render: compileToFunction(`<Child>1</Child>`)
}
createRecord(parentId, Parent)

render(h(Parent), root)
expect(serializeInner(root)).toBe(`1`)

rerender(parentId, compileToFunction(`<Child>2</Child>`))
expect(serializeInner(root)).toBe(`2`)
})
})
86 changes: 59 additions & 27 deletions packages/runtime-core/src/component.ts
Expand Up @@ -349,6 +349,10 @@ export interface ComponentInternalInstance {
slots: InternalSlots
refs: Data
emit: EmitFn

attrsProxy: Data | null
slotsProxy: Slots | null

/**
* used for keeping track of .once event handlers on components
* @internal
Expand Down Expand Up @@ -536,6 +540,9 @@ export function createComponentInstance(
setupState: EMPTY_OBJ,
setupContext: null,

attrsProxy: null,
slotsProxy: null,

// suspense related
suspense,
suspenseId: suspense ? suspense.pendingId : 0,
Expand Down Expand Up @@ -923,31 +930,57 @@ export function finishComponentSetup(
}
}

function createAttrsProxy(instance: ComponentInternalInstance): Data {
return new Proxy(
instance.attrs,
__DEV__
? {
get(target, key: string) {
markAttrsAccessed()
track(instance, TrackOpTypes.GET, '$attrs')
return target[key]
},
set() {
warn(`setupContext.attrs is readonly.`)
return false
},
deleteProperty() {
warn(`setupContext.attrs is readonly.`)
return false
function getAttrsProxy(instance: ComponentInternalInstance): Data {
return (
instance.attrsProxy ||
(instance.attrsProxy = new Proxy(
instance.attrs,
__DEV__
? {
get(target, key: string) {
markAttrsAccessed()
track(instance, TrackOpTypes.GET, '$attrs')
return target[key]
},
set() {
warn(`setupContext.attrs is readonly.`)
return false
},
deleteProperty() {
warn(`setupContext.attrs is readonly.`)
return false
}
}
}
: {
get(target, key: string) {
track(instance, TrackOpTypes.GET, '$attrs')
return target[key]
: {
get(target, key: string) {
track(instance, TrackOpTypes.GET, '$attrs')
return target[key]
}
}
}
))
)
}

/**
* Dev-only
*/
function getSlotsProxy(instance: ComponentInternalInstance): Slots {
return (
instance.slotsProxy ||
(instance.slotsProxy = new Proxy(instance.slots, {
get(target, key: string) {
track(instance, TrackOpTypes.GET, '$slots')
return target[key]
},
set() {
warn(`setupContext.slots is readonly.`)
return false
},
deleteProperty() {
warn(`setupContext.slots is readonly.`)
return false
}
}))
)
}

Expand Down Expand Up @@ -978,16 +1011,15 @@ export function createSetupContext(
instance.exposed = exposed || {}
}

let attrs: Data
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({
get attrs() {
return attrs || (attrs = createAttrsProxy(instance))
return getAttrsProxy(instance)
},
get slots() {
return shallowReadonly(instance.slots)
return getSlotsProxy(instance)
},
get emit() {
return (event: string, ...args: any[]) => instance.emit(event, ...args)
Expand All @@ -997,7 +1029,7 @@ export function createSetupContext(
} else {
return {
get attrs() {
return attrs || (attrs = createAttrsProxy(instance))
return getAttrsProxy(instance)
},
slots: instance.slots,
emit: instance.emit,
Expand Down
2 changes: 2 additions & 0 deletions packages/runtime-core/src/componentPublicInstance.ts
Expand Up @@ -356,6 +356,8 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
if (key === '$attrs') {
track(instance, TrackOpTypes.GET, key)
__DEV__ && markAttrsAccessed()
} else if (__DEV__ && key === '$slots') {
track(instance, TrackOpTypes.GET, key)
}
return publicGetter(instance)
} else if (
Expand Down
3 changes: 3 additions & 0 deletions packages/runtime-core/src/componentSlots.ts
Expand Up @@ -23,6 +23,8 @@ import { ContextualRenderFn, withCtx } from './componentRenderContext'
import { isHmrUpdating } from './hmr'
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
import { toRaw } from '@vue/reactivity'
import { trigger } from '@vue/reactivity'
import { TriggerOpTypes } from '@vue/reactivity'

export type Slot<T extends any = any> = (
...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
Expand Down Expand Up @@ -196,6 +198,7 @@ export const updateSlots = (
// Parent was HMR updated so slot content may have changed.
// force update slots and mark instance for hmr as well
extend(slots, children as Slots)
trigger(instance, TriggerOpTypes.SET, '$slots')
} else if (optimized && type === SlotFlags.STABLE) {
// compiled AND stable.
// no need to update, and skip stale slots removal.
Expand Down

0 comments on commit 94fa67a

Please sign in to comment.