Compare commits

..

6 Commits

Author SHA1 Message Date
Rich Harris
6e769496ec -> v0.0.13 2017-12-14 08:23:00 -05:00
Rich Harris
e46aceb2fe implement prefetching anchors with rel=prefetch 2017-12-14 08:21:32 -05:00
Rich Harris
a87cac2481 refactor some stuff 2017-12-14 07:53:17 -05:00
Rich Harris
608fdb7533 -> v0.0.12 2017-12-14 07:15:07 -05:00
Rich Harris
80166b5a7d -> v0.0.11 2017-12-14 07:12:01 -05:00
Rich Harris
24b259f80b fix location, so that runtime can be found from /tmp 2017-12-14 07:11:52 -05:00
4 changed files with 100 additions and 75 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "sapper",
"version": "0.0.10",
"version": "0.0.13",
"description": "Combat-ready apps, engineered by Svelte",
"main": "connect.js",
"directories": {

View File

@@ -1,21 +1,103 @@
const detach = node => {
node.parentNode.removeChild(node);
};
let component;
const app = {
init(callback) {
init(target, routes) {
function select_route(url) {
if (url.origin !== window.location.origin) return null;
for (const route of routes) {
const match = route.pattern.exec(url.pathname);
if (match) {
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 <head> 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 (callback(new URL(a.href))) {
if (navigate(new URL(a.href))) {
event.preventDefault();
history.pushState({}, '', a.href);
}
});
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 => {
callback(window.location);
navigate(new URL(window.location));
});
callback(window.location);
navigate(new URL(window.location));
}
};

View File

@@ -1,47 +1,5 @@
import app from 'sapper/runtime/app.js';
import { detachNode } from 'svelte/shared.js';
import app from '__app__';
const target = document.querySelector('__selector__');
let component;
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 <head> contents
const start = document.querySelector('#sapper-head-start');
let end = document.querySelector('#sapper-head-end');
if (start && end) {
while (start.nextSibling !== end) detachNode(start.nextSibling);
detachNode(start);
detachNode(end);
}
target.innerHTML = '';
}
component = new mod.default({
target,
data: Object.assign(route, preloaded),
hydrate: !!component
});
});
}
// ROUTES
return true;
});
app.init(document.querySelector('__selector__'), [
__routes__
]);

View File

@@ -9,33 +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);
};