Skip to content

Commit

Permalink
use comment node as empty placeholder (fix <transition> SSR hydration)
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Aug 5, 2016
1 parent 0212521 commit 351aef3
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 28 deletions.
23 changes: 15 additions & 8 deletions src/core/vdom/patch.js
Expand Up @@ -30,6 +30,7 @@ function sameVnode (vnode1, vnode2) {
return (
vnode1.key === vnode2.key &&
vnode1.tag === vnode2.tag &&
vnode1.isComment === vnode2.isComment &&
!vnode1.data === !vnode2.data
)
}
Expand Down Expand Up @@ -87,12 +88,7 @@ export function createPatchFunction (backend) {
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(i = vnode.child)) {
if (vnode.data.pendingInsert) {
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
}
vnode.elm = vnode.child.$el
invokeCreateHooks(vnode, insertedVnodeQueue)
setScope(vnode)
initComponent(vnode, insertedVnodeQueue)
return vnode.elm
}
}
Expand Down Expand Up @@ -127,6 +123,8 @@ export function createPatchFunction (backend) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
} else if (vnode.isComment) {
elm = vnode.elm = nodeOps.createComment(vnode.text)
} else {
elm = vnode.elm = nodeOps.createTextNode(vnode.text)
}
Expand All @@ -144,6 +142,15 @@ export function createPatchFunction (backend) {
}
}

function initComponent (vnode, insertedVnodeQueue) {
if (vnode.data.pendingInsert) {
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
}
vnode.elm = vnode.child.$el
invokeCreateHooks(vnode, insertedVnodeQueue)
setScope(vnode)
}

// set scope id attribute for scoped CSS.
// this is implemented as a special case to avoid the overhead
// of going through the normal attribute patching process.
Expand Down Expand Up @@ -360,7 +367,7 @@ export function createPatchFunction (backend) {
if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */)
if (isDef(i = vnode.child)) {
// child component. it should have hydrated its own tree.
invokeCreateHooks(vnode, insertedVnodeQueue)
initComponent(vnode, insertedVnodeQueue)
return true
}
}
Expand Down Expand Up @@ -425,7 +432,7 @@ export function createPatchFunction (backend) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.hasAttribute('server-rendered')) {
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute('server-rendered')) {
oldVnode.removeAttribute('server-rendered')
hydrating = true
}
Expand Down
9 changes: 8 additions & 1 deletion src/core/vdom/vnode.js
Expand Up @@ -16,6 +16,7 @@ export default class VNode {
raw: ?boolean; // contains raw HTML
isStatic: ?boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean;

constructor (
tag?: string,
Expand Down Expand Up @@ -43,6 +44,7 @@ export default class VNode {
this.raw = false
this.isStatic = false
this.isRootInsert = true
this.isComment = false
// apply construct hook.
// this is applied during render, before patch happens.
// unlike other hooks, this is applied on both client and server.
Expand All @@ -53,4 +55,9 @@ export default class VNode {
}
}

export const emptyVNode = () => new VNode(undefined, undefined, undefined, '')
export const emptyVNode = () => {
const node = new VNode()
node.text = ''
node.isComment = true
return node
}
4 changes: 4 additions & 0 deletions src/platforms/web/runtime/node-ops.js
Expand Up @@ -14,6 +14,10 @@ export function createTextNode (text: string): Text {
return document.createTextNode(text)
}

export function createComment (text: string): Comment {
return document.createComment(text)
}

export function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) {
parentNode.insertBefore(newNode, referenceNode)
}
Expand Down
2 changes: 2 additions & 0 deletions src/server/render.js
Expand Up @@ -83,6 +83,8 @@ export function createRenderFunction (
} else {
if (node.tag) {
renderElement(node, write, next, isRoot)
} else if (node.isComment) {
write(`<!--${node.text}-->`, next)
} else {
write(node.raw ? node.text : encodeHTML(String(node.text)), next)
}
Expand Down
9 changes: 9 additions & 0 deletions test/ssr/ssr-string.spec.js
Expand Up @@ -500,6 +500,15 @@ describe('SSR: renderToString', () => {
})
})

it('comment nodes', done => {
renderVmWithOptions({
template: '<div><transition><div v-if="false"></test></transition></div>'
}, result => {
expect(result).toContain(`<div server-rendered="true"><!----></div>`)
done()
})
})

it('should catch error', done => {
renderToString(new Vue({
render () {
Expand Down
2 changes: 1 addition & 1 deletion test/unit/features/component/component-async.spec.js
Expand Up @@ -40,7 +40,7 @@ describe('Component async', () => {
}
}
}).$mount()
expect(vm.$el.nodeType).toBe(3)
expect(vm.$el.nodeType).toBe(8)
expect(vm.$children.length).toBe(0)
function next () {
expect(vm.$el.nodeType).toBe(1)
Expand Down
12 changes: 6 additions & 6 deletions test/unit/features/component/component-keep-alive.spec.js
Expand Up @@ -105,16 +105,16 @@ describe('Component keep-alive', () => {
vm.view = 'two'
waitForUpdate(() => {
expect(vm.$el.innerHTML).toBe(
'<div class="test test-leave test-leave-active">one</div>'
'<div class="test test-leave test-leave-active">one</div><!---->'
)
assertHookCalls(one, [1, 1, 1, 1, 0])
assertHookCalls(two, [0, 0, 0, 0, 0])
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.innerHTML).toBe(
'<div class="test test-leave-active">one</div>'
'<div class="test test-leave-active">one</div><!---->'
)
}).thenWaitFor(_next => { next = _next }).then(() => {
expect(vm.$el.innerHTML).toBe('')
expect(vm.$el.innerHTML).toBe('<!---->')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.innerHTML).toBe(
'<div class="test test-enter test-enter-active">two</div>'
Expand All @@ -135,16 +135,16 @@ describe('Component keep-alive', () => {
vm.view = 'one'
}).then(() => {
expect(vm.$el.innerHTML).toBe(
'<div class="test test-leave test-leave-active">two</div>'
'<div class="test test-leave test-leave-active">two</div><!---->'
)
assertHookCalls(one, [1, 1, 1, 1, 0])
assertHookCalls(two, [1, 1, 1, 1, 0])
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.innerHTML).toBe(
'<div class="test test-leave-active">two</div>'
'<div class="test test-leave-active">two</div><!---->'
)
}).thenWaitFor(_next => { next = _next }).then(() => {
expect(vm.$el.innerHTML).toBe('')
expect(vm.$el.innerHTML).toBe('<!---->')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.innerHTML).toBe(
'<div class="test test-enter test-enter-active">one</div>'
Expand Down
2 changes: 1 addition & 1 deletion test/unit/features/component/component.spec.js
Expand Up @@ -107,7 +107,7 @@ describe('Component', () => {
vm.view = ''
})
.then(() => {
expect(vm.$el.nodeType).toBe(3)
expect(vm.$el.nodeType).toBe(8)
expect(vm.$el.data).toBe('')
}).then(done)
})
Expand Down
12 changes: 6 additions & 6 deletions test/unit/features/transition/transition-mode.spec.js
Expand Up @@ -68,14 +68,14 @@ if (!isIE9) {
vm.view = 'two'
waitForUpdate(() => {
expect(vm.$el.innerHTML).toBe(
'<div class="test test-leave test-leave-active">one</div>'
'<div class="test test-leave test-leave-active">one</div><!---->'
)
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.innerHTML).toBe(
'<div class="test test-leave-active">one</div>'
'<div class="test test-leave-active">one</div><!---->'
)
}).thenWaitFor(_next => { next = _next }).then(() => {
expect(vm.$el.innerHTML).toBe('')
expect(vm.$el.innerHTML).toBe('<!---->')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.innerHTML).toBe(
'<div class="test test-enter test-enter-active">two</div>'
Expand Down Expand Up @@ -257,14 +257,14 @@ if (!isIE9) {
vm.view = 'two'
waitForUpdate(() => {
expect(vm.$el.innerHTML).toBe(
'<div class="test test-leave test-leave-active">one</div>'
'<div class="test test-leave test-leave-active">one</div><!---->'
)
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.innerHTML).toBe(
'<div class="test test-leave-active">one</div>'
'<div class="test test-leave-active">one</div><!---->'
)
}).thenWaitFor(_next => { next = _next }).then(() => {
expect(vm.$el.innerHTML).toBe('')
expect(vm.$el.innerHTML).toBe('<!---->')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.innerHTML).toBe(
'<div class="test test-enter test-enter-active">two</div>'
Expand Down
10 changes: 5 additions & 5 deletions test/unit/features/transition/transition.spec.js
Expand Up @@ -316,7 +316,7 @@ if (!isIE9) {
vm.ok = false
waitForUpdate(() => {
expect(leaveSpy).toHaveBeenCalled()
expect(vm.$el.innerHTML).toBe('')
expect(vm.$el.innerHTML).toBe('<!---->')
vm.ok = true
}).then(() => {
expect(enterSpy).toHaveBeenCalled()
Expand All @@ -339,9 +339,9 @@ if (!isIE9) {
vm.ok = false
waitForUpdate(() => {
expect(leaveSpy).toHaveBeenCalled()
expect(vm.$el.innerHTML).toBe('<div class="nope-leave nope-leave-active">foo</div>')
expect(vm.$el.innerHTML).toBe('<div class="nope-leave nope-leave-active">foo</div><!---->')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.innerHTML).toBe('')
expect(vm.$el.innerHTML).toBe('<!---->')
vm.ok = true
}).then(() => {
expect(enterSpy).toHaveBeenCalled()
Expand All @@ -367,7 +367,7 @@ if (!isIE9) {
}
}).$mount(el)

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

0 comments on commit 351aef3

Please sign in to comment.