UNPKG

26.6 kBJavaScriptView Raw
1/**
2 * Virtual DOM patching algorithm based on Snabbdom by
3 * Simon Friis Vindum (@paldepind)
4 * Licensed under the MIT License
5 * https://github.com/paldepind/snabbdom/blob/master/LICENSE
6 *
7 * modified by Evan You (@yyx990803)
8 *
9 * Not type-checking this because this file is perf-critical and the cost
10 * of making flow understand it is not worth it.
11 */
12
13import VNode, { cloneVNode } from './vnode'
14import config from '../config'
15import { SSR_ATTR } from 'shared/constants'
16import { registerRef } from './modules/ref'
17import { traverse } from '../observer/traverse'
18import { activeInstance } from '../instance/lifecycle'
19import { isTextInputType } from 'web/util/element'
20
21import {
22 warn,
23 isDef,
24 isUndef,
25 isTrue,
26 makeMap,
27 isRegExp,
28 isPrimitive
29} from '../util/index'
30
31export const emptyNode = new VNode('', {}, [])
32
33const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
34
35function sameVnode (a, b) {
36 return (
37 a.key === b.key &&
38 a.asyncFactory === b.asyncFactory && (
39 (
40 a.tag === b.tag &&
41 a.isComment === b.isComment &&
42 isDef(a.data) === isDef(b.data) &&
43 sameInputType(a, b)
44 ) || (
45 isTrue(a.isAsyncPlaceholder) &&
46 isUndef(b.asyncFactory.error)
47 )
48 )
49 )
50}
51
52function sameInputType (a, b) {
53 if (a.tag !== 'input') return true
54 let i
55 const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
56 const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
57 return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
58}
59
60function createKeyToOldIdx (children, beginIdx, endIdx) {
61 let i, key
62 const map = {}
63 for (i = beginIdx; i <= endIdx; ++i) {
64 key = children[i].key
65 if (isDef(key)) map[key] = i
66 }
67 return map
68}
69
70export function createPatchFunction (backend) {
71 let i, j
72 const cbs = {}
73
74 const { modules, nodeOps } = backend
75
76 for (i = 0; i < hooks.length; ++i) {
77 cbs[hooks[i]] = []
78 for (j = 0; j < modules.length; ++j) {
79 if (isDef(modules[j][hooks[i]])) {
80 cbs[hooks[i]].push(modules[j][hooks[i]])
81 }
82 }
83 }
84
85 function emptyNodeAt (elm) {
86 return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
87 }
88
89 function createRmCb (childElm, listeners) {
90 function remove () {
91 if (--remove.listeners === 0) {
92 removeNode(childElm)
93 }
94 }
95 remove.listeners = listeners
96 return remove
97 }
98
99 function removeNode (el) {
100 const parent = nodeOps.parentNode(el)
101 // element may have already been removed due to v-html / v-text
102 if (isDef(parent)) {
103 nodeOps.removeChild(parent, el)
104 }
105 }
106
107 function isUnknownElement (vnode, inVPre) {
108 return (
109 !inVPre &&
110 !vnode.ns &&
111 !(
112 config.ignoredElements.length &&
113 config.ignoredElements.some(ignore => {
114 return isRegExp(ignore)
115 ? ignore.test(vnode.tag)
116 : ignore === vnode.tag
117 })
118 ) &&
119 config.isUnknownElement(vnode.tag)
120 )
121 }
122
123 let creatingElmInVPre = 0
124
125 function createElm (
126 vnode,
127 insertedVnodeQueue,
128 parentElm,
129 refElm,
130 nested,
131 ownerArray,
132 index
133 ) {
134 if (isDef(vnode.elm) && isDef(ownerArray)) {
135 // This vnode was used in a previous render!
136 // now it's used as a new node, overwriting its elm would cause
137 // potential patch errors down the road when it's used as an insertion
138 // reference node. Instead, we clone the node on-demand before creating
139 // associated DOM element for it.
140 vnode = ownerArray[index] = cloneVNode(vnode)
141 }
142
143 vnode.isRootInsert = !nested // for transition enter check
144 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
145 return
146 }
147
148 const data = vnode.data
149 const children = vnode.children
150 const tag = vnode.tag
151 if (isDef(tag)) {
152 if (process.env.NODE_ENV !== 'production') {
153 if (data && data.pre) {
154 creatingElmInVPre++
155 }
156 if (isUnknownElement(vnode, creatingElmInVPre)) {
157 warn(
158 'Unknown custom element: <' + tag + '> - did you ' +
159 'register the component correctly? For recursive components, ' +
160 'make sure to provide the "name" option.',
161 vnode.context
162 )
163 }
164 }
165
166 vnode.elm = vnode.ns
167 ? nodeOps.createElementNS(vnode.ns, tag)
168 : nodeOps.createElement(tag, vnode)
169 setScope(vnode)
170
171 /* istanbul ignore if */
172 if (__WEEX__) {
173 // in Weex, the default insertion order is parent-first.
174 // List items can be optimized to use children-first insertion
175 // with append="tree".
176 const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
177 if (!appendAsTree) {
178 if (isDef(data)) {
179 invokeCreateHooks(vnode, insertedVnodeQueue)
180 }
181 insert(parentElm, vnode.elm, refElm)
182 }
183 createChildren(vnode, children, insertedVnodeQueue)
184 if (appendAsTree) {
185 if (isDef(data)) {
186 invokeCreateHooks(vnode, insertedVnodeQueue)
187 }
188 insert(parentElm, vnode.elm, refElm)
189 }
190 } else {
191 createChildren(vnode, children, insertedVnodeQueue)
192 if (isDef(data)) {
193 invokeCreateHooks(vnode, insertedVnodeQueue)
194 }
195 insert(parentElm, vnode.elm, refElm)
196 }
197
198 if (process.env.NODE_ENV !== 'production' && data && data.pre) {
199 creatingElmInVPre--
200 }
201 } else if (isTrue(vnode.isComment)) {
202 vnode.elm = nodeOps.createComment(vnode.text)
203 insert(parentElm, vnode.elm, refElm)
204 } else {
205 vnode.elm = nodeOps.createTextNode(vnode.text)
206 insert(parentElm, vnode.elm, refElm)
207 }
208 }
209
210 function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
211 let i = vnode.data
212 if (isDef(i)) {
213 const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
214 if (isDef(i = i.hook) && isDef(i = i.init)) {
215 i(vnode, false /* hydrating */)
216 }
217 // after calling the init hook, if the vnode is a child component
218 // it should've created a child instance and mounted it. the child
219 // component also has set the placeholder vnode's elm.
220 // in that case we can just return the element and be done.
221 if (isDef(vnode.componentInstance)) {
222 initComponent(vnode, insertedVnodeQueue)
223 insert(parentElm, vnode.elm, refElm)
224 if (isTrue(isReactivated)) {
225 reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
226 }
227 return true
228 }
229 }
230 }
231
232 function initComponent (vnode, insertedVnodeQueue) {
233 if (isDef(vnode.data.pendingInsert)) {
234 insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
235 vnode.data.pendingInsert = null
236 }
237 vnode.elm = vnode.componentInstance.$el
238 if (isPatchable(vnode)) {
239 invokeCreateHooks(vnode, insertedVnodeQueue)
240 setScope(vnode)
241 } else {
242 // empty component root.
243 // skip all element-related modules except for ref (#3455)
244 registerRef(vnode)
245 // make sure to invoke the insert hook
246 insertedVnodeQueue.push(vnode)
247 }
248 }
249
250 function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
251 let i
252 // hack for #4339: a reactivated component with inner transition
253 // does not trigger because the inner node's created hooks are not called
254 // again. It's not ideal to involve module-specific logic in here but
255 // there doesn't seem to be a better way to do it.
256 let innerNode = vnode
257 while (innerNode.componentInstance) {
258 innerNode = innerNode.componentInstance._vnode
259 if (isDef(i = innerNode.data) && isDef(i = i.transition)) {
260 for (i = 0; i < cbs.activate.length; ++i) {
261 cbs.activate[i](emptyNode, innerNode)
262 }
263 insertedVnodeQueue.push(innerNode)
264 break
265 }
266 }
267 // unlike a newly created component,
268 // a reactivated keep-alive component doesn't insert itself
269 insert(parentElm, vnode.elm, refElm)
270 }
271
272 function insert (parent, elm, ref) {
273 if (isDef(parent)) {
274 if (isDef(ref)) {
275 if (nodeOps.parentNode(ref) === parent) {
276 nodeOps.insertBefore(parent, elm, ref)
277 }
278 } else {
279 nodeOps.appendChild(parent, elm)
280 }
281 }
282 }
283
284 function createChildren (vnode, children, insertedVnodeQueue) {
285 if (Array.isArray(children)) {
286 if (process.env.NODE_ENV !== 'production') {
287 checkDuplicateKeys(children)
288 }
289 for (let i = 0; i < children.length; ++i) {
290 createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
291 }
292 } else if (isPrimitive(vnode.text)) {
293 nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
294 }
295 }
296
297 function isPatchable (vnode) {
298 while (vnode.componentInstance) {
299 vnode = vnode.componentInstance._vnode
300 }
301 return isDef(vnode.tag)
302 }
303
304 function invokeCreateHooks (vnode, insertedVnodeQueue) {
305 for (let i = 0; i < cbs.create.length; ++i) {
306 cbs.create[i](emptyNode, vnode)
307 }
308 i = vnode.data.hook // Reuse variable
309 if (isDef(i)) {
310 if (isDef(i.create)) i.create(emptyNode, vnode)
311 if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
312 }
313 }
314
315 // set scope id attribute for scoped CSS.
316 // this is implemented as a special case to avoid the overhead
317 // of going through the normal attribute patching process.
318 function setScope (vnode) {
319 let i
320 if (isDef(i = vnode.fnScopeId)) {
321 nodeOps.setStyleScope(vnode.elm, i)
322 } else {
323 let ancestor = vnode
324 while (ancestor) {
325 if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {
326 nodeOps.setStyleScope(vnode.elm, i)
327 }
328 ancestor = ancestor.parent
329 }
330 }
331 // for slot content they should also get the scopeId from the host instance.
332 if (isDef(i = activeInstance) &&
333 i !== vnode.context &&
334 i !== vnode.fnContext &&
335 isDef(i = i.$options._scopeId)
336 ) {
337 nodeOps.setStyleScope(vnode.elm, i)
338 }
339 }
340
341 function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {
342 for (; startIdx <= endIdx; ++startIdx) {
343 createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx)
344 }
345 }
346
347 function invokeDestroyHook (vnode) {
348 let i, j
349 const data = vnode.data
350 if (isDef(data)) {
351 if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
352 for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
353 }
354 if (isDef(i = vnode.children)) {
355 for (j = 0; j < vnode.children.length; ++j) {
356 invokeDestroyHook(vnode.children[j])
357 }
358 }
359 }
360
361 function removeVnodes (vnodes, startIdx, endIdx) {
362 for (; startIdx <= endIdx; ++startIdx) {
363 const ch = vnodes[startIdx]
364 if (isDef(ch)) {
365 if (isDef(ch.tag)) {
366 removeAndInvokeRemoveHook(ch)
367 invokeDestroyHook(ch)
368 } else { // Text node
369 removeNode(ch.elm)
370 }
371 }
372 }
373 }
374
375 function removeAndInvokeRemoveHook (vnode, rm) {
376 if (isDef(rm) || isDef(vnode.data)) {
377 let i
378 const listeners = cbs.remove.length + 1
379 if (isDef(rm)) {
380 // we have a recursively passed down rm callback
381 // increase the listeners count
382 rm.listeners += listeners
383 } else {
384 // directly removing
385 rm = createRmCb(vnode.elm, listeners)
386 }
387 // recursively invoke hooks on child component root node
388 if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {
389 removeAndInvokeRemoveHook(i, rm)
390 }
391 for (i = 0; i < cbs.remove.length; ++i) {
392 cbs.remove[i](vnode, rm)
393 }
394 if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
395 i(vnode, rm)
396 } else {
397 rm()
398 }
399 } else {
400 removeNode(vnode.elm)
401 }
402 }
403
404 function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
405 let oldStartIdx = 0
406 let newStartIdx = 0
407 let oldEndIdx = oldCh.length - 1
408 let oldStartVnode = oldCh[0]
409 let oldEndVnode = oldCh[oldEndIdx]
410 let newEndIdx = newCh.length - 1
411 let newStartVnode = newCh[0]
412 let newEndVnode = newCh[newEndIdx]
413 let oldKeyToIdx, idxInOld, vnodeToMove, refElm
414
415 // removeOnly is a special flag used only by <transition-group>
416 // to ensure removed elements stay in correct relative positions
417 // during leaving transitions
418 const canMove = !removeOnly
419
420 if (process.env.NODE_ENV !== 'production') {
421 checkDuplicateKeys(newCh)
422 }
423
424 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
425 if (isUndef(oldStartVnode)) {
426 oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
427 } else if (isUndef(oldEndVnode)) {
428 oldEndVnode = oldCh[--oldEndIdx]
429 } else if (sameVnode(oldStartVnode, newStartVnode)) {
430 patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
431 oldStartVnode = oldCh[++oldStartIdx]
432 newStartVnode = newCh[++newStartIdx]
433 } else if (sameVnode(oldEndVnode, newEndVnode)) {
434 patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
435 oldEndVnode = oldCh[--oldEndIdx]
436 newEndVnode = newCh[--newEndIdx]
437 } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
438 patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
439 canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
440 oldStartVnode = oldCh[++oldStartIdx]
441 newEndVnode = newCh[--newEndIdx]
442 } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
443 patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
444 canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
445 oldEndVnode = oldCh[--oldEndIdx]
446 newStartVnode = newCh[++newStartIdx]
447 } else {
448 if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
449 idxInOld = isDef(newStartVnode.key)
450 ? oldKeyToIdx[newStartVnode.key]
451 : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
452 if (isUndef(idxInOld)) { // New element
453 createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
454 } else {
455 vnodeToMove = oldCh[idxInOld]
456 if (sameVnode(vnodeToMove, newStartVnode)) {
457 patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
458 oldCh[idxInOld] = undefined
459 canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
460 } else {
461 // same key but different element. treat as new element
462 createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
463 }
464 }
465 newStartVnode = newCh[++newStartIdx]
466 }
467 }
468 if (oldStartIdx > oldEndIdx) {
469 refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
470 addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
471 } else if (newStartIdx > newEndIdx) {
472 removeVnodes(oldCh, oldStartIdx, oldEndIdx)
473 }
474 }
475
476 function checkDuplicateKeys (children) {
477 const seenKeys = {}
478 for (let i = 0; i < children.length; i++) {
479 const vnode = children[i]
480 const key = vnode.key
481 if (isDef(key)) {
482 if (seenKeys[key]) {
483 warn(
484 `Duplicate keys detected: '${key}'. This may cause an update error.`,
485 vnode.context
486 )
487 } else {
488 seenKeys[key] = true
489 }
490 }
491 }
492 }
493
494 function findIdxInOld (node, oldCh, start, end) {
495 for (let i = start; i < end; i++) {
496 const c = oldCh[i]
497 if (isDef(c) && sameVnode(node, c)) return i
498 }
499 }
500
501 function patchVnode (
502 oldVnode,
503 vnode,
504 insertedVnodeQueue,
505 ownerArray,
506 index,
507 removeOnly
508 ) {
509 if (oldVnode === vnode) {
510 return
511 }
512
513 if (isDef(vnode.elm) && isDef(ownerArray)) {
514 // clone reused vnode
515 vnode = ownerArray[index] = cloneVNode(vnode)
516 }
517
518 const elm = vnode.elm = oldVnode.elm
519
520 if (isTrue(oldVnode.isAsyncPlaceholder)) {
521 if (isDef(vnode.asyncFactory.resolved)) {
522 hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
523 } else {
524 vnode.isAsyncPlaceholder = true
525 }
526 return
527 }
528
529 // reuse element for static trees.
530 // note we only do this if the vnode is cloned -
531 // if the new node is not cloned it means the render functions have been
532 // reset by the hot-reload-api and we need to do a proper re-render.
533 if (isTrue(vnode.isStatic) &&
534 isTrue(oldVnode.isStatic) &&
535 vnode.key === oldVnode.key &&
536 (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
537 ) {
538 vnode.componentInstance = oldVnode.componentInstance
539 return
540 }
541
542 let i
543 const data = vnode.data
544 if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
545 i(oldVnode, vnode)
546 }
547
548 const oldCh = oldVnode.children
549 const ch = vnode.children
550 if (isDef(data) && isPatchable(vnode)) {
551 for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
552 if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
553 }
554 if (isUndef(vnode.text)) {
555 if (isDef(oldCh) && isDef(ch)) {
556 if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
557 } else if (isDef(ch)) {
558 if (process.env.NODE_ENV !== 'production') {
559 checkDuplicateKeys(ch)
560 }
561 if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
562 addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
563 } else if (isDef(oldCh)) {
564 removeVnodes(oldCh, 0, oldCh.length - 1)
565 } else if (isDef(oldVnode.text)) {
566 nodeOps.setTextContent(elm, '')
567 }
568 } else if (oldVnode.text !== vnode.text) {
569 nodeOps.setTextContent(elm, vnode.text)
570 }
571 if (isDef(data)) {
572 if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
573 }
574 }
575
576 function invokeInsertHook (vnode, queue, initial) {
577 // delay insert hooks for component root nodes, invoke them after the
578 // element is really inserted
579 if (isTrue(initial) && isDef(vnode.parent)) {
580 vnode.parent.data.pendingInsert = queue
581 } else {
582 for (let i = 0; i < queue.length; ++i) {
583 queue[i].data.hook.insert(queue[i])
584 }
585 }
586 }
587
588 let hydrationBailed = false
589 // list of modules that can skip create hook during hydration because they
590 // are already rendered on the client or has no need for initialization
591 // Note: style is excluded because it relies on initial clone for future
592 // deep updates (#7063).
593 const isRenderedModule = makeMap('attrs,class,staticClass,staticStyle,key')
594
595 // Note: this is a browser-only function so we can assume elms are DOM nodes.
596 function hydrate (elm, vnode, insertedVnodeQueue, inVPre) {
597 let i
598 const { tag, data, children } = vnode
599 inVPre = inVPre || (data && data.pre)
600 vnode.elm = elm
601
602 if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {
603 vnode.isAsyncPlaceholder = true
604 return true
605 }
606 // assert node match
607 if (process.env.NODE_ENV !== 'production') {
608 if (!assertNodeMatch(elm, vnode, inVPre)) {
609 return false
610 }
611 }
612 if (isDef(data)) {
613 if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */)
614 if (isDef(i = vnode.componentInstance)) {
615 // child component. it should have hydrated its own tree.
616 initComponent(vnode, insertedVnodeQueue)
617 return true
618 }
619 }
620 if (isDef(tag)) {
621 if (isDef(children)) {
622 // empty element, allow client to pick up and populate children
623 if (!elm.hasChildNodes()) {
624 createChildren(vnode, children, insertedVnodeQueue)
625 } else {
626 // v-html and domProps: innerHTML
627 if (isDef(i = data) && isDef(i = i.domProps) && isDef(i = i.innerHTML)) {
628 if (i !== elm.innerHTML) {
629 /* istanbul ignore if */
630 if (process.env.NODE_ENV !== 'production' &&
631 typeof console !== 'undefined' &&
632 !hydrationBailed
633 ) {
634 hydrationBailed = true
635 console.warn('Parent: ', elm)
636 console.warn('server innerHTML: ', i)
637 console.warn('client innerHTML: ', elm.innerHTML)
638 }
639 return false
640 }
641 } else {
642 // iterate and compare children lists
643 let childrenMatch = true
644 let childNode = elm.firstChild
645 for (let i = 0; i < children.length; i++) {
646 if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue, inVPre)) {
647 childrenMatch = false
648 break
649 }
650 childNode = childNode.nextSibling
651 }
652 // if childNode is not null, it means the actual childNodes list is
653 // longer than the virtual children list.
654 if (!childrenMatch || childNode) {
655 /* istanbul ignore if */
656 if (process.env.NODE_ENV !== 'production' &&
657 typeof console !== 'undefined' &&
658 !hydrationBailed
659 ) {
660 hydrationBailed = true
661 console.warn('Parent: ', elm)
662 console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children)
663 }
664 return false
665 }
666 }
667 }
668 }
669 if (isDef(data)) {
670 let fullInvoke = false
671 for (const key in data) {
672 if (!isRenderedModule(key)) {
673 fullInvoke = true
674 invokeCreateHooks(vnode, insertedVnodeQueue)
675 break
676 }
677 }
678 if (!fullInvoke && data['class']) {
679 // ensure collecting deps for deep class bindings for future updates
680 traverse(data['class'])
681 }
682 }
683 } else if (elm.data !== vnode.text) {
684 elm.data = vnode.text
685 }
686 return true
687 }
688
689 function assertNodeMatch (node, vnode, inVPre) {
690 if (isDef(vnode.tag)) {
691 return vnode.tag.indexOf('vue-component') === 0 || (
692 !isUnknownElement(vnode, inVPre) &&
693 vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase())
694 )
695 } else {
696 return node.nodeType === (vnode.isComment ? 8 : 3)
697 }
698 }
699
700 return function patch (oldVnode, vnode, hydrating, removeOnly) {
701 if (isUndef(vnode)) {
702 if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
703 return
704 }
705
706 let isInitialPatch = false
707 const insertedVnodeQueue = []
708
709 if (isUndef(oldVnode)) {
710 // empty mount (likely as component), create new root element
711 isInitialPatch = true
712 createElm(vnode, insertedVnodeQueue)
713 } else {
714 const isRealElement = isDef(oldVnode.nodeType)
715 if (!isRealElement && sameVnode(oldVnode, vnode)) {
716 // patch existing root node
717 patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
718 } else {
719 if (isRealElement) {
720 // mounting to a real element
721 // check if this is server-rendered content and if we can perform
722 // a successful hydration.
723 if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
724 oldVnode.removeAttribute(SSR_ATTR)
725 hydrating = true
726 }
727 if (isTrue(hydrating)) {
728 if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
729 invokeInsertHook(vnode, insertedVnodeQueue, true)
730 return oldVnode
731 } else if (process.env.NODE_ENV !== 'production') {
732 warn(
733 'The client-side rendered virtual DOM tree is not matching ' +
734 'server-rendered content. This is likely caused by incorrect ' +
735 'HTML markup, for example nesting block-level elements inside ' +
736 '<p>, or missing <tbody>. Bailing hydration and performing ' +
737 'full client-side render.'
738 )
739 }
740 }
741 // either not server-rendered, or hydration failed.
742 // create an empty node and replace it
743 oldVnode = emptyNodeAt(oldVnode)
744 }
745
746 // replacing existing element
747 const oldElm = oldVnode.elm
748 const parentElm = nodeOps.parentNode(oldElm)
749
750 // create new node
751 createElm(
752 vnode,
753 insertedVnodeQueue,
754 // extremely rare edge case: do not insert if old element is in a
755 // leaving transition. Only happens when combining transition +
756 // keep-alive + HOCs. (#4590)
757 oldElm._leaveCb ? null : parentElm,
758 nodeOps.nextSibling(oldElm)
759 )
760
761 // update parent placeholder node element, recursively
762 if (isDef(vnode.parent)) {
763 let ancestor = vnode.parent
764 const patchable = isPatchable(vnode)
765 while (ancestor) {
766 for (let i = 0; i < cbs.destroy.length; ++i) {
767 cbs.destroy[i](ancestor)
768 }
769 ancestor.elm = vnode.elm
770 if (patchable) {
771 for (let i = 0; i < cbs.create.length; ++i) {
772 cbs.create[i](emptyNode, ancestor)
773 }
774 // #6513
775 // invoke insert hooks that may have been merged by create hooks.
776 // e.g. for directives that uses the "inserted" hook.
777 const insert = ancestor.data.hook.insert
778 if (insert.merged) {
779 // start at index 1 to avoid re-invoking component mounted hook
780 for (let i = 1; i < insert.fns.length; i++) {
781 insert.fns[i]()
782 }
783 }
784 } else {
785 registerRef(ancestor)
786 }
787 ancestor = ancestor.parent
788 }
789 }
790
791 // destroy old node
792 if (isDef(parentElm)) {
793 removeVnodes([oldVnode], 0, 0)
794 } else if (isDef(oldVnode.tag)) {
795 invokeDestroyHook(oldVnode)
796 }
797 }
798 }
799
800 invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
801 return vnode.elm
802 }
803}