diff --git a/runtime/app.js b/runtime/app.js index 1e96205..64cc60b 100644 --- a/runtime/app.js +++ b/runtime/app.js @@ -6,58 +6,69 @@ let component; const app = { init(target, routes) { - function navigate(url) { - if (url.origin !== window.location.origin) return; + function select_route(url) { + if (url.origin !== window.location.origin) return null; - let match; - let params = {}; - const query = {}; - - function render(mod) { - const route = { query, params }; - - Promise.resolve( - mod.default.preload ? mod.default.preload(route) : {} - ).then(preloaded => { - if (component) { - component.destroy(); - } else { - // first load — remove SSR'd contents - const start = document.querySelector('#sapper-head-start'); - let end = document.querySelector('#sapper-head-end'); - - if (start && end) { - while (start.nextSibling !== end) detach(start.nextSibling); - detach(start); - detach(end); - } - - // preload additional routes - routes.reduce((promise, route) => promise.then(route.load), Promise.resolve()); - } - - component = new mod.default({ - target, - data: Object.assign(route, preloaded), - hydrate: !!component - }); - }); - } - - for (let i = 0; i < routes.length; i += 1) { - const route = routes[i]; + for (const route of routes) { const match = route.pattern.exec(url.pathname); if (match) { - params = route.params(match); - route.load().then(render); - return true; + const params = route.params(match); + + const query = {}; + for (const [key, value] of url.searchParams) query[key] = value || true; + + return { route, data: { params, query } }; } } } + function render(Component, data) { + Promise.resolve( + Component.preload ? Component.preload(data) : {} + ).then(preloaded => { + if (component) { + component.destroy(); + } else { + // first load — remove SSR'd contents + const start = document.querySelector('#sapper-head-start'); + let end = document.querySelector('#sapper-head-end'); + + if (start && end) { + while (start.nextSibling !== end) detach(start.nextSibling); + detach(start); + detach(end); + } + + // preload additional routes + routes.reduce((promise, route) => promise.then(route.load), Promise.resolve()); + } + + component = new Component({ + target, + data: Object.assign(data, preloaded), + hydrate: !!component + }); + }); + } + + function navigate(url) { + const selected = select_route(url); + if (selected) { + selected.route.load().then(mod => { + render(mod.default, selected.data); + }); + + return true; + } + } + + function findAnchor(node) { + while (node && node.nodeName !== 'A') node = node.parentNode; + return node; + } + window.addEventListener('click', event => { - let a = event.target; - while (a && a.nodeName !== 'A') a = a.parentNode; + const a = findAnchor(event.target); if (!a) return; if (navigate(new URL(a.href))) { @@ -66,11 +77,27 @@ const app = { } }); + function preload(event) { + const a = findAnchor(event.target); + if (!a || a.rel !== 'prefetch') return; + + const selected = select_route(new URL(a.href)); + + if (selected) { + selected.route.load().then(mod => { + if (mod.default.preload) mod.default.preload(selected.data); + }); + } + } + + window.addEventListener('touchstart', preload); + window.addEventListener('mouseover', preload); + window.addEventListener('popstate', event => { - navigate(window.location); + navigate(new URL(window.location)); }); - navigate(window.location); + navigate(new URL(window.location)); } };