patch.js 20 KB

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