Repository: vuejs/vue
Pull request: fix(core): fix sameVnode for async component

I found and fixed a vdom bug related to async component and SSR.
I originally noticed this bug on my closed project using Nuxt.
The minimal reproduction code is included in test.

## Phenomenon

Throw when hydrated pending async component is patched by v-if="false" node.
(version: 2.6.11)

## Cause

1. v-if="false" node is represented by comment node
2. sameVNode returns true when asyncPlaceholder and comment are compared.
3. patchVNode executed for asyncPlaceholder and comment.
4. comment doesn't have asyncFactory but asyncFactory.resolved is accessed.

## Fix

Fix sameVNode to return false when asyncPlaceholder and comment are compared.

<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
-->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

**What kind of change does this PR introduce?** (check at least one)

- [x] Bugfix
- [ ] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:

**Does this PR introduce a breaking change?** (check one)

- [ ] Yes
- [x] No

If yes, please describe the impact and migration path for existing applications:

**The PR fulfills these requirements:**

- [x] It's submitted to the `dev` branch for v2.x (or to a previous version branch), _not_ the `master` branch
- [x] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)
- [x] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup
- [x] New/updated tests are included

If adding a **new feature**, the PR's description includes:
- [x] A convincing reason for adding this feature (to avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it)

**Other information:**

Changed files:
filename: src/core/vdom/patch.js
patch: @@ -34,15 +34,15 @@ const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
 
 function sameVnode (a, b) {
   return (
-    a.key === b.key && (
+    a.key === b.key &&
+    a.asyncFactory === b.asyncFactory && (
       (
         a.tag === b.tag &&
         a.isComment === b.isComment &&
         isDef(a.data) === isDef(b.data) &&
         sameInputType(a, b)
       ) || (
         isTrue(a.isAsyncPlaceholder) &&
-        a.asyncFactory === b.asyncFactory &&
         isUndef(b.asyncFactory.error)
       )
     )

filename: test/unit/modules/vdom/patch/edge-cases.spec.js
patch: @@ -1,4 +1,5 @@
 import Vue from 'vue'
+import { SSR_ATTR } from 'shared/constants'
 
 describe('vdom patch: edge cases', () => {
   // exposed by #3406
@@ -432,4 +433,22 @@ describe('vdom patch: edge cases', () => {
       expect(vm.$el.textContent).not.toMatch('Infinity')
     }).then(done)
   })
+
+  it('should not throw when hydrated pending async component is patched by v-if="false"', done => {
+    const PendingAsyncComponent = () => new Promise(() => {})
+    const ssrAsyncComponent = document.createElement('div')
+    ssrAsyncComponent.setAttribute(SSR_ATTR, 'true')
+    const vm = new Vue({
+      data: {
+        visible: true
+      },
+      components: {
+        PendingAsyncComponent
+      },
+      template: '<pending-async-component v-if="visible"></pending-async-component>'
+    }).$mount(ssrAsyncComponent)
+
+    vm.visible = false
+    vm.$nextTick(done)
+  })
 })