mirror of
https://github.com/kevin-DL/sapper.git
synced 2026-01-19 22:05:20 +00:00
implement this.redirect in preload (#83)
This commit is contained in:
@@ -34,59 +34,6 @@ export default function create_templates() {
|
|||||||
return template.replace(/%sapper\.(\w+)%/g, (match, key) => {
|
return template.replace(/%sapper\.(\w+)%/g, (match, key) => {
|
||||||
return key in data ? data[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);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@ type Assets = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RouteObject = {
|
type RouteObject = {
|
||||||
|
id: string;
|
||||||
type: 'page' | 'route';
|
type: 'page' | 'route';
|
||||||
pattern: RegExp;
|
pattern: RegExp;
|
||||||
params: (match: RegExpMatchArray) => Record<string, string>;
|
params: (match: RegExpMatchArray) => Record<string, string>;
|
||||||
@@ -125,7 +126,6 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
|
|||||||
const mod = route.module;
|
const mod = route.module;
|
||||||
|
|
||||||
if (route.type === 'page') {
|
if (route.type === 'page') {
|
||||||
// for page routes, we're going to serve some HTML
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
|
||||||
// preload main.js and current route
|
// 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 };
|
const data = { params: req.params, query: req.query };
|
||||||
|
|
||||||
if (mod.preload) {
|
let redirect: { statusCode: number, location: string };
|
||||||
const promise = Promise.resolve(mod.preload(req)).then(preloaded => {
|
let error;
|
||||||
const serialized = try_serialize(preloaded);
|
|
||||||
Object.assign(data, preloaded);
|
|
||||||
|
|
||||||
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, {
|
return;
|
||||||
scripts: promise.then(({ serialized }) => {
|
}
|
||||||
const main = `<script src='/client/${chunks.main}'></script>`;
|
|
||||||
|
|
||||||
if (serialized) {
|
const serialized = try_serialize(preloaded); // TODO bail on non-POJOs
|
||||||
return `<script>__SAPPER__ = { preloaded: ${serialized} };</script>${main}`;
|
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);
|
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({
|
const page = template.render({
|
||||||
scripts: `<script src='/client/${chunks.main}'></script>`,
|
scripts,
|
||||||
html,
|
html,
|
||||||
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
|
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
|
||||||
styles: (css && css.code ? `<style>${css.code}</style>` : '')
|
styles: (css && css.code ? `<style>${css.code}</style>` : '')
|
||||||
@@ -178,7 +179,7 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
|
|||||||
body: page
|
body: page
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -74,17 +74,23 @@ function render(Component: ComponentConstructor, data: any, scroll: ScrollPositi
|
|||||||
}
|
}
|
||||||
|
|
||||||
function prepare_route(Component: ComponentConstructor, data: RouteData) {
|
function prepare_route(Component: ComponentConstructor, data: RouteData) {
|
||||||
|
let redirect: { statusCode: number, location: string } = null;
|
||||||
|
|
||||||
if (!Component.preload) {
|
if (!Component.preload) {
|
||||||
return { Component, data };
|
return { Component, data, redirect };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!component && window.__SAPPER__ && window.__SAPPER__.preloaded) {
|
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)
|
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 = {};
|
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);
|
render(Component, data, scroll_history[id], token);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<li><a href='/'>home</a></li>
|
<li><a href='/'>home</a></li>
|
||||||
<li><a href='/about'>about</a></li>
|
<li><a href='/about'>about</a></li>
|
||||||
<li><a href='/slow-preload'>slow preload</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>
|
<li><a rel=prefetch class='{{page === "blog" ? "selected" : ""}}' href='/blog'>blog</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
7
test/app/routes/redirect-from.html
Normal file
7
test/app/routes/redirect-from.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
preload() {
|
||||||
|
this.redirect(301, '/redirect-to');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
1
test/app/routes/redirect-to.html
Normal file
1
test/app/routes/redirect-to.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<h1>redirected</h1>
|
||||||
@@ -297,6 +297,33 @@ function run(env) {
|
|||||||
assert.ok(matches);
|
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', () => {
|
describe('headers', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user