implement this.redirect in preload (#83)

This commit is contained in:
Rich Harris
2018-02-17 22:56:47 -05:00
parent f8ea9ebda1
commit bff6f550be
7 changed files with 71 additions and 80 deletions

View File

@@ -34,59 +34,6 @@ export default function create_templates() {
return template.replace(/%sapper\.(\w+)%/g, (match, key) => {
return key in data ? data[key] : '';
});
},
stream: (req: any, res: any, data: Record<string, string | Promise<string>>) => {
let i = 0;
let body = '';
function stream_inner(): Promise<void> {
if (i >= template.length) {
return;
}
const start = template.indexOf('%sapper', i);
if (start === -1) {
const chunk = template.slice(i);
body += chunk;
res.end(chunk);
if (process.send) {
process.send({
__sapper__: true,
url: req.url,
method: req.method,
type: 'text/html',
body
});
}
return;
}
const chunk = template.slice(i, start);
body += chunk;
res.write(chunk);
const end = template.indexOf('%', start + 1);
if (end === -1) {
throw new Error(`Bad template`); // TODO validate ahead of time
}
const tag = template.slice(start + 1, end);
const match = /sapper\.(\w+)/.exec(tag);
if (!match || !(match[1] in data)) throw new Error(`Bad template`); // TODO ditto
return Promise.resolve(data[match[1]]).then(chunk => {
body += chunk;
res.write(chunk);
i = end + 1;
return stream_inner();
});
}
return Promise.resolve().then(stream_inner);
}
};
}

View File

@@ -19,6 +19,7 @@ type Assets = {
}
type RouteObject = {
id: string;
type: 'page' | 'route';
pattern: RegExp;
params: (match: RegExpMatchArray) => Record<string, string>;
@@ -125,7 +126,6 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
const mod = route.module;
if (route.type === 'page') {
// for page routes, we're going to serve some HTML
res.setHeader('Content-Type', 'text/html');
// preload main.js and current route
@@ -134,33 +134,34 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
const data = { params: req.params, query: req.query };
if (mod.preload) {
const promise = Promise.resolve(mod.preload(req)).then(preloaded => {
const serialized = try_serialize(preloaded);
Object.assign(data, preloaded);
let redirect: { statusCode: number, location: string };
let error;
return { rendered: mod.render(data), serialized };
});
Promise.resolve(
mod.preload ? mod.preload.call({
redirect: (statusCode: number, location: string) => {
redirect = { statusCode, location };
}
}, req) : {}
).then(preloaded => {
if (redirect) {
res.statusCode = redirect.statusCode;
res.setHeader('Location', redirect.location);
res.end();
return template.stream(req, res, {
scripts: promise.then(({ serialized }) => {
const main = `<script src='/client/${chunks.main}'></script>`;
return;
}
if (serialized) {
return `<script>__SAPPER__ = { preloaded: ${serialized} };</script>${main}`;
}
const serialized = try_serialize(preloaded); // TODO bail on non-POJOs
Object.assign(data, preloaded);
return main;
}),
html: promise.then(({ rendered }) => rendered.html),
head: promise.then(({ rendered }) => `<noscript id='sapper-head-start'></noscript>${rendered.head}<noscript id='sapper-head-end'></noscript>`),
styles: promise.then(({ rendered }) => (rendered.css && rendered.css.code ? `<style>${rendered.css.code}</style>` : ''))
});
} else {
const { html, head, css } = mod.render(data);
let scripts = `<script src='/client/${chunks.main}'></script>`;
scripts = `<script>__SAPPER__ = { preloaded: ${serialized} };</script>${scripts}`;
const page = template.render({
scripts: `<script src='/client/${chunks.main}'></script>`,
scripts,
html,
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
styles: (css && css.code ? `<style>${css.code}</style>` : '')
@@ -178,7 +179,7 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
body: page
});
}
}
});
}
else {

View File

@@ -74,17 +74,23 @@ function render(Component: ComponentConstructor, data: any, scroll: ScrollPositi
}
function prepare_route(Component: ComponentConstructor, data: RouteData) {
let redirect: { statusCode: number, location: string } = null;
if (!Component.preload) {
return { Component, data };
return { Component, data, redirect };
}
if (!component && window.__SAPPER__ && window.__SAPPER__.preloaded) {
return { Component, data: Object.assign(data, window.__SAPPER__.preloaded) };
return { Component, data: Object.assign(data, window.__SAPPER__.preloaded), redirect };
}
return Promise.resolve(Component.preload(data)).then(preloaded => {
return Promise.resolve(Component.preload.call({
redirect: (statusCode: number, location: string) => {
redirect = { statusCode, location };
}
}, data)).then(preloaded => {
Object.assign(data, preloaded)
return { Component, data };
return { Component, data, redirect };
});
}
@@ -110,7 +116,8 @@ function navigate(target: Target, id: number) {
const token = current_token = {};
return loaded.then(({ Component, data }) => {
return loaded.then(({ Component, data, redirect }) => {
if (redirect) return goto(redirect.location, { replaceState: true });
render(Component, data, scroll_history[id], token);
});
}

View File

@@ -3,6 +3,7 @@
<li><a href='/'>home</a></li>
<li><a href='/about'>about</a></li>
<li><a href='/slow-preload'>slow preload</a></li>
<li><a href='/redirect-from'>redirect</a></li>
<li><a rel=prefetch class='{{page === "blog" ? "selected" : ""}}' href='/blog'>blog</a></li>
</ul>
</nav>

View File

@@ -0,0 +1,7 @@
<script>
export default {
preload() {
this.redirect(301, '/redirect-to');
}
};
</script>

View File

@@ -0,0 +1 @@
<h1>redirected</h1>

View File

@@ -297,6 +297,33 @@ function run(env) {
assert.ok(matches);
});
});
it('redirects on server', () => {
return nightmare.goto(`${base}/redirect-from`)
.path()
.then(path => {
assert.equal(path, '/redirect-to');
})
.then(() => nightmare.page.title())
.then(title => {
assert.equal(title, 'redirected');
});
});
it('redirects on client', () => {
return nightmare.goto(base)
.wait('[href="/redirect-from"]')
.click('[href="/redirect-from"]')
.wait(200)
.path()
.then(path => {
assert.equal(path, '/redirect-to');
})
.then(() => nightmare.page.title())
.then(title => {
assert.equal(title, 'redirected');
});
});
});
describe('headers', () => {