9ae4fe0fa5dee952da9379dae6a47b371e6a01b7ba3307d835740bdd4a6b4216b713d0ca8044af708fe3aa9d681643f78a08cf99f7108ecaec7f25830a6130 26 KB

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