diff --git a/src/middleware/index.ts b/src/middleware/index.ts index c55ba9d..cd2f146 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -94,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; @@ -120,7 +118,7 @@ function get_asset_handler({ pathname, type, cache, body }: { const resolved = Promise.resolve(); function get_route_handler(chunks: Record, 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; @@ -135,15 +133,20 @@ function get_route_handler(chunks: Record, routes: RouteObject[] const data = { params: req.params, query: req.query }; let redirect: { statusCode: number, location: string }; - let error; + 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) : {} - ).then(preloaded => { + ).catch(err => { + error = { statusCode: 500, message: err }; + }).then(preloaded => { if (redirect) { res.statusCode = redirect.statusCode; res.setHeader('Location', redirect.location); @@ -152,6 +155,11 @@ function get_route_handler(chunks: Record, routes: RouteObject[] return; } + if (error) { + handle_error(req, res, error.statusCode, error.message); + return; + } + const serialized = try_serialize(preloaded); // TODO bail on non-POJOs Object.assign(data, preloaded); @@ -220,60 +228,28 @@ function get_route_handler(chunks: Record, 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; - - try { - for (const route of routes) { - if (!route.error && route.pattern.test(url)) return handle_route(route, req, res, next); - } - - // no matching route — 404 - next(); - } catch (error) { - console.error(error); - - res.statusCode = 500; - res.setHeader('Content-Type', 'text/html'); - - const rendered = error_route ? error_route.module.render({ - status: 500, - error - }) : { head: '', css: null, html: 'Not found' }; - - const { head, css, html } = rendered; - - res.end(template.render({ - scripts: ``, - html, - head: `${head}`, - styles: (css && css.code ? `` : '') - })); - } - }; -} - -function get_not_found_handler(chunks: Record, 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; + function handle_not_found(req: Req, res: ServerResponse, statusCode: number, message: Error | string) { + res.statusCode = statusCode; res.setHeader('Content-Type', 'text/html'); - const rendered = route ? route.module.render({ + const error = message instanceof Error ? message : new Error(message); + + const rendered = not_found_route ? not_found_route.module.render({ status: 404, - message: 'Not found' - }) : { head: '', css: null, html: 'Not found' }; + error + }) : { head: '', css: null, html: error.message }; const { head, css, html } = rendered; @@ -283,6 +259,47 @@ function get_not_found_handler(chunks: Record, routes: RouteObje head: `${head}`, styles: (css && css.code ? `` : '') })); + } + + const error_route = routes.find((route: RouteObject) => route.error === '5xx'); + + 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); + } + + res.statusCode = statusCode; + res.setHeader('Content-Type', 'text/html'); + + 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; + + res.end(template.render({ + scripts: ``, + html, + head: `${head}`, + styles: (css && css.code ? `` : '') + })); + } + + 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); + } }; } diff --git a/src/runtime/index.ts b/src/runtime/index.ts index 7ac8f8e..92fdd0e 100644 --- a/src/runtime/index.ts +++ b/src/runtime/index.ts @@ -75,20 +75,38 @@ 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, redirect }; + return { Component, data, redirect, error }; } if (!component && window.__SAPPER__ && window.__SAPPER__.preloaded) { - return { Component, data: Object.assign(data, window.__SAPPER__.preloaded), redirect }; + return { Component, data: Object.assign(data, window.__SAPPER__.preloaded), redirect, error }; } return Promise.resolve(Component.preload.call({ redirect: (statusCode: number, location: string) => { redirect = { statusCode, location }; + }, + error: (statusCode: number, message: Error | string) => { + error = { statusCode, message }; } - }, data)).then(preloaded => { + }, 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, redirect }; }); @@ -117,7 +135,10 @@ function navigate(target: Target, id: number) { const token = current_token = {}; return loaded.then(({ Component, data, redirect }) => { - if (redirect) return goto(redirect.location, { replaceState: true }); + if (redirect) { + return goto(redirect.location, { replaceState: true }); + } + render(Component, data, scroll_history[id], token); }); } diff --git a/test/app/routes/4xx.html b/test/app/routes/4xx.html index 119e9fc..837db65 100644 --- a/test/app/routes/4xx.html +++ b/test/app/routes/4xx.html @@ -3,8 +3,8 @@ -

{{status}}

-

{{message}}

+

Not found

+

{{error.message}}