| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- /**
- * Virtual DOM patching algorithm based on Snabbdom by
- * Simon Friis Vindum (@paldepind)
- * Licensed under the MIT License
- * https://github.com/paldepind/snabbdom/blob/master/LICENSE
- *
- * modified by Evan You (@yyx990803)
- *
- /*
- * Not type-checking this because this file is perf-critical and the cost
- * of making flow understand it is not worth it.
- */
- import config from '../config'
- import VNode from './vnode'
- import { isPrimitive, _toString, warn } from '../util/index'
- import { activeInstance } from '../instance/lifecycle'
- import { registerRef } from './modules/ref'
- export const emptyNode = new VNode('', {}, [])
- const hooks = ['create', 'update', 'remove', 'destroy']
- function isUndef (s) {
- return s == null
- }
- function isDef (s) {
- return s != null
- }
- function sameVnode (vnode1, vnode2) {
- return (
- vnode1.key === vnode2.key &&
- vnode1.tag === vnode2.tag &&
- vnode1.isComment === vnode2.isComment &&
- !vnode1.data === !vnode2.data
- )
- }
- function createKeyToOldIdx (children, beginIdx, endIdx) {
- let i, key
- const map = {}
- for (i = beginIdx; i <= endIdx; ++i) {
- key = children[i].key
- if (isDef(key)) map[key] = i
- }
- return map
- }
- export function createPatchFunction (backend) {
- let i, j
- const cbs = {}
- const { modules, nodeOps } = backend
- for (i = 0; i < hooks.length; ++i) {
- cbs[hooks[i]] = []
- for (j = 0; j < modules.length; ++j) {
- if (modules[j][hooks[i]] !== undefined) cbs[hooks[i]].push(modules[j][hooks[i]])
- }
- }
- function emptyNodeAt (elm) {
- return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
- }
- function createRmCb (childElm, listeners) {
- function remove () {
- if (--remove.listeners === 0) {
- removeElement(childElm)
- }
- }
- remove.listeners = listeners
- return remove
- }
- function removeElement (el) {
- const parent = nodeOps.parentNode(el)
- // element may have already been removed due to v-html
- if (parent) {
- nodeOps.removeChild(parent, el)
- }
- }
- let inPre = 0
- function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) {
- let i, isReactivated
- const data = vnode.data
- vnode.isRootInsert = !nested
- if (isDef(data)) {
- if (isDef(i = data.hook) && isDef(i = i.init)) {
- isReactivated = i(vnode, false /* hydrating */, parentElm, refElm)
- }
- // after calling the init hook, if the vnode is a child component
- // it should've created a child instance and mounted it. the child
- // component also has set the placeholder vnode's elm.
- // in that case we can just return the element and be done.
- if (isDef(i = vnode.child)) {
- initComponent(vnode, insertedVnodeQueue)
- if (isReactivated) {
- // unlike a newly created component,
- // a reactivated keep-alive component doesn't insert itself
- insert(parentElm, vnode.child.$el, refElm)
- }
- return
- }
- }
- const children = vnode.children
- const tag = vnode.tag
- if (isDef(tag)) {
- if (process.env.NODE_ENV !== 'production') {
- if (data && data.pre) {
- inPre++
- }
- if (
- !inPre &&
- !vnode.ns &&
- !(config.ignoredElements && config.ignoredElements.indexOf(tag) > -1) &&
- config.isUnknownElement(tag)
- ) {
- warn(
- 'Unknown custom element: <' + tag + '> - did you ' +
- 'register the component correctly? For recursive components, ' +
- 'make sure to provide the "name" option.',
- vnode.context
- )
- }
- }
- vnode.elm = vnode.ns
- ? nodeOps.createElementNS(vnode.ns, tag)
- : nodeOps.createElement(tag, vnode)
- setScope(vnode)
- /* istanbul ignore if */
- if (__WEEX__) {
- // in Weex, the default insertion order is parent-first.
- // List items can be optimized to use children-first insertion
- // with append="tree".
- const appendAsTree = data && data.appendAsTree
- if (!appendAsTree) {
- if (isDef(data)) {
- invokeCreateHooks(vnode, insertedVnodeQueue)
- }
- insert(parentElm, vnode.elm, refElm)
- }
- createChildren(vnode, children, insertedVnodeQueue)
- if (appendAsTree) {
- if (isDef(data)) {
- invokeCreateHooks(vnode, insertedVnodeQueue)
- }
- insert(parentElm, vnode.elm, refElm)
- }
- } else {
- createChildren(vnode, children, insertedVnodeQueue)
- if (isDef(data)) {
- invokeCreateHooks(vnode, insertedVnodeQueue)
- }
- insert(parentElm, vnode.elm, refElm)
- }
- if (process.env.NODE_ENV !== 'production' && data && data.pre) {
- inPre--
- }
- } else if (vnode.isComment) {
- vnode.elm = nodeOps.createComment(vnode.text)
- insert(parentElm, vnode.elm, refElm)
- } else {
- vnode.elm = nodeOps.createTextNode(vnode.text)
- insert(parentElm, vnode.elm, refElm)
- }
- }
- function insert (parent, elm, ref) {
- if (parent) {
- nodeOps.insertBefore(parent, elm, ref)
- }
- }
- function createChildren (vnode, children, insertedVnodeQueue) {
- if (Array.isArray(children)) {
- for (let i = 0; i < children.length; ++i) {
- createElm(children[i], insertedVnodeQueue, vnode.elm, null, true)
- }
- } else if (isPrimitive(vnode.text)) {
- nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text))
- }
- }
- function isPatchable (vnode) {
- while (vnode.child) {
- vnode = vnode.child._vnode
- }
- return isDef(vnode.tag)
- }
- function invokeCreateHooks (vnode, insertedVnodeQueue) {
- for (let i = 0; i < cbs.create.length; ++i) {
- cbs.create[i](emptyNode, vnode)
- }
- i = vnode.data.hook // Reuse variable
- if (isDef(i)) {
- if (i.create) i.create(emptyNode, vnode)
- if (i.insert) insertedVnodeQueue.push(vnode)
- }
- }
- function initComponent (vnode, insertedVnodeQueue) {
- if (vnode.data.pendingInsert) {
- insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
- }
- vnode.elm = vnode.child.$el
- if (isPatchable(vnode)) {
- invokeCreateHooks(vnode, insertedVnodeQueue)
- setScope(vnode)
- } else {
- // empty component root.
- // skip all element-related modules except for ref (#3455)
- registerRef(vnode)
- // make sure to invoke the insert hook
- insertedVnodeQueue.push(vnode)
- }
- }
- // set scope id attribute for scoped CSS.
- // this is implemented as a special case to avoid the overhead
- // of going through the normal attribute patching process.
- function setScope (vnode) {
- let i
- if (isDef(i = vnode.context) && isDef(i = i.$options._scopeId)) {
- nodeOps.setAttribute(vnode.elm, i, '')
- }
- if (isDef(i = activeInstance) &&
- i !== vnode.context &&
- isDef(i = i.$options._scopeId)) {
- nodeOps.setAttribute(vnode.elm, i, '')
- }
- }
- function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {
- for (; startIdx <= endIdx; ++startIdx) {
- createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm)
- }
- }
- function invokeDestroyHook (vnode) {
- let i, j
- const data = vnode.data
- if (isDef(data)) {
- if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
- for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
- }
- if (isDef(i = vnode.children)) {
- for (j = 0; j < vnode.children.length; ++j) {
- invokeDestroyHook(vnode.children[j])
- }
- }
- }
- function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
- for (; startIdx <= endIdx; ++startIdx) {
- const ch = vnodes[startIdx]
- if (isDef(ch)) {
- if (isDef(ch.tag)) {
- removeAndInvokeRemoveHook(ch)
- invokeDestroyHook(ch)
- } else { // Text node
- nodeOps.removeChild(parentElm, ch.elm)
- }
- }
- }
- }
- function removeAndInvokeRemoveHook (vnode, rm) {
- if (rm || isDef(vnode.data)) {
- const listeners = cbs.remove.length + 1
- if (!rm) {
- // directly removing
- rm = createRmCb(vnode.elm, listeners)
- } else {
- // we have a recursively passed down rm callback
- // increase the listeners count
- rm.listeners += listeners
- }
- // recursively invoke hooks on child component root node
- if (isDef(i = vnode.child) && isDef(i = i._vnode) && isDef(i.data)) {
- removeAndInvokeRemoveHook(i, rm)
- }
- for (i = 0; i < cbs.remove.length; ++i) {
- cbs.remove[i](vnode, rm)
- }
- if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
- i(vnode, rm)
- } else {
- rm()
- }
- } else {
- removeElement(vnode.elm)
- }
- }
- function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
- let oldStartIdx = 0
- let newStartIdx = 0
- let oldEndIdx = oldCh.length - 1
- let oldStartVnode = oldCh[0]
- let oldEndVnode = oldCh[oldEndIdx]
- let newEndIdx = newCh.length - 1
- let newStartVnode = newCh[0]
- let newEndVnode = newCh[newEndIdx]
- let oldKeyToIdx, idxInOld, elmToMove, refElm
- // removeOnly is a special flag used only by <transition-group>
- // to ensure removed elements stay in correct relative positions
- // during leaving transitions
- const canMove = !removeOnly
- while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
- if (isUndef(oldStartVnode)) {
- oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
- } else if (isUndef(oldEndVnode)) {
- oldEndVnode = oldCh[--oldEndIdx]
- } else if (sameVnode(oldStartVnode, newStartVnode)) {
- patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
- oldStartVnode = oldCh[++oldStartIdx]
- newStartVnode = newCh[++newStartIdx]
- } else if (sameVnode(oldEndVnode, newEndVnode)) {
- patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
- oldEndVnode = oldCh[--oldEndIdx]
- newEndVnode = newCh[--newEndIdx]
- } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
- patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
- canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
- oldStartVnode = oldCh[++oldStartIdx]
- newEndVnode = newCh[--newEndIdx]
- } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
- patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
- canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
- oldEndVnode = oldCh[--oldEndIdx]
- newStartVnode = newCh[++newStartIdx]
- } else {
- if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
- idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
- if (isUndef(idxInOld)) { // New element
- createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
- newStartVnode = newCh[++newStartIdx]
- } else {
- elmToMove = oldCh[idxInOld]
- /* istanbul ignore if */
- if (process.env.NODE_ENV !== 'production' && !elmToMove) {
- warn(
- 'It seems there are duplicate keys that is causing an update error. ' +
- 'Make sure each v-for item has a unique key.'
- )
- }
- if (elmToMove.tag !== newStartVnode.tag) {
- // same key but different element. treat as new element
- createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
- newStartVnode = newCh[++newStartIdx]
- } else {
- patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
- oldCh[idxInOld] = undefined
- canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
- newStartVnode = newCh[++newStartIdx]
- }
- }
- }
- }
- if (oldStartIdx > oldEndIdx) {
- refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
- addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
- } else if (newStartIdx > newEndIdx) {
- removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
- }
- }
- function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
- if (oldVnode === vnode) {
- return
- }
- // reuse element for static trees.
- // note we only do this if the vnode is cloned -
- // if the new node is not cloned it means the render functions have been
- // reset by the hot-reload-api and we need to do a proper re-render.
- if (vnode.isStatic &&
- oldVnode.isStatic &&
- vnode.key === oldVnode.key &&
- (vnode.isCloned || vnode.isOnce)) {
- vnode.elm = oldVnode.elm
- vnode.child = oldVnode.child
- return
- }
- let i
- const data = vnode.data
- const hasData = isDef(data)
- if (hasData && isDef(i = data.hook) && isDef(i = i.prepatch)) {
- i(oldVnode, vnode)
- }
- const elm = vnode.elm = oldVnode.elm
- const oldCh = oldVnode.children
- const ch = vnode.children
- if (hasData && isPatchable(vnode)) {
- for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
- if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
- }
- if (isUndef(vnode.text)) {
- if (isDef(oldCh) && isDef(ch)) {
- if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
- } else if (isDef(ch)) {
- if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
- addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
- } else if (isDef(oldCh)) {
- removeVnodes(elm, oldCh, 0, oldCh.length - 1)
- } else if (isDef(oldVnode.text)) {
- nodeOps.setTextContent(elm, '')
- }
- } else if (oldVnode.text !== vnode.text) {
- nodeOps.setTextContent(elm, vnode.text)
- }
- if (hasData) {
- if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
- }
- }
- function invokeInsertHook (vnode, queue, initial) {
- // delay insert hooks for component root nodes, invoke them after the
- // element is really inserted
- if (initial && vnode.parent) {
- vnode.parent.data.pendingInsert = queue
- } else {
- for (let i = 0; i < queue.length; ++i) {
- queue[i].data.hook.insert(queue[i])
- }
- }
- }
- let bailed = false
- function hydrate (elm, vnode, insertedVnodeQueue) {
- if (process.env.NODE_ENV !== 'production') {
- if (!assertNodeMatch(elm, vnode)) {
- return false
- }
- }
- vnode.elm = elm
- const { tag, data, children } = vnode
- if (isDef(data)) {
- if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */)
- if (isDef(i = vnode.child)) {
- // child component. it should have hydrated its own tree.
- initComponent(vnode, insertedVnodeQueue)
- return true
- }
- }
- if (isDef(tag)) {
- if (isDef(children)) {
- const childNodes = nodeOps.childNodes(elm)
- // empty element, allow client to pick up and populate children
- if (!childNodes.length) {
- createChildren(vnode, children, insertedVnodeQueue)
- } else {
- let childrenMatch = true
- if (childNodes.length !== children.length) {
- childrenMatch = false
- } else {
- for (let i = 0; i < children.length; i++) {
- if (!hydrate(childNodes[i], children[i], insertedVnodeQueue)) {
- childrenMatch = false
- break
- }
- }
- }
- if (!childrenMatch) {
- if (process.env.NODE_ENV !== 'production' &&
- typeof console !== 'undefined' &&
- !bailed) {
- bailed = true
- console.warn('Parent: ', elm)
- console.warn('Mismatching childNodes vs. VNodes: ', childNodes, children)
- }
- return false
- }
- }
- }
- if (isDef(data)) {
- invokeCreateHooks(vnode, insertedVnodeQueue)
- }
- }
- return true
- }
- function assertNodeMatch (node, vnode) {
- if (vnode.tag) {
- return (
- vnode.tag.indexOf('vue-component') === 0 ||
- vnode.tag.toLowerCase() === nodeOps.tagName(node).toLowerCase()
- )
- } else {
- return _toString(vnode.text) === node.data
- }
- }
- return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
- if (!vnode) {
- if (oldVnode) invokeDestroyHook(oldVnode)
- return
- }
- let elm, parent
- let isInitialPatch = false
- const insertedVnodeQueue = []
- if (!oldVnode) {
- // empty mount (likely as component), create new root element
- isInitialPatch = true
- createElm(vnode, insertedVnodeQueue, parentElm, refElm)
- } else {
- const isRealElement = isDef(oldVnode.nodeType)
- if (!isRealElement && sameVnode(oldVnode, vnode)) {
- // patch existing root node
- patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
- } else {
- if (isRealElement) {
- // mounting to a real element
- // check if this is server-rendered content and if we can perform
- // a successful hydration.
- if (oldVnode.nodeType === 1 && oldVnode.hasAttribute('server-rendered')) {
- oldVnode.removeAttribute('server-rendered')
- hydrating = true
- }
- if (hydrating) {
- if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
- invokeInsertHook(vnode, insertedVnodeQueue, true)
- return oldVnode
- } else if (process.env.NODE_ENV !== 'production') {
- warn(
- 'The client-side rendered virtual DOM tree is not matching ' +
- 'server-rendered content. This is likely caused by incorrect ' +
- 'HTML markup, for example nesting block-level elements inside ' +
- '<p>, or missing <tbody>. Bailing hydration and performing ' +
- 'full client-side render.'
- )
- }
- }
- // either not server-rendered, or hydration failed.
- // create an empty node and replace it
- oldVnode = emptyNodeAt(oldVnode)
- }
- // replacing existing element
- elm = oldVnode.elm
- parent = nodeOps.parentNode(elm)
- createElm(vnode, insertedVnodeQueue, parent, nodeOps.nextSibling(elm))
- if (vnode.parent) {
- // component root element replaced.
- // update parent placeholder node element, recursively
- let ancestor = vnode.parent
- while (ancestor) {
- ancestor.elm = vnode.elm
- ancestor = ancestor.parent
- }
- if (isPatchable(vnode)) {
- for (let i = 0; i < cbs.create.length; ++i) {
- cbs.create[i](emptyNode, vnode.parent)
- }
- }
- }
- if (parent !== null) {
- removeVnodes(parent, [oldVnode], 0, 0)
- } else if (isDef(oldVnode.tag)) {
- invokeDestroyHook(oldVnode)
- }
- }
- }
- invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
- return vnode.elm
- }
- }
|