Browse Source

fix: should warn unknown components during hydration

fix #6998
Evan You 8 years ago
parent
commit
df82aeb0bf
2 changed files with 48 additions and 30 deletions
  1. 37 30
      src/core/vdom/patch.js
  2. 11 0
      test/unit/modules/vdom/patch/hydration.spec.js

+ 37 - 30
src/core/vdom/patch.js

@@ -103,7 +103,23 @@ export function createPatchFunction (backend) {
     }
   }
 
-  let inPre = 0
+  function isUnknownElement (vnode, inVPre) {
+    return (
+      !inVPre &&
+      !vnode.ns &&
+      !(
+        config.ignoredElements.length &&
+        config.ignoredElements.some(ignore => {
+          return isRegExp(ignore)
+            ? ignore.test(vnode.tag)
+            : ignore === vnode.tag
+        })
+      ) &&
+      config.isUnknownElement(vnode.tag)
+    )
+  }
+
+  let creatingElmInVPre = 0
   function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) {
     vnode.isRootInsert = !nested // for transition enter check
     if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
@@ -116,21 +132,9 @@ export function createPatchFunction (backend) {
     if (isDef(tag)) {
       if (process.env.NODE_ENV !== 'production') {
         if (data && data.pre) {
-          inPre++
+          creatingElmInVPre++
         }
-        if (
-          !inPre &&
-          !vnode.ns &&
-          !(
-            config.ignoredElements.length &&
-            config.ignoredElements.some(ignore => {
-              return isRegExp(ignore)
-                ? ignore.test(tag)
-                : ignore === tag
-            })
-          ) &&
-          config.isUnknownElement(tag)
-        ) {
+        if (isUnknownElement(vnode, creatingElmInVPre)) {
           warn(
             'Unknown custom element: <' + tag + '> - did you ' +
             'register the component correctly? For recursive components, ' +
@@ -172,7 +176,7 @@ export function createPatchFunction (backend) {
       }
 
       if (process.env.NODE_ENV !== 'production' && data && data.pre) {
-        inPre--
+        creatingElmInVPre--
       }
     } else if (isTrue(vnode.isComment)) {
       vnode.elm = nodeOps.createComment(vnode.text)
@@ -527,25 +531,28 @@ export function createPatchFunction (backend) {
     }
   }
 
-  let bailed = false
+  let hydrationBailed = false
   // list of modules that can skip create hook during hydration because they
   // are already rendered on the client or has no need for initialization
   const isRenderedModule = makeMap('attrs,style,class,staticClass,staticStyle,key')
 
   // Note: this is a browser-only function so we can assume elms are DOM nodes.
-  function hydrate (elm, vnode, insertedVnodeQueue) {
+  function hydrate (elm, vnode, insertedVnodeQueue, inVPre) {
+    let i
+    const { tag, data, children } = vnode
+    inVPre = inVPre || (data && data.pre)
+    vnode.elm = elm
+
     if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {
-      vnode.elm = elm
       vnode.isAsyncPlaceholder = true
       return true
     }
+    // assert node match
     if (process.env.NODE_ENV !== 'production') {
-      if (!assertNodeMatch(elm, vnode)) {
+      if (!assertNodeMatch(elm, vnode, inVPre)) {
         return false
       }
     }
-    vnode.elm = elm
-    const { tag, data, children } = vnode
     if (isDef(data)) {
       if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */)
       if (isDef(i = vnode.componentInstance)) {
@@ -566,9 +573,9 @@ export function createPatchFunction (backend) {
               /* istanbul ignore if */
               if (process.env.NODE_ENV !== 'production' &&
                 typeof console !== 'undefined' &&
-                !bailed
+                !hydrationBailed
               ) {
-                bailed = true
+                hydrationBailed = true
                 console.warn('Parent: ', elm)
                 console.warn('server innerHTML: ', i)
                 console.warn('client innerHTML: ', elm.innerHTML)
@@ -580,7 +587,7 @@ export function createPatchFunction (backend) {
             let childrenMatch = true
             let childNode = elm.firstChild
             for (let i = 0; i < children.length; i++) {
-              if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue)) {
+              if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue, inVPre)) {
                 childrenMatch = false
                 break
               }
@@ -592,9 +599,9 @@ export function createPatchFunction (backend) {
               /* istanbul ignore if */
               if (process.env.NODE_ENV !== 'production' &&
                 typeof console !== 'undefined' &&
-                !bailed
+                !hydrationBailed
               ) {
-                bailed = true
+                hydrationBailed = true
                 console.warn('Parent: ', elm)
                 console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children)
               }
@@ -617,10 +624,10 @@ export function createPatchFunction (backend) {
     return true
   }
 
-  function assertNodeMatch (node, vnode) {
+  function assertNodeMatch (node, vnode, inVPre) {
     if (isDef(vnode.tag)) {
-      return (
-        vnode.tag.indexOf('vue-component') === 0 ||
+      return vnode.tag.indexOf('vue-component') === 0 || (
+        !isUnknownElement(vnode, inVPre) &&
         vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase())
       )
     } else {

+ 11 - 0
test/unit/modules/vdom/patch/hydration.spec.js

@@ -150,6 +150,17 @@ describe('vdom patch: hydration', () => {
     expect('not matching server-rendered content').toHaveBeenWarned()
   })
 
+  it('should warn failed hydration when component is not properly registered', () => {
+    const dom = createMockSSRDOM('<div><foo></foo></div>')
+
+    new Vue({
+      template: '<div><foo></foo></div>'
+    }).$mount(dom)
+
+    expect('not matching server-rendered content').toHaveBeenWarned()
+    expect('Unknown custom element: <foo>').toHaveBeenWarned()
+  })
+
   it('should overwrite textNodes in the correct position but with mismatching text without warning', () => {
     const dom = createMockSSRDOM('<div><span>foo</span></div>')