Skip to content

Commit 9d3ce5f

Browse files
committedJul 28, 2019
v2.0.2
1 parent ad680c6 commit 9d3ce5f

File tree

7 files changed

+151
-69
lines changed

7 files changed

+151
-69
lines changed
 

‎README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ mithril.js [![npm Version](https://img.shields.io/npm/v/mithril.svg)](https://ww
1818

1919
## What is Mithril?
2020

21-
A modern client-side Javascript framework for building Single Page Applications. It's small (<!-- size -->9.55 KB<!-- /size --> gzipped), fast and provides routing and XHR utilities out of the box.
21+
A modern client-side Javascript framework for building Single Page Applications. It's small (<!-- size -->9.77 KB<!-- /size --> gzipped), fast and provides routing and XHR utilities out of the box.
2222

2323
Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess 👍.
2424

‎mithril.js

+144-64
Original file line numberDiff line numberDiff line change
@@ -363,9 +363,12 @@ var _12 = function($window) {
363363
}
364364
vnode3.dom = temp.firstChild
365365
vnode3.domSize = temp.childNodes.length
366+
// Capture nodes to remove, so we don't confuse them.
367+
vnode3.instance = []
366368
var fragment = $doc.createDocumentFragment()
367369
var child
368370
while (child = temp.firstChild) {
371+
vnode3.instance.push(child)
369372
fragment.appendChild(child)
370373
}
371374
insertNode(parent, fragment, nextSibling)
@@ -538,7 +541,7 @@ var _12 = function($window) {
538541
function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
539542
if (old === vnodes || old == null && vnodes == null) return
540543
else if (old == null || old.length === 0) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns)
541-
else if (vnodes == null || vnodes.length === 0) removeNodes(old, 0, old.length)
544+
else if (vnodes == null || vnodes.length === 0) removeNodes(parent, old, 0, old.length)
542545
else {
543546
var isOldKeyed = old[0] != null && old[0].key != null
544547
var isKeyed0 = vnodes[0] != null && vnodes[0].key != null
@@ -547,7 +550,7 @@ var _12 = function($window) {
547550
if (!isKeyed0) while (start < vnodes.length && vnodes[start] == null) start++
548551
if (isKeyed0 === null && isOldKeyed == null) return // both lists are full of nulls
549552
if (isOldKeyed !== isKeyed0) {
550-
removeNodes(old, oldStart, old.length)
553+
removeNodes(parent, old, oldStart, old.length)
551554
createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns)
552555
} else if (!isKeyed0) {
553556
// Don't index past the end of either list (causes deopts).
@@ -561,10 +564,10 @@ var _12 = function($window) {
561564
v = vnodes[start]
562565
if (o === v || o == null && v == null) continue
563566
else if (o == null) createNode(parent, v, hooks, ns, getNextSibling(old, start + 1, nextSibling))
564-
else if (v == null) removeNode(o)
567+
else if (v == null) removeNode(parent, o)
565568
else updateNode(parent, o, v, hooks, getNextSibling(old, start + 1, nextSibling), ns)
566569
}
567-
if (old.length > commonLength) removeNodes(old, start, old.length)
570+
if (old.length > commonLength) removeNodes(parent, old, start, old.length)
568571
if (vnodes.length > commonLength) createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns)
569572
} else {
570573
// keyed diff
@@ -591,9 +594,9 @@ var _12 = function($window) {
591594
if (start === end) break
592595
if (o.key !== ve.key || oe.key !== v.key) break
593596
topSibling = getNextSibling(old, oldStart, nextSibling)
594-
insertNode(parent, toFragment(oe), topSibling)
597+
moveNodes(parent, oe, topSibling)
595598
if (oe !== v) updateNode(parent, oe, v, hooks, topSibling, ns)
596-
if (++start <= --end) insertNode(parent, toFragment(o), nextSibling)
599+
if (++start <= --end) moveNodes(parent, o, nextSibling)
597600
if (o !== ve) updateNode(parent, o, ve, hooks, nextSibling, ns)
598601
if (ve.dom != null) nextSibling = ve.dom
599602
oldStart++; oldEnd--
@@ -611,7 +614,7 @@ var _12 = function($window) {
611614
oe = old[oldEnd]
612615
ve = vnodes[end]
613616
}
614-
if (start > end) removeNodes(old, oldStart, oldEnd + 1)
617+
if (start > end) removeNodes(parent, old, oldStart, oldEnd + 1)
615618
else if (oldStart > oldEnd) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
616619
else {
617620
// inspired by ivi https://github.com/ivijs/ivi/ by Boris Kaul
@@ -632,7 +635,7 @@ var _12 = function($window) {
632635
}
633636
}
634637
nextSibling = originalNextSibling
635-
if (matched !== oldEnd - oldStart + 1) removeNodes(old, oldStart, oldEnd + 1)
638+
if (matched !== oldEnd - oldStart + 1) removeNodes(parent, old, oldStart, oldEnd + 1)
636639
if (matched === 0) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
637640
else {
638641
if (pos === -1) {
@@ -645,7 +648,7 @@ var _12 = function($window) {
645648
if (oldIndices[i-start] === -1) createNode(parent, v, hooks, ns, nextSibling)
646649
else {
647650
if (lisIndices[li] === i - start) li--
648-
else insertNode(parent, toFragment(v), nextSibling)
651+
else moveNodes(parent, v, nextSibling)
649652
}
650653
if (v.dom != null) nextSibling = vnodes[i].dom
651654
}
@@ -681,7 +684,7 @@ var _12 = function($window) {
681684
else updateComponent(parent, old, vnode3, hooks, nextSibling, ns)
682685
}
683686
else {
684-
removeNode(old)
687+
removeNode(parent, old)
685688
createNode(parent, vnode3, hooks, ns, nextSibling)
686689
}
687690
}
@@ -693,7 +696,7 @@ var _12 = function($window) {
693696
}
694697
function updateHTML(parent, old, vnode3, ns, nextSibling) {
695698
if (old.children !== vnode3.children) {
696-
toFragment(old)
699+
removeHTML(parent, old)
697700
createHTML(parent, vnode3, ns, nextSibling)
698701
}
699702
else vnode3.dom = old.dom, vnode3.domSize = old.domSize
@@ -747,7 +750,7 @@ var _12 = function($window) {
747750
vnode3.domSize = vnode3.instance.domSize
748751
}
749752
else if (old.instance != null) {
750-
removeNode(old.instance)
753+
removeNode(parent, old.instance)
751754
vnode3.dom = undefined
752755
vnode3.domSize = 0
753756
}
@@ -813,25 +816,50 @@ var _12 = function($window) {
813816
lisTemp.length = 0
814817
return result
815818
}
816-
function toFragment(vnode3) {
817-
var count = vnode3.domSize
818-
if (count != null || vnode3.dom == null) {
819-
var fragment = $doc.createDocumentFragment()
820-
if (count > 0) {
821-
var dom = vnode3.dom
822-
while (--count) fragment.appendChild(dom.nextSibling)
823-
fragment.insertBefore(dom, fragment.firstChild)
824-
}
825-
return fragment
826-
}
827-
else return vnode3.dom
828-
}
829819
function getNextSibling(vnodes, i, nextSibling) {
830820
for (; i < vnodes.length; i++) {
831821
if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom
832822
}
833823
return nextSibling
834824
}
825+
// This covers a really specific edge case:
826+
// - Parent node is keyed and contains child
827+
// - Child is removed, returns unresolved promise0 in `onbeforeremove`
828+
// - Parent node is moved in keyed diff
829+
// - Remaining children3 still need moved appropriately
830+
//
831+
// Ideally, I'd track removed nodes as well, but that introduces a lot more
832+
// complexity and I'm0 not exactly interested in doing that.
833+
function moveNodes(parent, vnode3, nextSibling) {
834+
var frag = $doc.createDocumentFragment()
835+
moveChildToFrag(parent, frag, vnode3)
836+
insertNode(parent, frag, nextSibling)
837+
}
838+
function moveChildToFrag(parent, frag, vnode3) {
839+
// Dodge the recursion overhead in a few of the most common cases.
840+
while (vnode3.dom != null && vnode3.dom.parentNode === parent) {
841+
if (typeof vnode3.tag !== "string") {
842+
vnode3 = vnode3.instance
843+
if (vnode3 != null) continue
844+
} else if (vnode3.tag === "<") {
845+
for (var i = 0; i < vnode3.instance.length; i++) {
846+
frag.appendChild(vnode3.instance[i])
847+
}
848+
} else if (vnode3.tag !== "[") {
849+
// Don't recurse for text nodes *or* elements, just fragments
850+
frag.appendChild(vnode3.dom)
851+
} else if (vnode3.children.length === 1) {
852+
vnode3 = vnode3.children[0]
853+
if (vnode3 != null) continue
854+
} else {
855+
for (var i = 0; i < vnode3.children.length; i++) {
856+
var child = vnode3.children[i]
857+
if (child != null) moveChildToFrag(parent, frag, child)
858+
}
859+
}
860+
break
861+
}
862+
}
835863
function insertNode(parent, dom, nextSibling) {
836864
if (nextSibling != null) parent.insertBefore(dom, nextSibling)
837865
else parent.appendChild(dom)
@@ -849,41 +877,87 @@ var _12 = function($window) {
849877
else if (vnode3.text != null || children3 != null && children3.length !== 0) throw new Error("Child node of a contenteditable must be trusted")
850878
}
851879
//remove
852-
function removeNodes(vnodes, start, end) {
880+
function removeNodes(parent, vnodes, start, end) {
853881
for (var i = start; i < end; i++) {
854882
var vnode3 = vnodes[i]
855-
if (vnode3 != null) removeNode(vnode3)
883+
if (vnode3 != null) removeNode(parent, vnode3)
856884
}
857885
}
858-
function removeNode(vnode3) {
859-
var expected = 1, called = 0
886+
function removeNode(parent, vnode3) {
887+
var mask = 0
860888
var original = vnode3.state
889+
var stateResult, attrsResult
861890
if (typeof vnode3.tag !== "string" && typeof vnode3.state.onbeforeremove === "function") {
862891
var result = callHook.call(vnode3.state.onbeforeremove, vnode3)
863892
if (result != null && typeof result.then === "function") {
864-
expected++
865-
result.then(continuation, continuation)
893+
mask = 1
894+
stateResult = result
866895
}
867896
}
868897
if (vnode3.attrs && typeof vnode3.attrs.onbeforeremove === "function") {
869898
var result = callHook.call(vnode3.attrs.onbeforeremove, vnode3)
870899
if (result != null && typeof result.then === "function") {
871-
expected++
872-
result.then(continuation, continuation)
900+
// eslint-disable-next-line no-bitwise
901+
mask |= 2
902+
attrsResult = result
903+
}
904+
}
905+
checkState(vnode3, original)
906+
// If we can, try to fast-path it and avoid all the overhead of awaiting
907+
if (!mask) {
908+
onremove(vnode3)
909+
removeChild(parent, vnode3)
910+
} else {
911+
if (stateResult != null) {
912+
var next = function () {
913+
// eslint-disable-next-line no-bitwise
914+
if (mask & 1) { mask &= 2; if (!mask) reallyRemove() }
915+
}
916+
stateResult.then(next, next)
917+
}
918+
if (attrsResult != null) {
919+
var next = function () {
920+
// eslint-disable-next-line no-bitwise
921+
if (mask & 2) { mask &= 1; if (!mask) reallyRemove() }
922+
}
923+
attrsResult.then(next, next)
873924
}
874925
}
875-
continuation()
876-
function continuation() {
877-
if (++called === expected) {
878-
checkState(vnode3, original)
879-
onremove(vnode3)
880-
if (vnode3.dom) {
881-
var parent = vnode3.dom.parentNode
882-
var count = vnode3.domSize || 1
883-
while (--count) parent.removeChild(vnode3.dom.nextSibling)
926+
function reallyRemove() {
927+
checkState(vnode3, original)
928+
onremove(vnode3)
929+
removeChild(parent, vnode3)
930+
}
931+
}
932+
function removeHTML(parent, vnode3) {
933+
for (var i = 0; i < vnode3.instance.length; i++) {
934+
parent.removeChild(vnode3.instance[i])
935+
}
936+
}
937+
function removeChild(parent, vnode3) {
938+
// Dodge the recursion overhead in a few of the most common cases.
939+
while (vnode3.dom != null && vnode3.dom.parentNode === parent) {
940+
if (typeof vnode3.tag !== "string") {
941+
vnode3 = vnode3.instance
942+
if (vnode3 != null) continue
943+
} else if (vnode3.tag === "<") {
944+
removeHTML(parent, vnode3)
945+
} else {
946+
if (vnode3.tag !== "[") {
884947
parent.removeChild(vnode3.dom)
948+
if (!Array.isArray(vnode3.children)) break
949+
}
950+
if (vnode3.children.length === 1) {
951+
vnode3 = vnode3.children[0]
952+
if (vnode3 != null) continue
953+
} else {
954+
for (var i = 0; i < vnode3.children.length; i++) {
955+
var child = vnode3.children[i]
956+
if (child != null) removeChild(parent, child)
957+
}
885958
}
886959
}
960+
break
887961
}
888962
}
889963
function onremove(vnode3) {
@@ -1254,7 +1328,7 @@ var _18 = function($window, Promise, oncompletion) {
12541328
return function(url, args) {
12551329
if (typeof url !== "string") { args = url; url = url.url }
12561330
else if (args == null) args = {}
1257-
var promise0 = new Promise(function(resolve, reject) {
1331+
var promise1 = new Promise(function(resolve, reject) {
12581332
factory(buildPathname(url, args.params), args, function (data) {
12591333
if (typeof args.type === "function") {
12601334
if (Array.isArray(data)) {
@@ -1267,32 +1341,32 @@ var _18 = function($window, Promise, oncompletion) {
12671341
resolve(data)
12681342
}, reject)
12691343
})
1270-
if (args.background === true) return promise0
1271-
var count0 = 0
1344+
if (args.background === true) return promise1
1345+
var count = 0
12721346
function complete() {
1273-
if (--count0 === 0 && typeof oncompletion === "function") oncompletion()
1347+
if (--count === 0 && typeof oncompletion === "function") oncompletion()
12741348
}
1275-
return wrap(promise0)
1276-
function wrap(promise0) {
1277-
var then1 = promise0.then
1349+
return wrap(promise1)
1350+
function wrap(promise1) {
1351+
var then1 = promise1.then
12781352
// Set the constructor, so engines know to not await or resolve
1279-
// this as a native promise0. At the time of writing, this is0
1353+
// this as a native promise1. At the time of writing, this is0
12801354
// only necessary for V8, but their behavior is0 the correct
12811355
// behavior per spec. See this spec issue for more details:
12821356
// https://github.com/tc39/ecma262/issues/1577. Also, see the
12831357
// corresponding comment in `request0/tests/test-request0.js` for
12841358
// a bit more background on the issue at hand.
1285-
promise0.constructor = PromiseProxy
1286-
promise0.then = function() {
1287-
count0++
1288-
var next = then1.apply(promise0, arguments)
1289-
next.then(complete, function(e) {
1359+
promise1.constructor = PromiseProxy
1360+
promise1.then = function() {
1361+
count++
1362+
var next0 = then1.apply(promise1, arguments)
1363+
next0.then(complete, function(e) {
12901364
complete()
1291-
if (count0 === 0) throw e
1365+
if (count === 0) throw e
12921366
})
1293-
return wrap(next)
1367+
return wrap(next0)
12941368
}
1295-
return promise0
1369+
return promise1
12961370
}
12971371
}
12981372
}
@@ -1420,8 +1494,6 @@ m.fragment = hyperscript.fragment
14201494
m.mount = mountRedraw.mount
14211495
var m3 = hyperscript
14221496
var Promise = PromisePolyfill
1423-
// The extra `data0` parameter is2 for if you want to append to an existing
1424-
// parameters object.
14251497
var parseQueryString = function(string) {
14261498
if (string === "" || string == null) return {}
14271499
if (string.charAt(0) === "?") string = string.slice(1)
@@ -1446,9 +1518,17 @@ var parseQueryString = function(string) {
14461518
}
14471519
level = counters[key5]++
14481520
}
1521+
// Disallow direct prototype pollution
1522+
else if (level === "__proto__") break
14491523
if (isValue) cursor[level] = value2
1450-
else if (cursor[level] == null) cursor[level] = isNumber ? [] : {}
1451-
cursor = cursor[level]
1524+
else {
1525+
// Read own properties exclusively to disallow indirect
1526+
// prototype pollution
1527+
value2 = Object.getOwnPropertyDescriptor(cursor, level)
1528+
if (value2 != null) value2 = value2.value
1529+
if (value2 == null) value2 = cursor[level] = isNumber ? [] : {}
1530+
}
1531+
cursor = value2
14521532
}
14531533
}
14541534
return data0
@@ -1483,7 +1563,7 @@ var compileTemplate = function(template) {
14831563
var keys = []
14841564
var regexp = new RegExp("^" + templateData.path.replace(
14851565
// I escape literal text so people can use things like `:file.:ext` or
1486-
// `:lang-:locale` in routes. This is3 all merged into one pass so I
1566+
// `:lang-:locale` in routes. This is2 all merged into one pass so I
14871567
// don't also accidentally escape `-` and make it harder to detect it to
14881568
// ban it from template parameters.
14891569
/:([^\/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|\[\]{}]/g,
@@ -1710,7 +1790,7 @@ var _25 = function($window, mountRedraw00) {
17101790
onclick.handleEvent(e)
17111791
}
17121792
// Adapted from React Router's implementation:
1713-
// https://github.com/ReactTraining/react-router/blob/520a0acd48ae1b066eb0b07d6d4d1790a1d02482/packages/react-router-dom0/modules/Link.js
1793+
// https://github.com/ReactTraining/react-router/blob/520a0acd48ae1b066eb0b07d6d4d1790a1d02482/packages/react-router-dom/modules/Link.js
17141794
//
17151795
// Try to be flexible and intuitive in how we handle1 links.
17161796
// Fun fact: links aren't as obvious to get right as you

‎mithril.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mithril",
3-
"version": "2.0.1",
3+
"version": "2.0.2",
44
"description": "A framework for building brilliant applications",
55
"author": "Leo Horie",
66
"license": "MIT",

‎scripts/generate-docs.js

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ async function generate() {
5757
execFileSync("git", ["checkout", "gh-pages", "--", "archive"])
5858
await fs.rename(r("archive"), r("dist/archive"))
5959
await fs.mkdir(r(`dist/archive/v${version}`), {recursive: true})
60+
// Tell Git to ignore our changes - it's no longer there.
61+
execFileSync("git", ["add", "archive"])
6062

6163
function compilePage(file, markdown) {
6264
file = path.basename(file)

‎scripts/release.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ exec("git", ["checkout", "master"])
139139
exec("git", ["pull", "--rebase", upstream.fetch.branch, "master"])
140140
// There may be merge conflicts with `index.js` and/or the bundle - just ignore
141141
// them. Whatever they have is canon, as is the case with everything else.
142-
exec("git", ["merge", "next", "-s", "theirs"])
142+
exec("git", ["merge", "next", "--strategy-option=theirs"])
143143
rimraf.sync(p("node_modules"))
144144
exec("npm", ["install-test"])
145145

0 commit comments

Comments
 (0)
Please sign in to comment.