Skip to content

Commit

Permalink
feat(hmr): root instance reload
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Dec 22, 2019
1 parent c3e1c81 commit eda495e
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 10 deletions.
11 changes: 10 additions & 1 deletion packages/runtime-core/src/apiCreateApp.ts
Expand Up @@ -6,7 +6,7 @@ import { RootRenderFunction } from './renderer'
import { InjectionKey } from './apiInject'
import { isFunction, NO, isObject } from '@vue/shared'
import { warn } from './warning'
import { createVNode } from './vnode'
import { createVNode, cloneVNode } from './vnode'

export interface App<HostElement = any> {
config: AppConfig
Expand Down Expand Up @@ -47,6 +47,7 @@ export interface AppContext {
components: Record<string, Component>
directives: Record<string, Directive>
provides: Record<string | symbol, any>
reload?: () => void // HMR only
}

type PluginInstallFunction = (app: App) => any
Expand Down Expand Up @@ -175,6 +176,14 @@ export function createAppAPI<HostNode, HostElement>(
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
vnode.appContext = context

// HMR root reload
if (__BUNDLER__ && __DEV__) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer)
}
}

render(vnode, rootContainer)
isMounted = true
return vnode.component!.proxy
Expand Down
12 changes: 10 additions & 2 deletions packages/runtime-core/src/hmr.ts
Expand Up @@ -60,7 +60,9 @@ function createRecord(id: string, comp: ComponentOptions): boolean {
}

function rerender(id: string, newRender?: RenderFunction) {
map.get(id)!.instances.forEach(instance => {
// Array.from creates a snapshot which avoids the set being mutated during
// updates
Array.from(map.get(id)!.instances).forEach(instance => {
if (newRender) {
instance.render = newRender
}
Expand All @@ -85,13 +87,19 @@ function reload(id: string, newComp: ComponentOptions) {
// 2. Mark component dirty. This forces the renderer to replace the component
// on patch.
comp.__hmrUpdated = true
record.instances.forEach(instance => {
// Array.from creates a snapshot which avoids the set being mutated during
// updates
Array.from(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 (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(
Expand Down
12 changes: 6 additions & 6 deletions packages/runtime-core/src/renderer.ts
Expand Up @@ -1727,12 +1727,12 @@ export function createRenderer<
}
}

const render: RootRenderFunction<
HostNode,
HostElement & {
_vnode: HostVNode | null
}
> = (vnode, container) => {
type HostRootElement = HostElement & { _vnode: HostVNode | null }

const render: RootRenderFunction<HostNode, HostElement> = (
vnode,
container: HostRootElement
) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/vnode.ts
Expand Up @@ -15,7 +15,7 @@ import {
import { RawSlots } from './componentSlots'
import { ShapeFlags } from './shapeFlags'
import { isReactive, Ref } from '@vue/reactivity'
import { AppContext } from './apiApp'
import { AppContext } from './apiCreateApp'
import { SuspenseBoundary } from './components/Suspense'
import { DirectiveBinding } from './directives'
import { SuspenseImpl } from './components/Suspense'
Expand Down

0 comments on commit eda495e

Please sign in to comment.