Skip to content

Commit 351aef3

Browse files
committed
use comment node as empty placeholder (fix <transition> SSR hydration)
1 parent 0212521 commit 351aef3

File tree

10 files changed

+57
-28
lines changed

10 files changed

+57
-28
lines changed

src/core/vdom/patch.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ function sameVnode (vnode1, vnode2) {
3030
return (
3131
vnode1.key === vnode2.key &&
3232
vnode1.tag === vnode2.tag &&
33+
vnode1.isComment === vnode2.isComment &&
3334
!vnode1.data === !vnode2.data
3435
)
3536
}
@@ -87,12 +88,7 @@ export function createPatchFunction (backend) {
8788
// component also has set the placeholder vnode's elm.
8889
// in that case we can just return the element and be done.
8990
if (isDef(i = vnode.child)) {
90-
if (vnode.data.pendingInsert) {
91-
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
92-
}
93-
vnode.elm = vnode.child.$el
94-
invokeCreateHooks(vnode, insertedVnodeQueue)
95-
setScope(vnode)
91+
initComponent(vnode, insertedVnodeQueue)
9692
return vnode.elm
9793
}
9894
}
@@ -127,6 +123,8 @@ export function createPatchFunction (backend) {
127123
if (isDef(data)) {
128124
invokeCreateHooks(vnode, insertedVnodeQueue)
129125
}
126+
} else if (vnode.isComment) {
127+
elm = vnode.elm = nodeOps.createComment(vnode.text)
130128
} else {
131129
elm = vnode.elm = nodeOps.createTextNode(vnode.text)
132130
}
@@ -144,6 +142,15 @@ export function createPatchFunction (backend) {
144142
}
145143
}
146144

145+
function initComponent (vnode, insertedVnodeQueue) {
146+
if (vnode.data.pendingInsert) {
147+
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
148+
}
149+
vnode.elm = vnode.child.$el
150+
invokeCreateHooks(vnode, insertedVnodeQueue)
151+
setScope(vnode)
152+
}
153+
147154
// set scope id attribute for scoped CSS.
148155
// this is implemented as a special case to avoid the overhead
149156
// of going through the normal attribute patching process.
@@ -360,7 +367,7 @@ export function createPatchFunction (backend) {
360367
if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */)
361368
if (isDef(i = vnode.child)) {
362369
// child component. it should have hydrated its own tree.
363-
invokeCreateHooks(vnode, insertedVnodeQueue)
370+
initComponent(vnode, insertedVnodeQueue)
364371
return true
365372
}
366373
}
@@ -425,7 +432,7 @@ export function createPatchFunction (backend) {
425432
// mounting to a real element
426433
// check if this is server-rendered content and if we can perform
427434
// a successful hydration.
428-
if (oldVnode.hasAttribute('server-rendered')) {
435+
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute('server-rendered')) {
429436
oldVnode.removeAttribute('server-rendered')
430437
hydrating = true
431438
}

src/core/vdom/vnode.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export default class VNode {
1616
raw: ?boolean; // contains raw HTML
1717
isStatic: ?boolean; // hoisted static node
1818
isRootInsert: boolean; // necessary for enter transition check
19+
isComment: boolean;
1920

2021
constructor (
2122
tag?: string,
@@ -43,6 +44,7 @@ export default class VNode {
4344
this.raw = false
4445
this.isStatic = false
4546
this.isRootInsert = true
47+
this.isComment = false
4648
// apply construct hook.
4749
// this is applied during render, before patch happens.
4850
// unlike other hooks, this is applied on both client and server.
@@ -53,4 +55,9 @@ export default class VNode {
5355
}
5456
}
5557

56-
export const emptyVNode = () => new VNode(undefined, undefined, undefined, '')
58+
export const emptyVNode = () => {
59+
const node = new VNode()
60+
node.text = ''
61+
node.isComment = true
62+
return node
63+
}

src/platforms/web/runtime/node-ops.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export function createTextNode (text: string): Text {
1414
return document.createTextNode(text)
1515
}
1616

17+
export function createComment (text: string): Comment {
18+
return document.createComment(text)
19+
}
20+
1721
export function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) {
1822
parentNode.insertBefore(newNode, referenceNode)
1923
}

src/server/render.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ export function createRenderFunction (
8383
} else {
8484
if (node.tag) {
8585
renderElement(node, write, next, isRoot)
86+
} else if (node.isComment) {
87+
write(`<!--${node.text}-->`, next)
8688
} else {
8789
write(node.raw ? node.text : encodeHTML(String(node.text)), next)
8890
}

test/ssr/ssr-string.spec.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,15 @@ describe('SSR: renderToString', () => {
500500
})
501501
})
502502

503+
it('comment nodes', done => {
504+
renderVmWithOptions({
505+
template: '<div><transition><div v-if="false"></test></transition></div>'
506+
}, result => {
507+
expect(result).toContain(`<div server-rendered="true"><!----></div>`)
508+
done()
509+
})
510+
})
511+
503512
it('should catch error', done => {
504513
renderToString(new Vue({
505514
render () {

test/unit/features/component/component-async.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe('Component async', () => {
4040
}
4141
}
4242
}).$mount()
43-
expect(vm.$el.nodeType).toBe(3)
43+
expect(vm.$el.nodeType).toBe(8)
4444
expect(vm.$children.length).toBe(0)
4545
function next () {
4646
expect(vm.$el.nodeType).toBe(1)

test/unit/features/component/component-keep-alive.spec.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,16 @@ describe('Component keep-alive', () => {
105105
vm.view = 'two'
106106
waitForUpdate(() => {
107107
expect(vm.$el.innerHTML).toBe(
108-
'<div class="test test-leave test-leave-active">one</div>'
108+
'<div class="test test-leave test-leave-active">one</div><!---->'
109109
)
110110
assertHookCalls(one, [1, 1, 1, 1, 0])
111111
assertHookCalls(two, [0, 0, 0, 0, 0])
112112
}).thenWaitFor(nextFrame).then(() => {
113113
expect(vm.$el.innerHTML).toBe(
114-
'<div class="test test-leave-active">one</div>'
114+
'<div class="test test-leave-active">one</div><!---->'
115115
)
116116
}).thenWaitFor(_next => { next = _next }).then(() => {
117-
expect(vm.$el.innerHTML).toBe('')
117+
expect(vm.$el.innerHTML).toBe('<!---->')
118118
}).thenWaitFor(nextFrame).then(() => {
119119
expect(vm.$el.innerHTML).toBe(
120120
'<div class="test test-enter test-enter-active">two</div>'
@@ -135,16 +135,16 @@ describe('Component keep-alive', () => {
135135
vm.view = 'one'
136136
}).then(() => {
137137
expect(vm.$el.innerHTML).toBe(
138-
'<div class="test test-leave test-leave-active">two</div>'
138+
'<div class="test test-leave test-leave-active">two</div><!---->'
139139
)
140140
assertHookCalls(one, [1, 1, 1, 1, 0])
141141
assertHookCalls(two, [1, 1, 1, 1, 0])
142142
}).thenWaitFor(nextFrame).then(() => {
143143
expect(vm.$el.innerHTML).toBe(
144-
'<div class="test test-leave-active">two</div>'
144+
'<div class="test test-leave-active">two</div><!---->'
145145
)
146146
}).thenWaitFor(_next => { next = _next }).then(() => {
147-
expect(vm.$el.innerHTML).toBe('')
147+
expect(vm.$el.innerHTML).toBe('<!---->')
148148
}).thenWaitFor(nextFrame).then(() => {
149149
expect(vm.$el.innerHTML).toBe(
150150
'<div class="test test-enter test-enter-active">one</div>'

test/unit/features/component/component.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ describe('Component', () => {
107107
vm.view = ''
108108
})
109109
.then(() => {
110-
expect(vm.$el.nodeType).toBe(3)
110+
expect(vm.$el.nodeType).toBe(8)
111111
expect(vm.$el.data).toBe('')
112112
}).then(done)
113113
})

test/unit/features/transition/transition-mode.spec.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,14 @@ if (!isIE9) {
6868
vm.view = 'two'
6969
waitForUpdate(() => {
7070
expect(vm.$el.innerHTML).toBe(
71-
'<div class="test test-leave test-leave-active">one</div>'
71+
'<div class="test test-leave test-leave-active">one</div><!---->'
7272
)
7373
}).thenWaitFor(nextFrame).then(() => {
7474
expect(vm.$el.innerHTML).toBe(
75-
'<div class="test test-leave-active">one</div>'
75+
'<div class="test test-leave-active">one</div><!---->'
7676
)
7777
}).thenWaitFor(_next => { next = _next }).then(() => {
78-
expect(vm.$el.innerHTML).toBe('')
78+
expect(vm.$el.innerHTML).toBe('<!---->')
7979
}).thenWaitFor(nextFrame).then(() => {
8080
expect(vm.$el.innerHTML).toBe(
8181
'<div class="test test-enter test-enter-active">two</div>'
@@ -257,14 +257,14 @@ if (!isIE9) {
257257
vm.view = 'two'
258258
waitForUpdate(() => {
259259
expect(vm.$el.innerHTML).toBe(
260-
'<div class="test test-leave test-leave-active">one</div>'
260+
'<div class="test test-leave test-leave-active">one</div><!---->'
261261
)
262262
}).thenWaitFor(nextFrame).then(() => {
263263
expect(vm.$el.innerHTML).toBe(
264-
'<div class="test test-leave-active">one</div>'
264+
'<div class="test test-leave-active">one</div><!---->'
265265
)
266266
}).thenWaitFor(_next => { next = _next }).then(() => {
267-
expect(vm.$el.innerHTML).toBe('')
267+
expect(vm.$el.innerHTML).toBe('<!---->')
268268
}).thenWaitFor(nextFrame).then(() => {
269269
expect(vm.$el.innerHTML).toBe(
270270
'<div class="test test-enter test-enter-active">two</div>'

test/unit/features/transition/transition.spec.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ if (!isIE9) {
316316
vm.ok = false
317317
waitForUpdate(() => {
318318
expect(leaveSpy).toHaveBeenCalled()
319-
expect(vm.$el.innerHTML).toBe('')
319+
expect(vm.$el.innerHTML).toBe('<!---->')
320320
vm.ok = true
321321
}).then(() => {
322322
expect(enterSpy).toHaveBeenCalled()
@@ -339,9 +339,9 @@ if (!isIE9) {
339339
vm.ok = false
340340
waitForUpdate(() => {
341341
expect(leaveSpy).toHaveBeenCalled()
342-
expect(vm.$el.innerHTML).toBe('<div class="nope-leave nope-leave-active">foo</div>')
342+
expect(vm.$el.innerHTML).toBe('<div class="nope-leave nope-leave-active">foo</div><!---->')
343343
}).thenWaitFor(nextFrame).then(() => {
344-
expect(vm.$el.innerHTML).toBe('')
344+
expect(vm.$el.innerHTML).toBe('<!---->')
345345
vm.ok = true
346346
}).then(() => {
347347
expect(enterSpy).toHaveBeenCalled()
@@ -367,7 +367,7 @@ if (!isIE9) {
367367
}
368368
}).$mount(el)
369369

370-
expect(vm.$el.innerHTML).toBe('')
370+
expect(vm.$el.innerHTML).toBe('<!---->')
371371
vm.ok = true
372372
waitForUpdate(() => {
373373
expect(vm.$el.children[0].className).toBe('test test-enter test-enter-active')
@@ -652,7 +652,7 @@ if (!isIE9) {
652652
expect(vm.$el.childNodes[0].getAttribute('class')).toBe('test v-leave-active')
653653
}).thenWaitFor(duration + 10).then(() => {
654654
expect(vm.$el.childNodes.length).toBe(1)
655-
expect(vm.$el.childNodes[0].nodeType).toBe(3) // should be an empty text node
655+
expect(vm.$el.childNodes[0].nodeType).toBe(8) // should be an empty comment node
656656
expect(vm.$el.childNodes[0].textContent).toBe('')
657657
vm.ok = true
658658
}).then(() => {

0 commit comments

Comments
 (0)