Skip to content

Commit 08b944a

Browse files
kfuledead-claudia
authored andcommitted
router: delay mounting RouterRoot until the first route is resolved (fixes #2621) (#3030)
1 parent ae85e4a commit 08b944a

File tree

2 files changed

+186
-7
lines changed

2 files changed

+186
-7
lines changed

api/router.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,24 @@ module.exports = function($window, mountRedraw) {
2828
var ready = false
2929
var hasBeenResolved = false
3030

31-
var compiled, fallbackRoute
31+
var dom, compiled, fallbackRoute
3232

3333
var currentResolver, component, attrs, currentPath, lastUpdate
3434

3535
var RouterRoot = {
3636
onremove: function() {
37+
ready = hasBeenResolved = false
3738
$window.removeEventListener("popstate", fireAsync, false)
3839
},
3940
view: function() {
40-
if (!hasBeenResolved) return
41+
// The route has already been resolved.
42+
// Therefore, the following early return is not needed.
43+
// if (!hasBeenResolved) return
44+
45+
var vnode = Vnode(component, attrs.key, attrs)
46+
if (currentResolver) return currentResolver.render(vnode)
4147
// Wrap in a fragment to preserve existing key semantics
42-
var vnode = [Vnode(component, attrs.key, attrs)]
43-
if (currentResolver) vnode = currentResolver.render(vnode[0])
44-
return vnode
48+
return [vnode]
4549
},
4650
}
4751

@@ -90,7 +94,7 @@ module.exports = function($window, mountRedraw) {
9094
if (hasBeenResolved) mountRedraw.redraw()
9195
else {
9296
hasBeenResolved = true
93-
mountRedraw.redraw.sync()
97+
mountRedraw.mount(dom, RouterRoot)
9498
}
9599
}
96100
// There's no understating how much I *wish* I could
@@ -148,11 +152,13 @@ module.exports = function($window, mountRedraw) {
148152
throw new ReferenceError("Default route doesn't match any known routes.")
149153
}
150154
}
155+
dom = root
151156

152157
$window.addEventListener("popstate", fireAsync, false)
153158

154159
ready = true
155-
mountRedraw.mount(root, RouterRoot)
160+
161+
// The RouterRoot component is mounted when the route is first resolved.
156162
resolveRoute()
157163
}
158164
route.set = function(path, data, options) {

api/tests/test-router.js

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1931,6 +1931,179 @@ o.spec("route", function() {
19311931
o(route.param()).deepEquals({id:"1"})
19321932
})
19331933
})
1934+
1935+
o("route component is mounted after the route is initially resolved (synchronous)", function() {
1936+
var Component = {
1937+
view: lock(function() {
1938+
// the first rendered vnode is cleared
1939+
o(root.childNodes.length).equals(0)
1940+
return m("span")
1941+
})
1942+
}
1943+
1944+
// initial root node
1945+
root.textContent = "foo"
1946+
o(root.childNodes.length).equals(1)
1947+
o(root.childNodes[0].nodeName).equals("#text")
1948+
o(root.childNodes[0].nodeValue).equals("foo")
1949+
1950+
// render another vnode first
1951+
var render = coreRenderer($window)
1952+
var vnode = m("a", "loading...")
1953+
render(root, vnode)
1954+
o(root.childNodes.length).equals(1)
1955+
o(root.childNodes[0].nodeName).equals("A")
1956+
o(root.childNodes[0].firstChild.nodeName).equals("#text")
1957+
o(root.childNodes[0].firstChild.nodeValue).equals("loading...")
1958+
1959+
// call route() (mount synchronously)
1960+
$window.location.href = prefix + "/"
1961+
route(root, "/", {
1962+
"/" : Component
1963+
})
1964+
1965+
// route component is mounted and the first rendered vnode is cleared
1966+
o(root.childNodes.length).equals(1)
1967+
o(root.childNodes[0]).notEquals(vnode.dom)
1968+
o(root.childNodes[0].nodeName).equals("SPAN")
1969+
o(root.childNodes[0].childNodes.length).equals(0)
1970+
})
1971+
1972+
o("route component is mounted after the route is initially resolved (render, synchronous)", function() {
1973+
var Component = {
1974+
render: lock(function() {
1975+
// the first rendered vnode is cleared
1976+
o(root.childNodes.length).equals(0)
1977+
return m("span")
1978+
})
1979+
}
1980+
1981+
// initial root node
1982+
root.textContent = "foo"
1983+
o(root.childNodes.length).equals(1)
1984+
o(root.childNodes[0].nodeName).equals("#text")
1985+
o(root.childNodes[0].nodeValue).equals("foo")
1986+
1987+
// render another vnode first
1988+
var render = coreRenderer($window)
1989+
var vnode = m("a", "loading...")
1990+
render(root, vnode)
1991+
o(root.childNodes.length).equals(1)
1992+
o(root.childNodes[0].nodeName).equals("A")
1993+
o(root.childNodes[0].firstChild.nodeName).equals("#text")
1994+
o(root.childNodes[0].firstChild.nodeValue).equals("loading...")
1995+
1996+
// call route() (mount synchronously)
1997+
$window.location.href = prefix + "/"
1998+
route(root, "/", {
1999+
"/" : Component
2000+
})
2001+
2002+
// route component is mounted and the first rendered vnode is cleared
2003+
o(root.childNodes.length).equals(1)
2004+
o(root.childNodes[0]).notEquals(vnode.dom)
2005+
o(root.childNodes[0].nodeName).equals("SPAN")
2006+
o(root.childNodes[0].childNodes.length).equals(0)
2007+
})
2008+
2009+
o("route component is mounted after the route is initially resolved (onmatch, asynchronous)", function() {
2010+
var Component = {
2011+
view: lock(function() {
2012+
return m("span")
2013+
})
2014+
}
2015+
2016+
// check for the order of calling onmatch and render
2017+
var count = 0
2018+
2019+
var resolver = {
2020+
onmatch: lock(function() {
2021+
count += 1
2022+
o(count).equals(1)
2023+
2024+
// the first rendered vnode is not yet cleared
2025+
o(root.childNodes.length).equals(1)
2026+
o(root.childNodes[0]).equals(vnode.dom)
2027+
o(root.childNodes[0].nodeName).equals("A")
2028+
o(root.childNodes[0].firstChild.nodeName).equals("#text")
2029+
o(root.childNodes[0].firstChild.nodeValue).equals("loading...")
2030+
return Component
2031+
}),
2032+
render: lock(function(vnode) {
2033+
count += 1
2034+
o(count).equals(2)
2035+
2036+
// the first rendered vnode is cleared
2037+
o(root.childNodes.length).equals(0)
2038+
return vnode
2039+
})
2040+
}
2041+
2042+
// initial root node
2043+
root.textContent = "foo"
2044+
o(root.childNodes.length).equals(1)
2045+
o(root.childNodes[0].nodeName).equals("#text")
2046+
o(root.childNodes[0].nodeValue).equals("foo")
2047+
2048+
// render another vnode first
2049+
var render = coreRenderer($window)
2050+
var vnode = m("a", "loading...")
2051+
render(root, vnode)
2052+
o(root.childNodes.length).equals(1)
2053+
o(root.childNodes[0].nodeName).equals("A")
2054+
o(root.childNodes[0].firstChild.nodeName).equals("#text")
2055+
o(root.childNodes[0].firstChild.nodeValue).equals("loading...")
2056+
2057+
// call route() (mount asynchronously)
2058+
$window.location.href = prefix + "/"
2059+
route(root, "/", {
2060+
"/" : resolver
2061+
})
2062+
2063+
// the first rendered vnode is not yet cleared
2064+
o(root.childNodes.length).equals(1)
2065+
o(root.childNodes[0]).equals(vnode.dom)
2066+
o(root.childNodes[0].nodeName).equals("A")
2067+
o(root.childNodes[0].firstChild.nodeName).equals("#text")
2068+
o(root.childNodes[0].firstChild.nodeValue).equals("loading...")
2069+
2070+
// The count of route resolver method calls is still 0
2071+
o(count).equals(0)
2072+
2073+
return waitCycles(1).then(function() {
2074+
// route component is mounted and the first rendered vnode is cleared
2075+
o(root.childNodes.length).equals(1)
2076+
o(root.childNodes[0]).notEquals(vnode.dom)
2077+
o(root.childNodes[0].nodeName).equals("SPAN")
2078+
o(root.childNodes[0].childNodes.length).equals(0)
2079+
2080+
o(count).equals(2)
2081+
})
2082+
})
2083+
2084+
o("error in the route component is thrown and not caught in the initial rendering (#2621)", function() {
2085+
var Component = {
2086+
view: lock(function() {
2087+
throw Error("foo")
2088+
})
2089+
}
2090+
2091+
// Errors thrown during redrawing of mounted components are caught in m.mount()
2092+
// and console.error is called.
2093+
// Therefore, spy is used to confirm that console.error is not called
2094+
// when it is first mounted.
2095+
var spy = o.spy(console.error)
2096+
console.error = spy
2097+
2098+
$window.location.href = prefix + "/"
2099+
o(function(){
2100+
route(root, "/", {
2101+
"/" : Component
2102+
})
2103+
}).throws("foo")
2104+
2105+
o(spy.callCount).equals(0)
2106+
})
19342107
})
19352108
})
19362109
})

0 commit comments

Comments
 (0)