]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(hmr): root instance reload
authorEvan You <yyx990803@gmail.com>
Sun, 22 Dec 2019 17:25:04 +0000 (12:25 -0500)
committerEvan You <yyx990803@gmail.com>
Sun, 22 Dec 2019 17:25:04 +0000 (12:25 -0500)
packages/runtime-core/src/apiCreateApp.ts
packages/runtime-core/src/hmr.ts
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/vnode.ts

index defe9af8cd33b5b25cf955764616d9b6b51e9cff..57d77332a3f91d124fe986c09a06933ea00e7db3 100644 (file)
@@ -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
@@ -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
@@ -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
index 84433067e01607b626e1bdedd11e3c9eb4cd934f..cdbf91bea7791d549302488dbdc2638327f711a9 100644 (file)
@@ -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
     }
@@ -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(
index c651247ccd2a9a2be051d6c209381ced76c65eb8..f81e803ca1c99962fa2dce40eb74ceed9d1060b0 100644 (file)
@@ -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)
index 05cbcf54103634ded6b4309cd1ec9d9b0682a34c..ac4e6e236e3001e7f5dab7a94e86f50d5b79556c 100644 (file)
@@ -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'