export function shouldUpdateComponent(
prevVNode: VNode,
nextVNode: VNode,
+ parentComponent: ComponentInternalInstance | null,
optimized?: boolean
): boolean {
const { props: prevProps, children: prevChildren } = prevVNode
const { props: nextProps, children: nextChildren, patchFlag } = nextVNode
+
+ // Parent component's render function was hot-updated. Since this may have
+ // caused the child component's slots content to have changed, we need to
+ // force the child to update as well.
+ if (
+ __BUNDLER__ &&
+ __DEV__ &&
+ (prevChildren || nextChildren) &&
+ parentComponent &&
+ parentComponent.renderUpdated
+ ) {
+ return true
+ }
+
if (patchFlag > 0) {
if (patchFlag & PatchFlags.DYNAMIC_SLOTS) {
// slot content that references values that might have changed,
ComponentOptions,
RenderFunction
} from './component'
+import { queueJob, queuePostFlushCb } from './scheduler'
// Expose the HMR runtime on the global object
// This makes it entirely tree-shakable without polluting the exports and makes
: {}
globalObject.__VUE_HMR_RUNTIME__ = {
- isRecorded: tryWrap(isRecorded),
createRecord: tryWrap(createRecord),
rerender: tryWrap(rerender),
reload: tryWrap(reload)
map.get(instance.type.__hmrId!)!.instances.delete(instance)
}
-function isRecorded(id: string): boolean {
- return map.has(id)
-}
-
-function createRecord(id: string, comp: ComponentOptions) {
+function createRecord(id: string, comp: ComponentOptions): boolean {
if (map.has(id)) {
- return
+ return false
}
map.set(id, {
comp,
instances: new Set()
})
+ return true
}
function rerender(id: string, newRender: RenderFunction) {
map.get(id)!.instances.forEach(instance => {
instance.render = newRender
instance.renderCache = []
+ // this flag forces child components with slot content to update
+ instance.renderUpdated = true
instance.update()
- // TODO force scoped slots passed to children to have DYNAMIC_SLOTS flag
+ instance.renderUpdated = false
})
}
function reload(id: string, newComp: ComponentOptions) {
- // TODO
- console.log('reload', id)
+ const record = map.get(id)!
+ // 1. Update existing comp definition to match new one
+ const comp = record.comp
+ Object.assign(comp, newComp)
+ for (const key in comp) {
+ if (!(key in newComp)) {
+ delete (comp as any)[key]
+ }
+ }
+ // 2. Mark component dirty. This forces the renderer to replace the component
+ // on patch.
+ comp.__hmrUpdated = true
+ record.instances.forEach(instance => {
+ if (instance.parent) {
+ // 3. 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(instance.parent.update)
+ } else if (typeof window !== 'undefined') {
+ window.location.reload()
+ } else {
+ console.warn(
+ '[HMR] Root or manually mounted instance modified. Full reload required.'
+ )
+ }
+ })
+ // 4. Make sure to unmark the component after the reload.
+ queuePostFlushCb(() => {
+ comp.__hmrUpdated = false
+ })
}
-function tryWrap(fn: (id: string, arg: any) => void): Function {
+function tryWrap(fn: (id: string, arg: any) => any): Function {
return (id: string, arg: any) => {
try {
- fn(id, arg)
+ return fn(id, arg)
} catch (e) {
console.error(e)
console.warn(
- `Something went wrong during Vue component hot-reload. ` +
+ `[HMR] Something went wrong during Vue component hot-reload. ` +
`Full reload required.`
)
}