patch.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  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. import VNode from './vnode'
  13. import config from '../config'
  14. import { SSR_ATTR } from 'shared/constants'
  15. import { registerRef } from './modules/ref'
  16. import { activeInstance } from '../instance/lifecycle'
  17. import { isTextInputType } from 'web/util/element'
  18. import {
  19. warn,
  20. isDef,
  21. isUndef,
  22. isTrue,
  23. makeMap,
  24. isPrimitive
  25. } from '../util/index'
  26. export const emptyNode = new VNode('', {}, [])
  27. const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
  28. function sameVnode (a, b) {
  29. return (
  30. a.key === b.key && (
  31. (
  32. a.tag === b.tag &&
  33. a.isComment === b.isComment &&
  34. isDef(a.data) === isDef(b.data) &&
  35. sameInputType(a, b)
  36. ) || (
  37. isTrue(a.isAsyncPlaceholder) &&
  38. a.asyncFactory === b.asyncFactory &&
  39. isUndef(b.asyncFactory.error)
  40. )
  41. )
  42. )
  43. }
  44. function sameInputType (a, b) {
  45. if (a.tag !== 'input') return true
  46. let i
  47. const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
  48. const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
  49. return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
  50. }
  51. function createKeyToOldIdx (children, beginIdx, endIdx) {
  52. let i, key
  53. const map = {}
  54. for (i = beginIdx; i <= endIdx; ++i) {
  55. key = children[i].key
  56. if (isDef(key)) map[key] = i
  57. }
  58. return map
  59. }
  60. export function createPatchFunction (backend) {
  61. let i, j
  62. const cbs = {}
  63. const { modules, nodeOps } = backend
  64. for (i = 0; i < hooks.length; ++i) {
  65. cbs[hooks[i]] = []
  66. for (j = 0; j < modules.length; ++j) {
  67. if (isDef(modules[j][hooks[i]])) {
  68. cbs[hooks[i]].push(modules[j][hooks[i]])
  69. }
  70. }
  71. }
  72. function emptyNodeAt (elm) {
  73. return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
  74. }
  75. function createRmCb (childElm, listeners) {
  76. function remove () {
  77. if (--remove.listeners === 0) {
  78. removeNode(childElm)
  79. }
  80. }
  81. remove.listeners = listeners
  82. return remove
  83. }
  84. function removeNode (el) {
  85. const parent = nodeOps.parentNode(el)
  86. // element may have already been removed due to v-html / v-text
  87. if (isDef(parent)) {
  88. nodeOps.removeChild(parent, el)
  89. }
  90. }
  91. let inPre = 0
  92. function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) {
  93. vnode.isRootInsert = !nested // for transition enter check
  94. if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  95. return
  96. }
  97. const data = vnode.data
  98. const children = vnode.children
  99. const tag = vnode.tag
  100. if (isDef(tag)) {
  101. if (process.env.NODE_ENV !== 'production') {
  102. if (data && data.pre) {
  103. inPre++
  104. }
  105. if (
  106. !inPre &&
  107. !vnode.ns &&
  108. !(config.ignoredElements.length && config.ignoredElements.indexOf(tag) > -1) &&
  109. config.isUnknownElement(tag)
  110. ) {
  111. warn(
  112. 'Unknown custom element: <' + tag + '> - did you ' +
  113. 'register the component correctly? For recursive components, ' +
  114. 'make sure to provide the "name" option.',
  115. vnode.context
  116. )
  117. }
  118. }
  119. vnode.elm = vnode.ns
  120. ? nodeOps.createElementNS(vnode.ns, tag)
  121. : nodeOps.createElement(tag, vnode)
  122. setScope(vnode)
  123. /* istanbul ignore if */
  124. if (__WEEX__) {
  125. // in Weex, the default insertion order is parent-first.
  126. // List items can be optimized to use children-first insertion
  127. // with append="tree".
  128. const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
  129. if (!appendAsTree) {
  130. if (isDef(data)) {
  131. invokeCreateHooks(vnode, insertedVnodeQueue)
  132. }
  133. insert(parentElm, vnode.elm, refElm)
  134. }
  135. createChildren(vnode, children, insertedVnodeQueue)
  136. if (appendAsTree) {
  137. if (isDef(data)) {
  138. invokeCreateHooks(vnode, insertedVnodeQueue)
  139. }
  140. insert(parentElm, vnode.elm, refElm)
  141. }
  142. } else {
  143. createChildren(vnode, children, insertedVnodeQueue)
  144. if (isDef(data)) {
  145. invokeCreateHooks(vnode, insertedVnodeQueue)
  146. }
  147. insert(parentElm, vnode.elm, refElm)
  148. }
  149. if (process.env.NODE_ENV !== 'production' && data && data.pre) {
  150. inPre--
  151. }
  152. } else if (isTrue(vnode.isComment)) {
  153. vnode.elm = nodeOps.createComment(vnode.text)
  154. insert(parentElm, vnode.elm, refElm)
  155. } else {
  156. vnode.elm = nodeOps.createTextNode(vnode.text)
  157. insert(parentElm, vnode.elm, refElm)
  158. }
  159. }
  160. function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  161. let i = vnode.data
  162. if (isDef(i)) {
  163. const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
  164. if (isDef(i = i.hook) && isDef(i = i.init)) {
  165. i(vnode, false /* hydrating */, parentElm, refElm)
  166. }
  167. // after calling the init hook, if the vnode is a child component
  168. // it should've created a child instance and mounted it. the child
  169. // component also has set the placeholder vnode's elm.
  170. // in that case we can just return the element and be done.
  171. if (isDef(vnode.componentInstance)) {
  172. initComponent(vnode, insertedVnodeQueue)
  173. if (isTrue(isReactivated)) {
  174. reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
  175. }
  176. return true
  177. }
  178. }
  179. }
  180. function initComponent (vnode, insertedVnodeQueue) {
  181. if (isDef(vnode.data.pendingInsert)) {
  182. insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
  183. vnode.data.pendingInsert = null
  184. }
  185. vnode.elm = vnode.componentInstance.$el
  186. if (isPatchable(vnode)) {
  187. invokeCreateHooks(vnode, insertedVnodeQueue)
  188. setScope(vnode)
  189. } else {
  190. // empty component root.
  191. // skip all element-related modules except for ref (#3455)
  192. registerRef(vnode)
  193. // make sure to invoke the insert hook
  194. insertedVnodeQueue.push(vnode)
  195. }
  196. }
  197. function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  198. let i
  199. // hack for #4339: a reactivated component with inner transition
  200. // does not trigger because the inner node's created hooks are not called
  201. // again. It's not ideal to involve module-specific logic in here but
  202. // there doesn't seem to be a better way to do it.
  203. let innerNode = vnode
  204. while (innerNode.componentInstance) {
  205. innerNode = innerNode.componentInstance._vnode
  206. if (isDef(i = innerNode.data) && isDef(i = i.transition)) {
  207. for (i = 0; i < cbs.activate.length; ++i) {
  208. cbs.activate[i](emptyNode, innerNode)
  209. }
  210. insertedVnodeQueue.push(innerNode)
  211. break
  212. }
  213. }
  214. // unlike a newly created component,
  215. // a reactivated keep-alive component doesn't insert itself
  216. insert(parentElm, vnode.elm, refElm)
  217. }
  218. function insert (parent, elm, ref) {
  219. if (isDef(parent)) {
  220. if (isDef(ref)) {
  221. if (ref.parentNode === parent) {
  222. nodeOps.insertBefore(parent, elm, ref)
  223. }
  224. } else {
  225. nodeOps.appendChild(parent, elm)
  226. }
  227. }
  228. }
  229. function createChildren (vnode, children, insertedVnodeQueue) {
  230. if (Array.isArray(children)) {
  231. for (let i = 0; i < children.length; ++i) {
  232. createElm(children[i], insertedVnodeQueue, vnode.elm, null, true)
  233. }
  234. } else if (isPrimitive(vnode.text)) {
  235. nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text))
  236. }
  237. }
  238. function isPatchable (vnode) {
  239. while (vnode.componentInstance) {
  240. vnode = vnode.componentInstance._vnode
  241. }
  242. return isDef(vnode.tag)
  243. }
  244. function invokeCreateHooks (vnode, insertedVnodeQueue) {
  245. for (let i = 0; i < cbs.create.length; ++i) {
  246. cbs.create[i](emptyNode, vnode)
  247. }
  248. i = vnode.data.hook // Reuse variable
  249. if (isDef(i)) {
  250. if (isDef(i.create)) i.create(emptyNode, vnode)
  251. if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
  252. }
  253. }
  254. // set scope id attribute for scoped CSS.
  255. // this is implemented as a special case to avoid the overhead
  256. // of going through the normal attribute patching process.
  257. function setScope (vnode) {
  258. let i
  259. let ancestor = vnode
  260. while (ancestor) {
  261. if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {
  262. nodeOps.setAttribute(vnode.elm, i, '')
  263. }
  264. ancestor = ancestor.parent
  265. }
  266. // for slot content they should also get the scopeId from the host instance.
  267. if (isDef(i = activeInstance) &&
  268. i !== vnode.context &&
  269. isDef(i = i.$options._scopeId)
  270. ) {
  271. nodeOps.setAttribute(vnode.elm, i, '')
  272. }
  273. }
  274. function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {
  275. for (; startIdx <= endIdx; ++startIdx) {
  276. createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm)
  277. }
  278. }
  279. function invokeDestroyHook (vnode) {
  280. let i, j
  281. const data = vnode.data
  282. if (isDef(data)) {
  283. if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
  284. for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
  285. }
  286. if (isDef(i = vnode.children)) {
  287. for (j = 0; j < vnode.children.length; ++j) {
  288. invokeDestroyHook(vnode.children[j])
  289. }
  290. }
  291. }
  292. function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
  293. for (; startIdx <= endIdx; ++startIdx) {
  294. const ch = vnodes[startIdx]
  295. if (isDef(ch)) {
  296. if (isDef(ch.tag)) {
  297. removeAndInvokeRemoveHook(ch)
  298. invokeDestroyHook(ch)
  299. } else { // Text node
  300. removeNode(ch.elm)
  301. }
  302. }
  303. }
  304. }
  305. function removeAndInvokeRemoveHook (vnode, rm) {
  306. if (isDef(rm) || isDef(vnode.data)) {
  307. let i
  308. const listeners = cbs.remove.length + 1
  309. if (isDef(rm)) {
  310. // we have a recursively passed down rm callback
  311. // increase the listeners count
  312. rm.listeners += listeners
  313. } else {
  314. // directly removing
  315. rm = createRmCb(vnode.elm, listeners)
  316. }
  317. // recursively invoke hooks on child component root node
  318. if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {
  319. removeAndInvokeRemoveHook(i, rm)
  320. }
  321. for (i = 0; i < cbs.remove.length; ++i) {
  322. cbs.remove[i](vnode, rm)
  323. }
  324. if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
  325. i(vnode, rm)
  326. } else {
  327. rm()
  328. }
  329. } else {
  330. removeNode(vnode.elm)
  331. }
  332. }
  333. function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
  334. let oldStartIdx = 0
  335. let newStartIdx = 0
  336. let oldEndIdx = oldCh.length - 1
  337. let oldStartVnode = oldCh[0]
  338. let oldEndVnode = oldCh[oldEndIdx]
  339. let newEndIdx = newCh.length - 1
  340. let newStartVnode = newCh[0]
  341. let newEndVnode = newCh[newEndIdx]
  342. let oldKeyToIdx, idxInOld, elmToMove, refElm
  343. // removeOnly is a special flag used only by <transition-group>
  344. // to ensure removed elements stay in correct relative positions
  345. // during leaving transitions
  346. const canMove = !removeOnly
  347. while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
  348. if (isUndef(oldStartVnode)) {
  349. oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
  350. } else if (isUndef(oldEndVnode)) {
  351. oldEndVnode = oldCh[--oldEndIdx]
  352. } else if (sameVnode(oldStartVnode, newStartVnode)) {
  353. patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
  354. oldStartVnode = oldCh[++oldStartIdx]
  355. newStartVnode = newCh[++newStartIdx]
  356. } else if (sameVnode(oldEndVnode, newEndVnode)) {
  357. patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
  358. oldEndVnode = oldCh[--oldEndIdx]
  359. newEndVnode = newCh[--newEndIdx]
  360. } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
  361. patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
  362. canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
  363. oldStartVnode = oldCh[++oldStartIdx]
  364. newEndVnode = newCh[--newEndIdx]
  365. } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
  366. patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
  367. canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
  368. oldEndVnode = oldCh[--oldEndIdx]
  369. newStartVnode = newCh[++newStartIdx]
  370. } else {
  371. if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
  372. idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
  373. if (isUndef(idxInOld)) { // New element
  374. createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
  375. newStartVnode = newCh[++newStartIdx]
  376. } else {
  377. elmToMove = oldCh[idxInOld]
  378. /* istanbul ignore if */
  379. if (process.env.NODE_ENV !== 'production' && !elmToMove) {
  380. warn(
  381. 'It seems there are duplicate keys that is causing an update error. ' +
  382. 'Make sure each v-for item has a unique key.'
  383. )
  384. }
  385. if (sameVnode(elmToMove, newStartVnode)) {
  386. patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
  387. oldCh[idxInOld] = undefined
  388. canMove && nodeOps.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm)
  389. newStartVnode = newCh[++newStartIdx]
  390. } else {
  391. // same key but different element. treat as new element
  392. createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
  393. newStartVnode = newCh[++newStartIdx]
  394. }
  395. }
  396. }
  397. }
  398. if (oldStartIdx > oldEndIdx) {
  399. refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
  400. addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  401. } else if (newStartIdx > newEndIdx) {
  402. removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  403. }
  404. }
  405. function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
  406. if (oldVnode === vnode) {
  407. return
  408. }
  409. const elm = vnode.elm = oldVnode.elm
  410. if (isTrue(oldVnode.isAsyncPlaceholder)) {
  411. if (isDef(vnode.asyncFactory.resolved)) {
  412. hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
  413. } else {
  414. vnode.isAsyncPlaceholder = true
  415. }
  416. return
  417. }
  418. // reuse element for static trees.
  419. // note we only do this if the vnode is cloned -
  420. // if the new node is not cloned it means the render functions have been
  421. // reset by the hot-reload-api and we need to do a proper re-render.
  422. if (isTrue(vnode.isStatic) &&
  423. isTrue(oldVnode.isStatic) &&
  424. vnode.key === oldVnode.key &&
  425. (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
  426. ) {
  427. vnode.componentInstance = oldVnode.componentInstance
  428. return
  429. }
  430. let i
  431. const data = vnode.data
  432. if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
  433. i(oldVnode, vnode)
  434. }
  435. const oldCh = oldVnode.children
  436. const ch = vnode.children
  437. if (isDef(data) && isPatchable(vnode)) {
  438. for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
  439. if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
  440. }
  441. if (isUndef(vnode.text)) {
  442. if (isDef(oldCh) && isDef(ch)) {
  443. if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
  444. } else if (isDef(ch)) {
  445. if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
  446. addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
  447. } else if (isDef(oldCh)) {
  448. removeVnodes(elm, oldCh, 0, oldCh.length - 1)
  449. } else if (isDef(oldVnode.text)) {
  450. nodeOps.setTextContent(elm, '')
  451. }
  452. } else if (oldVnode.text !== vnode.text) {
  453. nodeOps.setTextContent(elm, vnode.text)
  454. }
  455. if (isDef(data)) {
  456. if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
  457. }
  458. }
  459. function invokeInsertHook (vnode, queue, initial) {
  460. // delay insert hooks for component root nodes, invoke them after the
  461. // element is really inserted
  462. if (isTrue(initial) && isDef(vnode.parent)) {
  463. vnode.parent.data.pendingInsert = queue
  464. } else {
  465. for (let i = 0; i < queue.length; ++i) {
  466. queue[i].data.hook.insert(queue[i])
  467. }
  468. }
  469. }
  470. let bailed = false
  471. // list of modules that can skip create hook during hydration because they
  472. // are already rendered on the client or has no need for initialization
  473. const isRenderedModule = makeMap('attrs,style,class,staticClass,staticStyle,key')
  474. // Note: this is a browser-only function so we can assume elms are DOM nodes.
  475. function hydrate (elm, vnode, insertedVnodeQueue) {
  476. if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {
  477. vnode.elm = elm
  478. vnode.isAsyncPlaceholder = true
  479. return true
  480. }
  481. if (process.env.NODE_ENV !== 'production') {
  482. if (!assertNodeMatch(elm, vnode)) {
  483. return false
  484. }
  485. }
  486. vnode.elm = elm
  487. const { tag, data, children } = vnode
  488. if (isDef(data)) {
  489. if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */)
  490. if (isDef(i = vnode.componentInstance)) {
  491. // child component. it should have hydrated its own tree.
  492. initComponent(vnode, insertedVnodeQueue)
  493. return true
  494. }
  495. }
  496. if (isDef(tag)) {
  497. if (isDef(children)) {
  498. // empty element, allow client to pick up and populate children
  499. if (!elm.hasChildNodes()) {
  500. createChildren(vnode, children, insertedVnodeQueue)
  501. } else {
  502. let childrenMatch = true
  503. let childNode = elm.firstChild
  504. for (let i = 0; i < children.length; i++) {
  505. if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue)) {
  506. childrenMatch = false
  507. break
  508. }
  509. childNode = childNode.nextSibling
  510. }
  511. // if childNode is not null, it means the actual childNodes list is
  512. // longer than the virtual children list.
  513. if (!childrenMatch || childNode) {
  514. if (process.env.NODE_ENV !== 'production' &&
  515. typeof console !== 'undefined' &&
  516. !bailed
  517. ) {
  518. bailed = true
  519. console.warn('Parent: ', elm)
  520. console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children)
  521. }
  522. return false
  523. }
  524. }
  525. }
  526. if (isDef(data)) {
  527. for (const key in data) {
  528. if (!isRenderedModule(key)) {
  529. invokeCreateHooks(vnode, insertedVnodeQueue)
  530. break
  531. }
  532. }
  533. }
  534. } else if (elm.data !== vnode.text) {
  535. elm.data = vnode.text
  536. }
  537. return true
  538. }
  539. function assertNodeMatch (node, vnode) {
  540. if (isDef(vnode.tag)) {
  541. return (
  542. vnode.tag.indexOf('vue-component') === 0 ||
  543. vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase())
  544. )
  545. } else {
  546. return node.nodeType === (vnode.isComment ? 8 : 3)
  547. }
  548. }
  549. return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
  550. if (isUndef(vnode)) {
  551. if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
  552. return
  553. }
  554. let isInitialPatch = false
  555. const insertedVnodeQueue = []
  556. if (isUndef(oldVnode)) {
  557. // empty mount (likely as component), create new root element
  558. isInitialPatch = true
  559. createElm(vnode, insertedVnodeQueue, parentElm, refElm)
  560. } else {
  561. const isRealElement = isDef(oldVnode.nodeType)
  562. if (!isRealElement && sameVnode(oldVnode, vnode)) {
  563. // patch existing root node
  564. patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
  565. } else {
  566. if (isRealElement) {
  567. // mounting to a real element
  568. // check if this is server-rendered content and if we can perform
  569. // a successful hydration.
  570. if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
  571. oldVnode.removeAttribute(SSR_ATTR)
  572. hydrating = true
  573. }
  574. if (isTrue(hydrating)) {
  575. if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
  576. invokeInsertHook(vnode, insertedVnodeQueue, true)
  577. return oldVnode
  578. } else if (process.env.NODE_ENV !== 'production') {
  579. warn(
  580. 'The client-side rendered virtual DOM tree is not matching ' +
  581. 'server-rendered content. This is likely caused by incorrect ' +
  582. 'HTML markup, for example nesting block-level elements inside ' +
  583. '<p>, or missing <tbody>. Bailing hydration and performing ' +
  584. 'full client-side render.'
  585. )
  586. }
  587. }
  588. // either not server-rendered, or hydration failed.
  589. // create an empty node and replace it
  590. oldVnode = emptyNodeAt(oldVnode)
  591. }
  592. // replacing existing element
  593. const oldElm = oldVnode.elm
  594. const parentElm = nodeOps.parentNode(oldElm)
  595. createElm(
  596. vnode,
  597. insertedVnodeQueue,
  598. // extremely rare edge case: do not insert if old element is in a
  599. // leaving transition. Only happens when combining transition +
  600. // keep-alive + HOCs. (#4590)
  601. oldElm._leaveCb ? null : parentElm,
  602. nodeOps.nextSibling(oldElm)
  603. )
  604. if (isDef(vnode.parent)) {
  605. // component root element replaced.
  606. // update parent placeholder node element, recursively
  607. let ancestor = vnode.parent
  608. while (ancestor) {
  609. ancestor.elm = vnode.elm
  610. ancestor = ancestor.parent
  611. }
  612. if (isPatchable(vnode)) {
  613. for (let i = 0; i < cbs.create.length; ++i) {
  614. cbs.create[i](emptyNode, vnode.parent)
  615. }
  616. }
  617. }
  618. if (isDef(parentElm)) {
  619. removeVnodes(parentElm, [oldVnode], 0, 0)
  620. } else if (isDef(oldVnode.tag)) {
  621. invokeDestroyHook(oldVnode)
  622. }
  623. }
  624. }
  625. invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
  626. return vnode.elm
  627. }
  628. }