diff --git a/runtime/app.js b/runtime/app.js index ca91898..1e96205 100644 --- a/runtime/app.js +++ b/runtime/app.js @@ -1,21 +1,76 @@ +const detach = node => { + node.parentNode.removeChild(node); +}; + +let component; + const app = { - init(callback) { + init(target, routes) { + function navigate(url) { + if (url.origin !== window.location.origin) return; + + 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]; + const match = route.pattern.exec(url.pathname); + if (match) { + params = route.params(match); + route.load().then(render); + return true; + } + } + } + window.addEventListener('click', event => { let a = event.target; while (a && a.nodeName !== 'A') a = a.parentNode; if (!a) return; - if (callback(new URL(a.href))) { + if (navigate(new URL(a.href))) { event.preventDefault(); history.pushState({}, '', a.href); } }); window.addEventListener('popstate', event => { - callback(window.location); + navigate(window.location); }); - callback(window.location); + navigate(window.location); } }; diff --git a/templates/main.js b/templates/main.js index 8eaa116..252b60f 100644 --- a/templates/main.js +++ b/templates/main.js @@ -1,50 +1,5 @@ import app from '__app__'; -const target = document.querySelector('__selector__'); -let component; - -const detach = node => { - node.parentNode.removeChild(node); -}; - -app.init(url => { - if (url.origin !== window.location.origin) return; - - 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 { - // 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); - } - - target.innerHTML = ''; - } - - component = new mod.default({ - target, - data: Object.assign(route, preloaded), - hydrate: !!component - }); - }); - } - - // ROUTES - - return true; -}); \ No newline at end of file +app.init(document.querySelector('__selector__'), [ + __routes__ +]); \ No newline at end of file diff --git a/utils/create_app.js b/utils/create_app.js index 854b49f..8f3b2b9 100644 --- a/utils/create_app.js +++ b/utils/create_app.js @@ -9,34 +9,18 @@ module.exports = function create_app(src, dest, routes, options) { const code = routes .filter(route => route.type === 'page') .map(route => { - const condition = route.dynamic.length === 0 ? - `url.pathname === '/${route.parts.join('/')}'` : - `match = ${route.pattern}.exec(url.pathname)`; + const params = route.dynamic.length === 0 ? + '{}' : + `{ ${route.dynamic.map((part, i) => `${part}: match[${i + 1}]`).join(', ') } }`; - const lines = []; - - route.dynamic.forEach((part, i) => { - lines.push( - `params.${part} = match[${i + 1}];` - ); - }); - - lines.push( - `import('${src}/${route.file}').then(render);` - ); - - return ` - if (${condition}) { - ${lines.join(`\n\t\t\t\t\t`)} - } - `.replace(/^\t{3}/gm, '').trim(); + return `{ pattern: ${route.pattern}, params: match => (${params}), load: () => import('${src}/${route.file}') }` }) - .join(' else ') + ' else return false;'; + .join(',\n\t'); const main = template .replace('__app__', path.resolve(__dirname, '../runtime/app.js')) .replace('__selector__', options.selector || 'main') - .replace('// ROUTES', code); + .replace('__routes__', code); fs.writeFileSync(path.join(dest, 'main.js'), main); }; \ No newline at end of file