fix: should warn unknown components during hydration

fix #6998
This commit is contained in:
Evan You 2017-11-13 15:52:01 -05:00
parent daed1e7355
commit df82aeb0bf
2 changed files with 48 additions and 30 deletions

View File

@ -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 {

View File

@ -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>')