Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

implement this.redirect in preload #127

Merged
merged 4 commits into from
Feb 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"sander": "^0.6.0",
"serialize-javascript": "^1.4.0",
"url-parse": "^1.2.0",
"wait-port": "^0.2.2",
"walk-sync": "^0.3.2",
"webpack": "^3.10.0"
},
Expand Down
34 changes: 20 additions & 14 deletions src/cli/utils.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import * as net from 'net';
import waitPort from 'wait-port';

export function wait_for_port(port: number, cb: () => void) {
const socket = net.createConnection({ port }, () => {
cb();
socket.destroy();
});
waitPort({ port }).then(cb);
}

socket.on('error', err => {
setTimeout(() => {
wait_for_port(port, cb);
}, 100);
});
// import * as net from 'net';

setTimeout(() => {
socket.destroy();
}, 100);
}
// export function wait_for_port(port: number, cb: () => void) {
// const socket = net.createConnection(port, 'localhost', () => {
// cb();
// socket.destroy();
// });

// socket.on('error', err => {
// setTimeout(() => {
// wait_for_port(port, cb);
// }, 100);
// });

// setTimeout(() => {
// socket.destroy();
// }, 100);
// }
53 changes: 0 additions & 53 deletions src/core/create_template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};
}
146 changes: 82 additions & 64 deletions src/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Assets = {
}

type RouteObject = {
id: string;
type: 'page' | 'route';
pattern: RegExp;
params: (match: RegExpMatchArray) => Record<string, string>;
Expand Down Expand Up @@ -93,9 +94,7 @@ export default function middleware({ routes }: {
}
},

get_route_handler(client_info.assetsByChunkName, routes, template),

get_not_found_handler(client_info.assetsByChunkName, routes, template)
get_route_handler(client_info.assetsByChunkName, routes, template)
].filter(Boolean));

return middleware;
Expand All @@ -119,13 +118,12 @@ function get_asset_handler({ pathname, type, cache, body }: {
const resolved = Promise.resolve();

function get_route_handler(chunks: Record<string, string>, routes: RouteObject[], template: Template) {
function handle_route(route: RouteObject, req: Req, res: ServerResponse, next: () => void) {
function handle_route(route: RouteObject, req: Req, res: ServerResponse) {
req.params = route.params(route.pattern.exec(req.pathname));

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
Expand All @@ -134,33 +132,44 @@ 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);

return { rendered: mod.render(data), serialized };
});
let redirect: { statusCode: number, location: string };
let error: { statusCode: number, message: Error | string };

Promise.resolve(
mod.preload ? mod.preload.call({
redirect: (statusCode: number, location: string) => {
redirect = { statusCode, location };
},
error: (statusCode: number, message: Error | string) => {
error = { statusCode, message };
}
}, req) : {}
).catch(err => {
error = { statusCode: 500, message: err };
}).then(preloaded => {
if (redirect) {
res.statusCode = redirect.statusCode;
res.setHeader('Location', redirect.location);
res.end();

return;
}

return template.stream(req, res, {
scripts: promise.then(({ serialized }) => {
const main = `<script src='/client/${chunks.main}'></script>`;
if (error) {
handle_error(req, res, error.statusCode, error.message);
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>` : '')
Expand All @@ -178,7 +187,7 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
body: page
});
}
}
});
}

else {
Expand Down Expand Up @@ -219,60 +228,55 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
};
}

handler(req, res, next);
handler(req, res, () => {
handle_not_found(req, res, 404, 'Not found');
});
} else {
// no matching handler for method — 404
next();
handle_not_found(req, res, 404, 'Not found');
}
}
}

const error_route = routes.find((route: RouteObject) => route.error === '5xx')
const not_found_route = routes.find((route: RouteObject) => route.error === '4xx');

return function find_route(req: Req, res: ServerResponse, next: () => void) {
const url = req.pathname;
function handle_not_found(req: Req, res: ServerResponse, statusCode: number, message: Error | string) {
res.statusCode = statusCode;
res.setHeader('Content-Type', 'text/html');

try {
for (const route of routes) {
if (!route.error && route.pattern.test(url)) return handle_route(route, req, res, next);
}
const error = message instanceof Error ? message : new Error(message);

// no matching route — 404
next();
} catch (error) {
console.error(error);
const rendered = not_found_route ? not_found_route.module.render({
status: 404,
error
}) : { head: '', css: null, html: error.message };

res.statusCode = 500;
res.setHeader('Content-Type', 'text/html');
const { head, css, html } = rendered;

const rendered = error_route ? error_route.module.render({
status: 500,
error
}) : { head: '', css: null, html: 'Not found' };
res.end(template.render({
scripts: `<script src='/client/${chunks.main}'></script>`,
html,
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
styles: (css && css.code ? `<style>${css.code}</style>` : '')
}));
}

const { head, css, html } = rendered;
const error_route = routes.find((route: RouteObject) => route.error === '5xx');

res.end(template.render({
scripts: `<script src='/client/${chunks.main}'></script>`,
html,
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
styles: (css && css.code ? `<style>${css.code}</style>` : '')
}));
function handle_error(req: Req, res: ServerResponse, statusCode: number, message: Error | string) {
if (statusCode >= 400 && statusCode < 500) {
return handle_not_found(req, res, statusCode, message);
}
};
}

function get_not_found_handler(chunks: Record<string, string>, routes: RouteObject[], template: Template) {
const route = routes.find((route: RouteObject) => route.error === '4xx');

return function handle_not_found(req: Req, res: ServerResponse) {
res.statusCode = 404;
res.statusCode = statusCode;
res.setHeader('Content-Type', 'text/html');

const rendered = route ? route.module.render({
status: 404,
message: 'Not found'
}) : { head: '', css: null, html: 'Not found' };
const error = message instanceof Error ? message : new Error(message);

const rendered = error_route ? error_route.module.render({
status: 500,
error
}) : { head: '', css: null, html: `Internal server error: ${error.message}` };

const { head, css, html } = rendered;

Expand All @@ -282,6 +286,20 @@ function get_not_found_handler(chunks: Record<string, string>, routes: RouteObje
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
styles: (css && css.code ? `<style>${css.code}</style>` : '')
}));
}

return function find_route(req: Req, res: ServerResponse, next: () => void) {
const url = req.pathname;

try {
for (const route of routes) {
if (!route.error && route.pattern.test(url)) return handle_route(route, req, res);
}

handle_not_found(req, res, 404, 'Not found');
} catch (error) {
handle_error(req, res, 500, error);
}
};
}

Expand Down
38 changes: 33 additions & 5 deletions src/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,41 @@ function render(Component: ComponentConstructor, data: any, scroll: ScrollPositi
}

function prepare_route(Component: ComponentConstructor, data: RouteData) {
let redirect: { statusCode: number, location: string } = null;
let error: { statusCode: number, message: Error | string } = null;

if (!Component.preload) {
return { Component, data };
return { Component, data, redirect, error };
}

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, error };
}

return Promise.resolve(Component.preload(data)).then(preloaded => {
return Promise.resolve(Component.preload.call({
redirect: (statusCode: number, location: string) => {
redirect = { statusCode, location };
},
error: (statusCode: number, message: Error | string) => {
error = { statusCode, message };
}
}, data)).catch(err => {
error = { statusCode: 500, message: err };
}).then(preloaded => {
if (error) {
const route = error.statusCode >= 400 && error.statusCode < 500
? errors['4xx']
: errors['5xx'];

return route.load().then(({ default: Component }: { default: ComponentConstructor }) => {
const err = error.message instanceof Error ? error.message : new Error(error.message);
Object.assign(data, { status: error.statusCode, error: err });
return { Component, data, redirect: null };
});
}

Object.assign(data, preloaded)
return { Component, data };
return { Component, data, redirect };
});
}

Expand All @@ -110,7 +134,11 @@ 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);
});
}
Expand Down
Loading