]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test: test hot module replacement
authorEvan You <yyx990803@gmail.com>
Mon, 16 Dec 2019 22:57:34 +0000 (17:57 -0500)
committerEvan You <yyx990803@gmail.com>
Tue, 17 Dec 2019 17:31:38 +0000 (12:31 -0500)
packages/runtime-core/__tests__/hmr.spec.ts [new file with mode: 0644]
packages/runtime-core/src/hmr.ts
packages/runtime-core/src/index.ts
packages/runtime-core/src/renderer.ts

diff --git a/packages/runtime-core/__tests__/hmr.spec.ts b/packages/runtime-core/__tests__/hmr.spec.ts
new file mode 100644 (file)
index 0000000..4da8d0d
--- /dev/null
@@ -0,0 +1,150 @@
+import { HMRRuntime } from '../src/hmr'
+import '../src/hmr'
+import { ComponentOptions, RenderFunction } from '../src/component'
+import {
+  render,
+  nodeOps,
+  h,
+  serializeInner,
+  triggerEvent,
+  TestElement,
+  nextTick
+} from '@vue/runtime-test'
+import * as runtimeTest from '@vue/runtime-test'
+import { baseCompile } from '@vue/compiler-core'
+
+declare var __VUE_HMR_RUNTIME__: HMRRuntime
+const { createRecord, rerender, reload } = __VUE_HMR_RUNTIME__
+
+function compileToFunction(template: string) {
+  const { code } = baseCompile(template)
+  const render = new Function('Vue', code)(runtimeTest) as RenderFunction
+  render.isRuntimeCompiled = true
+  return render
+}
+
+describe('hot module replacement', () => {
+  test('inject global runtime', () => {
+    expect(createRecord).toBeDefined()
+    expect(rerender).toBeDefined()
+    expect(reload).toBeDefined()
+  })
+
+  test('createRecord', () => {
+    expect(createRecord('test1', {})).toBe(true)
+    // if id has already been created, should return false
+    expect(createRecord('test1', {})).toBe(false)
+  })
+
+  test('rerender', async () => {
+    const root = nodeOps.createElement('div')
+    const parentId = 'test2-parent'
+    const childId = 'test2-child'
+
+    const Child: ComponentOptions = {
+      __hmrId: childId,
+      render: compileToFunction(`<slot/>`)
+    }
+    createRecord(childId, Child)
+
+    const Parent: ComponentOptions = {
+      __hmrId: parentId,
+      data() {
+        return { count: 0 }
+      },
+      components: { Child },
+      render: compileToFunction(
+        `<div @click="count++">{{ count }}<Child>{{ count }}</Child></div>`
+      )
+    }
+    createRecord(parentId, Parent)
+
+    render(h(Parent), root)
+    expect(serializeInner(root)).toBe(`<div>0<!---->0<!----></div>`)
+
+    // Perform some state change. This change should be preserved after the
+    // re-render!
+    triggerEvent(root.children[0] as TestElement, 'click')
+    await nextTick()
+    expect(serializeInner(root)).toBe(`<div>1<!---->1<!----></div>`)
+
+    // Update text while preserving state
+    rerender(
+      parentId,
+      compileToFunction(
+        `<div @click="count++">{{ count }}!<Child>{{ count }}</Child></div>`
+      )
+    )
+    expect(serializeInner(root)).toBe(`<div>1!<!---->1<!----></div>`)
+
+    // Should force child update on slot content change
+    rerender(
+      parentId,
+      compileToFunction(
+        `<div @click="count++">{{ count }}!<Child>{{ count }}!</Child></div>`
+      )
+    )
+    expect(serializeInner(root)).toBe(`<div>1!<!---->1!<!----></div>`)
+
+    // Should force update element children despite block optimization
+    rerender(
+      parentId,
+      compileToFunction(
+        `<div @click="count++">{{ count }}<span>{{ count }}</span>
+        <Child>{{ count }}!</Child>
+      </div>`
+      )
+    )
+    expect(serializeInner(root)).toBe(
+      `<div>1<span>1</span><!---->1!<!----></div>`
+    )
+
+    // Should force update child slot elements
+    rerender(
+      parentId,
+      compileToFunction(
+        `<div @click="count++">
+        <Child><span>{{ count }}</span></Child>
+      </div>`
+      )
+    )
+    expect(serializeInner(root)).toBe(`<div><!----><span>1</span><!----></div>`)
+  })
+
+  test('reload', async () => {
+    const root = nodeOps.createElement('div')
+    const childId = 'test3-child'
+    const unmoutSpy = jest.fn()
+    const mountSpy = jest.fn()
+
+    const Child: ComponentOptions = {
+      __hmrId: childId,
+      data() {
+        return { count: 0 }
+      },
+      unmounted: unmoutSpy,
+      render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
+    }
+    createRecord(childId, Child)
+
+    const Parent: ComponentOptions = {
+      render: () => h(Child)
+    }
+
+    render(h(Parent), root)
+    expect(serializeInner(root)).toBe(`<div>0</div>`)
+
+    reload(childId, {
+      __hmrId: childId,
+      data() {
+        return { count: 1 }
+      },
+      mounted: mountSpy,
+      render: compileToFunction(`<div @click="count++">{{ count }}</div>`)
+    })
+    await nextTick()
+    expect(serializeInner(root)).toBe(`<div>1</div>`)
+    expect(unmoutSpy).toHaveBeenCalledTimes(1)
+    expect(mountSpy).toHaveBeenCalledTimes(1)
+  })
+})
index 7c10defbab09330a5e12e35ce9253b57354da142..940f01d028c844962eab2dc384dfebacd893013a 100644 (file)
@@ -5,6 +5,12 @@ import {
 } from './component'
 import { queueJob, queuePostFlushCb } from './scheduler'
 
+export interface HMRRuntime {
+  createRecord: typeof createRecord
+  rerender: typeof rerender
+  reload: typeof reload
+}
+
 // Expose the HMR runtime on the global object
 // This makes it entirely tree-shakable without polluting the exports and makes
 // it easier to be used in toolings like vue-loader
@@ -24,7 +30,7 @@ if (__BUNDLER__ && __DEV__) {
     createRecord: tryWrap(createRecord),
     rerender: tryWrap(rerender),
     reload: tryWrap(reload)
-  }
+  } as HMRRuntime
 }
 
 interface HMRRecord {
index aa624d5ef7ddf43b7ae4477affbdb13c79cf1eac..dbf89cba74ecc648e6fe8fe81f491c01450660d1 100644 (file)
@@ -130,3 +130,4 @@ export {
   DirectiveArguments
 } from './directives'
 export { SuspenseBoundary } from './components/Suspense'
+export { HMRRuntime } from './hmr'
index 8543e6ae7662f3088d447d6d09b06afc32587db7..133a21c2461e7ada1525cd28531fa190d5a8c127 100644 (file)
@@ -475,6 +475,7 @@ export function createRenderer<
       // HMR updated, force full diff
       patchFlag = 0
       optimized = false
+      dynamicChildren = null
     }
 
     if (patchFlag > 0) {
@@ -593,6 +594,9 @@ export function createRenderer<
   ) {
     for (let i = 0; i < newChildren.length; i++) {
       const oldVNode = oldChildren[i]
+      if (!oldVNode) {
+        debugger
+      }
       patch(
         oldVNode,
         newChildren[i],
@@ -682,7 +686,7 @@ export function createRenderer<
       ? n1.anchor
       : hostCreateComment(showID ? `fragment-${devFragmentID}-end` : ''))!
 
-    let { patchFlag } = n2
+    let { patchFlag, dynamicChildren } = n2
     if (patchFlag > 0) {
       optimized = true
     }
@@ -691,6 +695,7 @@ export function createRenderer<
       // HMR updated, force full diff
       patchFlag = 0
       optimized = false
+      dynamicChildren = null
     }
 
     if (n1 == null) {
@@ -712,12 +717,12 @@ export function createRenderer<
         optimized
       )
     } else {
-      if (patchFlag & PatchFlags.STABLE_FRAGMENT && n2.dynamicChildren) {
+      if (patchFlag & PatchFlags.STABLE_FRAGMENT && dynamicChildren != null) {
         // a stable fragment (template root or <template v-for>) doesn't need to
         // patch children order, but it may contain dynamicChildren.
         patchBlockChildren(
           n1.dynamicChildren!,
-          n2.dynamicChildren,
+          dynamicChildren,
           container,
           parentComponent,
           parentSuspense,