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

Commit 4ca8195

Browse files
authored
Merge pull request #127 from sveltejs/gh-83
implement this.redirect in preload
2 parents f8ea9eb + cb12231 commit 4ca8195

File tree

12 files changed

+249
-146
lines changed

12 files changed

+249
-146
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"sander": "^0.6.0",
3737
"serialize-javascript": "^1.4.0",
3838
"url-parse": "^1.2.0",
39+
"wait-port": "^0.2.2",
3940
"walk-sync": "^0.3.2",
4041
"webpack": "^3.10.0"
4142
},

src/cli/utils.ts

+20-14
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1-
import * as net from 'net';
1+
import waitPort from 'wait-port';
22

33
export function wait_for_port(port: number, cb: () => void) {
4-
const socket = net.createConnection({ port }, () => {
5-
cb();
6-
socket.destroy();
7-
});
4+
waitPort({ port }).then(cb);
5+
}
86

9-
socket.on('error', err => {
10-
setTimeout(() => {
11-
wait_for_port(port, cb);
12-
}, 100);
13-
});
7+
// import * as net from 'net';
148

15-
setTimeout(() => {
16-
socket.destroy();
17-
}, 100);
18-
}
9+
// export function wait_for_port(port: number, cb: () => void) {
10+
// const socket = net.createConnection(port, 'localhost', () => {
11+
// cb();
12+
// socket.destroy();
13+
// });
14+
15+
// socket.on('error', err => {
16+
// setTimeout(() => {
17+
// wait_for_port(port, cb);
18+
// }, 100);
19+
// });
20+
21+
// setTimeout(() => {
22+
// socket.destroy();
23+
// }, 100);
24+
// }

src/core/create_template.ts

-53
Original file line numberDiff line numberDiff line change
@@ -34,59 +34,6 @@ export default function create_templates() {
3434
return template.replace(/%sapper\.(\w+)%/g, (match, key) => {
3535
return key in data ? data[key] : '';
3636
});
37-
},
38-
stream: (req: any, res: any, data: Record<string, string | Promise<string>>) => {
39-
let i = 0;
40-
41-
let body = '';
42-
43-
function stream_inner(): Promise<void> {
44-
if (i >= template.length) {
45-
return;
46-
}
47-
48-
const start = template.indexOf('%sapper', i);
49-
50-
if (start === -1) {
51-
const chunk = template.slice(i);
52-
body += chunk;
53-
res.end(chunk);
54-
55-
if (process.send) {
56-
process.send({
57-
__sapper__: true,
58-
url: req.url,
59-
method: req.method,
60-
type: 'text/html',
61-
body
62-
});
63-
}
64-
65-
return;
66-
}
67-
68-
const chunk = template.slice(i, start);
69-
body += chunk;
70-
res.write(chunk);
71-
72-
const end = template.indexOf('%', start + 1);
73-
if (end === -1) {
74-
throw new Error(`Bad template`); // TODO validate ahead of time
75-
}
76-
77-
const tag = template.slice(start + 1, end);
78-
const match = /sapper\.(\w+)/.exec(tag);
79-
if (!match || !(match[1] in data)) throw new Error(`Bad template`); // TODO ditto
80-
81-
return Promise.resolve(data[match[1]]).then(chunk => {
82-
body += chunk;
83-
res.write(chunk);
84-
i = end + 1;
85-
return stream_inner();
86-
});
87-
}
88-
89-
return Promise.resolve().then(stream_inner);
9037
}
9138
};
9239
}

src/middleware/index.ts

+82-64
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type Assets = {
1919
}
2020

2121
type RouteObject = {
22+
id: string;
2223
type: 'page' | 'route';
2324
pattern: RegExp;
2425
params: (match: RegExpMatchArray) => Record<string, string>;
@@ -93,9 +94,7 @@ export default function middleware({ routes }: {
9394
}
9495
},
9596

96-
get_route_handler(client_info.assetsByChunkName, routes, template),
97-
98-
get_not_found_handler(client_info.assetsByChunkName, routes, template)
97+
get_route_handler(client_info.assetsByChunkName, routes, template)
9998
].filter(Boolean));
10099

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

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

125124
const mod = route.module;
126125

127126
if (route.type === 'page') {
128-
// for page routes, we're going to serve some HTML
129127
res.setHeader('Content-Type', 'text/html');
130128

131129
// preload main.js and current route
@@ -134,33 +132,44 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
134132

135133
const data = { params: req.params, query: req.query };
136134

137-
if (mod.preload) {
138-
const promise = Promise.resolve(mod.preload(req)).then(preloaded => {
139-
const serialized = try_serialize(preloaded);
140-
Object.assign(data, preloaded);
141-
142-
return { rendered: mod.render(data), serialized };
143-
});
135+
let redirect: { statusCode: number, location: string };
136+
let error: { statusCode: number, message: Error | string };
137+
138+
Promise.resolve(
139+
mod.preload ? mod.preload.call({
140+
redirect: (statusCode: number, location: string) => {
141+
redirect = { statusCode, location };
142+
},
143+
error: (statusCode: number, message: Error | string) => {
144+
error = { statusCode, message };
145+
}
146+
}, req) : {}
147+
).catch(err => {
148+
error = { statusCode: 500, message: err };
149+
}).then(preloaded => {
150+
if (redirect) {
151+
res.statusCode = redirect.statusCode;
152+
res.setHeader('Location', redirect.location);
153+
res.end();
154+
155+
return;
156+
}
144157

145-
return template.stream(req, res, {
146-
scripts: promise.then(({ serialized }) => {
147-
const main = `<script src='/client/${chunks.main}'></script>`;
158+
if (error) {
159+
handle_error(req, res, error.statusCode, error.message);
160+
return;
161+
}
148162

149-
if (serialized) {
150-
return `<script>__SAPPER__ = { preloaded: ${serialized} };</script>${main}`;
151-
}
163+
const serialized = try_serialize(preloaded); // TODO bail on non-POJOs
164+
Object.assign(data, preloaded);
152165

153-
return main;
154-
}),
155-
html: promise.then(({ rendered }) => rendered.html),
156-
head: promise.then(({ rendered }) => `<noscript id='sapper-head-start'></noscript>${rendered.head}<noscript id='sapper-head-end'></noscript>`),
157-
styles: promise.then(({ rendered }) => (rendered.css && rendered.css.code ? `<style>${rendered.css.code}</style>` : ''))
158-
});
159-
} else {
160166
const { html, head, css } = mod.render(data);
161167

168+
let scripts = `<script src='/client/${chunks.main}'></script>`;
169+
scripts = `<script>__SAPPER__ = { preloaded: ${serialized} };</script>${scripts}`;
170+
162171
const page = template.render({
163-
scripts: `<script src='/client/${chunks.main}'></script>`,
172+
scripts,
164173
html,
165174
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
166175
styles: (css && css.code ? `<style>${css.code}</style>` : '')
@@ -178,7 +187,7 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
178187
body: page
179188
});
180189
}
181-
}
190+
});
182191
}
183192

184193
else {
@@ -219,60 +228,55 @@ function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]
219228
};
220229
}
221230

222-
handler(req, res, next);
231+
handler(req, res, () => {
232+
handle_not_found(req, res, 404, 'Not found');
233+
});
223234
} else {
224235
// no matching handler for method — 404
225-
next();
236+
handle_not_found(req, res, 404, 'Not found');
226237
}
227238
}
228239
}
229240

230-
const error_route = routes.find((route: RouteObject) => route.error === '5xx')
241+
const not_found_route = routes.find((route: RouteObject) => route.error === '4xx');
231242

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

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

240-
// no matching route — 404
241-
next();
242-
} catch (error) {
243-
console.error(error);
249+
const rendered = not_found_route ? not_found_route.module.render({
250+
status: 404,
251+
error
252+
}) : { head: '', css: null, html: error.message };
244253

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

248-
const rendered = error_route ? error_route.module.render({
249-
status: 500,
250-
error
251-
}) : { head: '', css: null, html: 'Not found' };
256+
res.end(template.render({
257+
scripts: `<script src='/client/${chunks.main}'></script>`,
258+
html,
259+
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
260+
styles: (css && css.code ? `<style>${css.code}</style>` : '')
261+
}));
262+
}
252263

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

255-
res.end(template.render({
256-
scripts: `<script src='/client/${chunks.main}'></script>`,
257-
html,
258-
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
259-
styles: (css && css.code ? `<style>${css.code}</style>` : '')
260-
}));
266+
function handle_error(req: Req, res: ServerResponse, statusCode: number, message: Error | string) {
267+
if (statusCode >= 400 && statusCode < 500) {
268+
return handle_not_found(req, res, statusCode, message);
261269
}
262-
};
263-
}
264270

265-
function get_not_found_handler(chunks: Record<string, string>, routes: RouteObject[], template: Template) {
266-
const route = routes.find((route: RouteObject) => route.error === '4xx');
267-
268-
return function handle_not_found(req: Req, res: ServerResponse) {
269-
res.statusCode = 404;
271+
res.statusCode = statusCode;
270272
res.setHeader('Content-Type', 'text/html');
271273

272-
const rendered = route ? route.module.render({
273-
status: 404,
274-
message: 'Not found'
275-
}) : { head: '', css: null, html: 'Not found' };
274+
const error = message instanceof Error ? message : new Error(message);
275+
276+
const rendered = error_route ? error_route.module.render({
277+
status: 500,
278+
error
279+
}) : { head: '', css: null, html: `Internal server error: ${error.message}` };
276280

277281
const { head, css, html } = rendered;
278282

@@ -282,6 +286,20 @@ function get_not_found_handler(chunks: Record<string, string>, routes: RouteObje
282286
head: `<noscript id='sapper-head-start'></noscript>${head}<noscript id='sapper-head-end'></noscript>`,
283287
styles: (css && css.code ? `<style>${css.code}</style>` : '')
284288
}));
289+
}
290+
291+
return function find_route(req: Req, res: ServerResponse, next: () => void) {
292+
const url = req.pathname;
293+
294+
try {
295+
for (const route of routes) {
296+
if (!route.error && route.pattern.test(url)) return handle_route(route, req, res);
297+
}
298+
299+
handle_not_found(req, res, 404, 'Not found');
300+
} catch (error) {
301+
handle_error(req, res, 500, error);
302+
}
285303
};
286304
}
287305

src/runtime/index.ts

+33-5
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,41 @@ function render(Component: ComponentConstructor, data: any, scroll: ScrollPositi
7474
}
7575

7676
function prepare_route(Component: ComponentConstructor, data: RouteData) {
77+
let redirect: { statusCode: number, location: string } = null;
78+
let error: { statusCode: number, message: Error | string } = null;
79+
7780
if (!Component.preload) {
78-
return { Component, data };
81+
return { Component, data, redirect, error };
7982
}
8083

8184
if (!component && window.__SAPPER__ && window.__SAPPER__.preloaded) {
82-
return { Component, data: Object.assign(data, window.__SAPPER__.preloaded) };
85+
return { Component, data: Object.assign(data, window.__SAPPER__.preloaded), redirect, error };
8386
}
8487

85-
return Promise.resolve(Component.preload(data)).then(preloaded => {
88+
return Promise.resolve(Component.preload.call({
89+
redirect: (statusCode: number, location: string) => {
90+
redirect = { statusCode, location };
91+
},
92+
error: (statusCode: number, message: Error | string) => {
93+
error = { statusCode, message };
94+
}
95+
}, data)).catch(err => {
96+
error = { statusCode: 500, message: err };
97+
}).then(preloaded => {
98+
if (error) {
99+
const route = error.statusCode >= 400 && error.statusCode < 500
100+
? errors['4xx']
101+
: errors['5xx'];
102+
103+
return route.load().then(({ default: Component }: { default: ComponentConstructor }) => {
104+
const err = error.message instanceof Error ? error.message : new Error(error.message);
105+
Object.assign(data, { status: error.statusCode, error: err });
106+
return { Component, data, redirect: null };
107+
});
108+
}
109+
86110
Object.assign(data, preloaded)
87-
return { Component, data };
111+
return { Component, data, redirect };
88112
});
89113
}
90114

@@ -110,7 +134,11 @@ function navigate(target: Target, id: number) {
110134

111135
const token = current_token = {};
112136

113-
return loaded.then(({ Component, data }) => {
137+
return loaded.then(({ Component, data, redirect }) => {
138+
if (redirect) {
139+
return goto(redirect.location, { replaceState: true });
140+
}
141+
114142
render(Component, data, scroll_history[id], token);
115143
});
116144
}

0 commit comments

Comments
 (0)