Bundled output for commit 285cb5382f [skip ci]
This commit is contained in:
parent
285cb5382f
commit
277f35fb3a
3 changed files with 251 additions and 145 deletions
302
mithril.js
302
mithril.js
|
|
@ -1,7 +1,7 @@
|
|||
;(function() {
|
||||
"use strict"
|
||||
function Vnode(tag, key, attrs0, children, text, dom) {
|
||||
return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: undefined, events: undefined, instance: undefined, skip: false}
|
||||
return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: undefined, events: undefined, instance: undefined}
|
||||
}
|
||||
Vnode.normalize = function(node) {
|
||||
if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined)
|
||||
|
|
@ -589,38 +589,47 @@ var coreRenderer = function($window) {
|
|||
// are visited in the fourth part of the diff and in the `removeNodes` loop.
|
||||
// ## Diffing
|
||||
//
|
||||
// If one list is keyed and the other is unkeyed, the old is removed, and the new one is
|
||||
// inserted (since the keys are guaranteed to differ).
|
||||
// Reading https://github.com/localvoid/ivi/blob/ddc09d06abaef45248e6133f7040d00d3c6be853/packages/ivi/src/vdom/implementation.ts#L617-L837
|
||||
// may be good for context on longest increasing subsequence-based logic for moving nodes.
|
||||
//
|
||||
// Then comes the unkeyed diff algo, and at last0, the keyed diff algorithm that is split
|
||||
// in four parts (simplifying a bit).
|
||||
// In order to diff keyed lists, one has to
|
||||
//
|
||||
// The first part goes through both lists top-down as long as the nodes at each level have
|
||||
// the same key2.
|
||||
// 1) match1 nodes in both lists, per key2, and update them accordingly
|
||||
// 2) create the nodes present in the new list, but absent in the old one
|
||||
// 3) remove the nodes present in the old list, but absent in the new one
|
||||
// 4) figure out what nodes in 1) to move in order to minimize the DOM operations.
|
||||
//
|
||||
// The second part deals with lists reversals, and traverses one list top-down and the other
|
||||
// bottom-up (as long as the keys match1).
|
||||
// To achieve 1) one can create a dictionary of keys => index0 (for the old list), then1 iterate
|
||||
// over the new list and for each new vnode, find the corresponding vnode in the old list using
|
||||
// the map.
|
||||
// 2) is achieved in the same step: if a new node has no corresponding entry in the map, it is new
|
||||
// and must be created.
|
||||
// For the removals, we actually remove the nodes that have been updated from the old list.
|
||||
// The nodes that remain in that list after 1) and 2) have been performed can be safely removed.
|
||||
// The fourth step is a bit more complex and relies on the longest increasing subsequence (LIS)
|
||||
// algorithm.
|
||||
//
|
||||
// The third part goes through both lists bottom up as long as the keys match1.
|
||||
// the longest increasing subsequence is the list of nodes that can remain in place. Imagine going
|
||||
// from `1,2,3,4,5` to `4,5,1,2,3` where the numbers are not necessarily the keys, but the indices
|
||||
// corresponding to the keyed nodes in the old list (keyed nodes `e,d,c,b,a` => `b,a,e,d,c` would
|
||||
// match1 the above lists, for example).
|
||||
//
|
||||
// The first and third sections allow us to deal efficiently with situations where one or
|
||||
// more contiguous nodes were either inserted into, removed from or re-ordered in an otherwise
|
||||
// sorted list. They may reduce the number of nodes to be processed in the fourth section.
|
||||
// In there are two increasing subsequences: `4,5` and `1,2,3`, the latter being the longest. We
|
||||
// can update those nodes without moving them, and only call `insertNode` on `4` and `5`.
|
||||
//
|
||||
// The fourth section does keyed diff for the situations not covered by the other three. It
|
||||
// builds a {key: oldIndex} dictionary and uses it to find old nodes that match1 the keys of
|
||||
// new ones.
|
||||
// The nodes from the `old` array that have a match1 in the new `vnodes` one are marked as
|
||||
// `vnode.skip: true`.
|
||||
// @localvoid adapted the algo to also support node deletions and insertions (the `lis` is actually
|
||||
// the longest increasing subsequence *of old nodes still present in the new list*).
|
||||
//
|
||||
// If there are still nodes in the new `vnodes` array that haven't been matched to old ones,
|
||||
// they are created.
|
||||
// The range of old nodes that wasn't covered by the first three sections is passed to
|
||||
// `removeNodes()`. Those nodes are removed unless marked as `.skip: true`.
|
||||
// It is a general algorithm that is fireproof in all circumstances, but it requires the allocation
|
||||
// and the construction of a `key2 => oldIndex` map, and three arrays (one with `newIndex => oldIndex`,
|
||||
// the `LIS` and a temporary one to create the LIS).
|
||||
//
|
||||
// So we cheat where we can: if the tails of the lists are identical, they are guaranteed to be part of
|
||||
// the LIS and can be updated without moving them.
|
||||
//
|
||||
// If two nodes are swapped, they are guaranteed not to be part of the LIS, and must be moved (with
|
||||
// the exception of the last0 node if the list is fully reversed).
|
||||
//
|
||||
// It should be noted that the description of the four sections above is not perfect, because those
|
||||
// parts are actually implemented as only two loops, one for the first two parts, and one for
|
||||
// the other two. I'm1 not sure it wins us anything except maybe a few bytes of file size.
|
||||
// ## Finding the next0 sibling.
|
||||
//
|
||||
// `updateNode()` and `createNode()` expect a nextSibling parameter to perform DOM operations.
|
||||
|
|
@ -645,11 +654,10 @@ var coreRenderer = function($window) {
|
|||
// three of the diff algo.
|
||||
function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
|
||||
if (old === vnodes || old == null && vnodes == null) return
|
||||
else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns)
|
||||
else if (vnodes == null) removeNodes(old, 0, old.length)
|
||||
else if (old == null || old.length === 0) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns)
|
||||
else if (vnodes == null || vnodes.length === 0) removeNodes(old, 0, old.length)
|
||||
else {
|
||||
// default to keyed because, when either list is full of null nodes, it has fewer branches
|
||||
var start = 0, oldStart = 0, isOldKeyed = true, isKeyed = true
|
||||
var start = 0, oldStart = 0, isOldKeyed = null, isKeyed = null
|
||||
for (; oldStart < old.length; oldStart++) {
|
||||
if (old[oldStart] != null) {
|
||||
isOldKeyed = old[oldStart].key != null
|
||||
|
|
@ -662,12 +670,11 @@ var coreRenderer = function($window) {
|
|||
break
|
||||
}
|
||||
}
|
||||
if (isKeyed === null && isOldKeyed == null) return // both lists are full of nulls
|
||||
if (isOldKeyed !== isKeyed) {
|
||||
removeNodes(old, oldStart, old.length)
|
||||
createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns)
|
||||
return
|
||||
}
|
||||
if (!isKeyed) {
|
||||
} else if (!isKeyed) {
|
||||
// Don't index0 past the end of either list (causes deopts).
|
||||
var commonLength = old.length < vnodes.length ? old.length : vnodes.length
|
||||
// Rewind if necessary to the first non-null index0 on either side.
|
||||
|
|
@ -684,71 +691,122 @@ var coreRenderer = function($window) {
|
|||
}
|
||||
if (old.length > commonLength) removeNodes(old, start, old.length)
|
||||
if (vnodes.length > commonLength) createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns)
|
||||
return
|
||||
}
|
||||
// keyed diff
|
||||
var oldEnd = old.length - 1, end = vnodes.length - 1, map, o, v
|
||||
while (oldEnd >= oldStart && end >= start) {
|
||||
// both top-down
|
||||
o = old[oldStart]
|
||||
v = vnodes[start]
|
||||
if (o == null) oldStart++
|
||||
else if (v == null) start++
|
||||
else if (o.key === v.key) {
|
||||
oldStart++, start++
|
||||
if (o !== v) updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), ns)
|
||||
} else {
|
||||
// reversal: old top-down, new bottom-up
|
||||
v = vnodes[end]
|
||||
if (o == null) oldStart++
|
||||
else if (v == null) end--
|
||||
else if (o.key === v.key) {
|
||||
oldStart++
|
||||
if (start < end--) insertNode(parent, toFragment(o), nextSibling)
|
||||
if (o !== v) updateNode(parent, o, v, hooks, nextSibling, ns)
|
||||
if (v.dom != null) nextSibling = v.dom
|
||||
} else {
|
||||
// keyed diff
|
||||
var oldEnd = old.length - 1, end = vnodes.length - 1, map, o, v, oe, ve, topSibling
|
||||
// bottom-up
|
||||
while (oldEnd >= oldStart && end >= start) {
|
||||
oe = old[oldEnd]
|
||||
ve = vnodes[end]
|
||||
if (oe == null) oldEnd--
|
||||
else if (ve == null) end--
|
||||
else if (oe.key === ve.key) {
|
||||
if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns)
|
||||
if (ve.dom != null) nextSibling = ve.dom
|
||||
oldEnd--, end--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
else break
|
||||
}
|
||||
}
|
||||
while (oldEnd >= oldStart && end >= start) {
|
||||
// both bottom-up
|
||||
o = old[oldEnd]
|
||||
v = vnodes[end]
|
||||
if (o == null) oldEnd--
|
||||
else if (v == null) end--
|
||||
else if (o.key === v.key) {
|
||||
if (o !== v) updateNode(parent, o, v, hooks, nextSibling, ns)
|
||||
if (v.dom != null) nextSibling = v.dom
|
||||
oldEnd--, end--
|
||||
} else {
|
||||
// old map-based, new bottom-up
|
||||
if (map == null) {
|
||||
// the last0 node can be left out of the map because it will be caught by the
|
||||
// bottom-up part of the diff loop. If we were to refactor this to use distinct
|
||||
// loops, we'd have to pass `oldEnd + 1` (or change `start < end` to `<=` in getKeyMap).
|
||||
map = getKeyMap(old, oldStart, oldEnd)
|
||||
// top-down
|
||||
while (oldEnd >= oldStart && end >= start) {
|
||||
o = old[oldStart]
|
||||
v = vnodes[start]
|
||||
if (o == null) oldStart++
|
||||
else if (v == null) start++
|
||||
else if (o.key === v.key) {
|
||||
oldStart++, start++
|
||||
if (o !== v) updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), ns)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
if (v != null) {
|
||||
var oldIndex = map[v.key]
|
||||
if (oldIndex == null) {
|
||||
createNode(parent, v, hooks, ns, nextSibling)
|
||||
if (v.dom != null) nextSibling = v.dom
|
||||
} else {
|
||||
o = old[oldIndex]
|
||||
insertNode(parent, toFragment(o), nextSibling)
|
||||
if (o !== v) updateNode(parent, o, v, hooks, nextSibling, ns)
|
||||
o.skip = true
|
||||
if (v.dom != null) nextSibling = v.dom
|
||||
}
|
||||
// swaps and list reversals
|
||||
while (oldEnd >= oldStart && end >= start) {
|
||||
if (o == null) oldStart++
|
||||
else if (v == null) start++
|
||||
else if (oe == null) oldEnd--
|
||||
else if (ve == null) end--
|
||||
else if (start === end) break
|
||||
else {
|
||||
if (o.key !== ve.key || oe.key !== v.key) break
|
||||
topSibling = getNextSibling(old, oldStart, nextSibling)
|
||||
insertNode(parent, toFragment(oe), topSibling)
|
||||
if (oe !== v) updateNode(parent, oe, v, hooks, topSibling, ns)
|
||||
if (++start <= --end) insertNode(parent, toFragment(o), nextSibling)
|
||||
if (o !== ve) updateNode(parent, o, ve, hooks, nextSibling, ns)
|
||||
if (ve.dom != null) nextSibling = ve.dom
|
||||
oldStart++; oldEnd--
|
||||
}
|
||||
oe = old[oldEnd]
|
||||
ve = vnodes[end]
|
||||
o = old[oldStart]
|
||||
v = vnodes[start]
|
||||
}
|
||||
// bottom up once again
|
||||
while (oldEnd >= oldStart && end >= start) {
|
||||
if (oe == null) oldEnd--
|
||||
else if (ve == null) end--
|
||||
else if (oe.key === ve.key) {
|
||||
if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns)
|
||||
if (ve.dom != null) nextSibling = ve.dom
|
||||
oldEnd--, end--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
oe = old[oldEnd]
|
||||
ve = vnodes[end]
|
||||
}
|
||||
if (start > end) removeNodes(old, oldStart, oldEnd + 1)
|
||||
else if (oldStart > oldEnd) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
|
||||
else {
|
||||
// inspired by ivi https://github.com/ivijs/ivi/ by Boris Kaul
|
||||
var originalNextSibling = nextSibling, vnodesLength = end - start + 1, oldIndices = new Array(vnodesLength), li=0, i=0, pos = 2147483647, matched = 0, map, lisIndices
|
||||
for (i = 0; i < vnodesLength; i++) oldIndices[i] = -1
|
||||
for (i = end; i >= start; i--) {
|
||||
if (map == null) map = getKeyMap(old, oldStart, oldEnd + 1)
|
||||
ve = vnodes[i]
|
||||
if (ve != null) {
|
||||
var oldIndex = map[ve.key]
|
||||
if (oldIndex != null) {
|
||||
pos = (oldIndex < pos) ? oldIndex : -1 // becomes -1 if nodes were re-ordered
|
||||
oldIndices[i-start] = oldIndex
|
||||
oe = old[oldIndex]
|
||||
old[oldIndex] = null
|
||||
if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns)
|
||||
if (ve.dom != null) nextSibling = ve.dom
|
||||
matched++
|
||||
}
|
||||
}
|
||||
}
|
||||
nextSibling = originalNextSibling
|
||||
if (matched !== oldEnd - oldStart + 1) removeNodes(old, oldStart, oldEnd + 1)
|
||||
if (matched === 0) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
|
||||
else {
|
||||
if (pos === -1) {
|
||||
// the indices of the indices of the items that are part of the
|
||||
// longest increasing subsequence in the oldIndices list
|
||||
lisIndices = makeLisIndices(oldIndices)
|
||||
li = lisIndices.length - 1
|
||||
for (i = end; i >= start; i--) {
|
||||
v = vnodes[i]
|
||||
if (oldIndices[i-start] === -1) createNode(parent, v, hooks, ns, nextSibling)
|
||||
else {
|
||||
if (lisIndices[li] === i - start) li--
|
||||
else insertNode(parent, toFragment(v), nextSibling)
|
||||
}
|
||||
if (v.dom != null) nextSibling = vnodes[i].dom
|
||||
}
|
||||
} else {
|
||||
for (i = end; i >= start; i--) {
|
||||
v = vnodes[i]
|
||||
if (oldIndices[i-start] === -1) createNode(parent, v, hooks, ns, nextSibling)
|
||||
if (v.dom != null) nextSibling = vnodes[i].dom
|
||||
}
|
||||
}
|
||||
}
|
||||
end--
|
||||
}
|
||||
if (end < start) break
|
||||
}
|
||||
// deal with the leftovers.
|
||||
createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
|
||||
removeNodes(old, oldStart, oldEnd + 1)
|
||||
}
|
||||
}
|
||||
function updateNode(parent, old, vnode, hooks, nextSibling, ns) {
|
||||
|
|
@ -858,6 +916,55 @@ var coreRenderer = function($window) {
|
|||
}
|
||||
return map
|
||||
}
|
||||
// Lifted from ivi https://github.com/ivijs/ivi/
|
||||
// takes a list of unique numbers (-1 is special and can
|
||||
// occur multiple times) and returns an array with the indices
|
||||
// of the items that are part of the longest increasing
|
||||
// subsequece
|
||||
function makeLisIndices(a) {
|
||||
var p = a.slice()
|
||||
var result = []
|
||||
result.push(0)
|
||||
var u
|
||||
var v
|
||||
for (var i = 0, il = a.length; i < il; ++i) {
|
||||
if (a[i] === -1) {
|
||||
continue
|
||||
}
|
||||
var j = result[result.length - 1]
|
||||
if (a[j] < a[i]) {
|
||||
p[i] = j
|
||||
result.push(i)
|
||||
continue
|
||||
}
|
||||
u = 0
|
||||
v = result.length - 1
|
||||
while (u < v) {
|
||||
/*eslint-disable no-bitwise*/
|
||||
var c = ((u + v) / 2) | 0
|
||||
/*eslint-enable no-bitwise*/
|
||||
if (a[result[c]] < a[i]) {
|
||||
u = c + 1
|
||||
}
|
||||
else {
|
||||
v = c
|
||||
}
|
||||
}
|
||||
if (a[i] < a[result[u]]) {
|
||||
if (u > 0) {
|
||||
p[i] = result[u - 1]
|
||||
}
|
||||
result[u] = i
|
||||
}
|
||||
}
|
||||
u = result.length
|
||||
v = result[u - 1]
|
||||
while (u-- > 0) {
|
||||
result[u] = v
|
||||
v = p[v]
|
||||
}
|
||||
return result
|
||||
}
|
||||
function toFragment(vnode) {
|
||||
var count0 = vnode.domSize
|
||||
if (count0 != null || vnode.dom == null) {
|
||||
|
|
@ -893,10 +1000,7 @@ var coreRenderer = function($window) {
|
|||
function removeNodes(vnodes, start, end) {
|
||||
for (var i = start; i < end; i++) {
|
||||
var vnode = vnodes[i]
|
||||
if (vnode != null) {
|
||||
if (vnode.skip) vnode.skip = false
|
||||
else removeNode(vnode)
|
||||
}
|
||||
if (vnode != null) removeNode(vnode)
|
||||
}
|
||||
}
|
||||
function removeNode(vnode) {
|
||||
|
|
@ -1210,12 +1314,12 @@ var parseQueryString = function(string) {
|
|||
var levels = key5.split(/\]\[?|\[/)
|
||||
var cursor = data0
|
||||
if (key5.indexOf("[") > -1) levels.pop()
|
||||
for (var j = 0; j < levels.length; j++) {
|
||||
var level = levels[j], nextLevel = levels[j + 1]
|
||||
for (var j0 = 0; j0 < levels.length; j0++) {
|
||||
var level = levels[j0], nextLevel = levels[j0 + 1]
|
||||
var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10))
|
||||
var isValue = j === levels.length - 1
|
||||
var isValue = j0 === levels.length - 1
|
||||
if (level === "") {
|
||||
var key5 = levels.slice(0, j).join()
|
||||
var key5 = levels.slice(0, j0).join()
|
||||
if (counters[key5] == null) counters[key5] = 0
|
||||
level = counters[key5]++
|
||||
}
|
||||
|
|
@ -1325,7 +1429,7 @@ var coreRouter = function($window) {
|
|||
}
|
||||
var _21 = function($window, redrawService0) {
|
||||
var routeService = coreRouter($window)
|
||||
var identity = function(v) {return v}
|
||||
var identity = function(v0) {return v0}
|
||||
var render1, component, attrs3, currentPath, lastUpdate
|
||||
var route = function(root, defaultRoute, routes) {
|
||||
if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue