Skip to content

Commit 7a2c986

Browse files
committed
relax SSR hydration match check to allow client populating empty parent nodes
1 parent 4afccc8 commit 7a2c986

File tree

2 files changed

+61
-25
lines changed

2 files changed

+61
-25
lines changed

src/core/vdom/patch.js

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,7 @@ export function createPatchFunction (backend) {
117117
? nodeOps.createElementNS(vnode.ns, tag)
118118
: nodeOps.createElement(tag)
119119
setScope(vnode)
120-
if (Array.isArray(children)) {
121-
for (i = 0; i < children.length; ++i) {
122-
nodeOps.appendChild(elm, createElm(children[i], insertedVnodeQueue, true))
123-
}
124-
} else if (isPrimitive(vnode.text)) {
125-
nodeOps.appendChild(elm, nodeOps.createTextNode(vnode.text))
126-
}
120+
createChildren(vnode, children, insertedVnodeQueue)
127121
if (isDef(data)) {
128122
invokeCreateHooks(vnode, insertedVnodeQueue)
129123
}
@@ -135,6 +129,16 @@ export function createPatchFunction (backend) {
135129
return vnode.elm
136130
}
137131

132+
function createChildren (vnode, children, insertedVnodeQueue) {
133+
if (Array.isArray(children)) {
134+
for (let i = 0; i < children.length; ++i) {
135+
nodeOps.appendChild(vnode.elm, createElm(children[i], insertedVnodeQueue, true))
136+
}
137+
} else if (isPrimitive(vnode.text)) {
138+
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text))
139+
}
140+
}
141+
138142
function isPatchable (vnode) {
139143
while (vnode.child) {
140144
vnode = vnode.child._vnode
@@ -404,26 +408,31 @@ export function createPatchFunction (backend) {
404408
if (isDef(tag)) {
405409
if (isDef(children)) {
406410
const childNodes = nodeOps.childNodes(elm)
407-
let childrenMatch = true
408-
if (childNodes.length !== children.length) {
409-
childrenMatch = false
411+
// empty element, allow client to pick up and populate children
412+
if (!childNodes.length) {
413+
createChildren(vnode, children, insertedVnodeQueue)
410414
} else {
411-
for (let i = 0; i < children.length; i++) {
412-
if (!hydrate(childNodes[i], children[i], insertedVnodeQueue)) {
413-
childrenMatch = false
414-
break
415+
let childrenMatch = true
416+
if (childNodes.length !== children.length) {
417+
childrenMatch = false
418+
} else {
419+
for (let i = 0; i < children.length; i++) {
420+
if (!hydrate(childNodes[i], children[i], insertedVnodeQueue)) {
421+
childrenMatch = false
422+
break
423+
}
415424
}
416425
}
417-
}
418-
if (!childrenMatch) {
419-
if (process.env.NODE_ENV !== 'production' &&
420-
typeof console !== 'undefined' &&
421-
!bailed) {
422-
bailed = true
423-
console.warn('Parent: ', elm)
424-
console.warn('Mismatching childNodes vs. VNodes: ', childNodes, children)
426+
if (!childrenMatch) {
427+
if (process.env.NODE_ENV !== 'production' &&
428+
typeof console !== 'undefined' &&
429+
!bailed) {
430+
bailed = true
431+
console.warn('Parent: ', elm)
432+
console.warn('Mismatching childNodes vs. VNodes: ', childNodes, children)
433+
}
434+
return false
425435
}
426-
return false
427436
}
428437
}
429438
if (isDef(data)) {

test/unit/modules/vdom/patch/hydration.spec.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,36 @@ describe('vdom patch: hydration', () => {
141141
template: '<div><span>{{a}}</span></div>'
142142
}
143143
}
144-
})
144+
}).$mount(dom)
145145

146-
vm.$mount(dom)
147146
expect('not matching server-rendered content').toHaveBeenWarned()
148147
})
148+
149+
it('should pick up elements with no children and populate without warning', done => {
150+
const dom = document.createElement('div')
151+
dom.setAttribute('server-rendered', 'true')
152+
dom.innerHTML = '<div><span></span></div>'
153+
const span = dom.querySelector('span')
154+
155+
const vm = new Vue({
156+
template: '<div><test></test></div>',
157+
components: {
158+
test: {
159+
data () {
160+
return { a: 'qux' }
161+
},
162+
template: '<div><span>{{a}}</span></div>'
163+
}
164+
}
165+
}).$mount(dom)
166+
167+
expect('not matching server-rendered content').not.toHaveBeenWarned()
168+
expect(span).toBe(vm.$el.querySelector('span'))
169+
expect(vm.$el.innerHTML).toBe('<div><span>qux</span></div>')
170+
171+
vm.$children[0].a = 'foo'
172+
waitForUpdate(() => {
173+
expect(vm.$el.innerHTML).toBe('<div><span>foo</span></div>')
174+
}).then(done)
175+
})
149176
})

0 commit comments

Comments
 (0)