diff --git a/runtime/src/app/app.ts b/runtime/src/app/app.ts index 2de70527b..f819ba1aa 100644 --- a/runtime/src/app/app.ts +++ b/runtime/src/app/app.ts @@ -10,6 +10,7 @@ import { ComponentLoader, ComponentConstructor, Route, + Query, Page } from './types'; import goto from './goto'; @@ -80,6 +81,20 @@ export { _history as history }; export const scroll_history: Record = {}; +export function extract_query(search: string) { + const query = Object.create(null); + if (search.length > 0) { + search.slice(1).split('&').forEach(searchParam => { + let [, key, value] = /([^=]*)(?:=(.*))?/.exec(decodeURIComponent(searchParam)); + value = (value || '').replace(/\+/g, ' '); + if (typeof query[key] === 'string') query[key] = [query[key]]; + if (typeof query[key] === 'object') (query[key] as string[]).push(value); + else query[key] = value; + }); + } + return query; +} + export function select_target(url: URL): Target { if (url.origin !== location.origin) return null; if (!url.pathname.startsWith(initial_data.baseUrl)) return null; @@ -93,18 +108,9 @@ export function select_target(url: URL): Target { const route = routes[i]; const match = route.pattern.exec(path); - if (match) { - const query: Record = Object.create(null); - if (url.search.length > 0) { - url.search.slice(1).split('&').forEach(searchParam => { - let [, key, value] = /([^=]*)(?:=(.*))?/.exec(decodeURIComponent(searchParam)); - value = (value || '').replace(/\+/g, ' '); - if (typeof query[key] === 'string') query[key] = [query[key]]; - if (typeof query[key] === 'object') (query[key] as string[]).push(value); - else query[key] = value; - }); - } + if (match) { + const query: Query = extract_query(url.search); const part = route.parts[route.parts.length - 1]; const params = part.params ? part.params(match) : {}; @@ -115,6 +121,35 @@ export function select_target(url: URL): Target { } } +export function handle_error(url: URL) { + const { pathname, search } = location; + const { session, preloaded, status, error } = initial_data; + + if (!root_preloaded) { + root_preloaded = preloaded && preloaded[0] + } + + const props = { + error, + status, + session, + level0: { + props: root_preloaded + }, + level1: { + props: { + status, + error + }, + component: ErrorComponent + }, + segments: preloaded + + } + const query = extract_query(search); + render(null, [], props, { path: pathname, query, params: {} }); +} + export function scroll_state() { return { x: pageXOffset, diff --git a/runtime/src/app/start/index.ts b/runtime/src/app/start/index.ts index e371d83b2..c68f371e7 100644 --- a/runtime/src/app/start/index.ts +++ b/runtime/src/app/start/index.ts @@ -6,6 +6,7 @@ import { scroll_history, scroll_state, select_target, + handle_error, set_target, uid, set_uid, @@ -34,10 +35,12 @@ export default function start(opts: { history.replaceState({ id: uid }, '', href); - if (!initial_data.error) { - const target = select_target(new URL(location.href)); - if (target) return navigate(target, uid, false, hash); - } + const url = new URL(location.href); + + if (initial_data.error) return handle_error(url); + + const target = select_target(url); + if (target) return navigate(target, uid, false, hash); }); } @@ -127,4 +130,4 @@ function handle_popstate(event: PopStateEvent) { set_cid(uid); history.replaceState({ id: cid }, '', location.href); } -} \ No newline at end of file +} diff --git a/runtime/src/server/middleware/get_page_handler.ts b/runtime/src/server/middleware/get_page_handler.ts index 75722f177..86762daa1 100644 --- a/runtime/src/server/middleware/get_page_handler.ts +++ b/runtime/src/server/middleware/get_page_handler.ts @@ -243,11 +243,12 @@ export function get_page_handler( preloaded: `[${preloaded.map(data => try_serialize(data)).join(',')}]`, session: session && try_serialize(session, err => { throw new Error(`Failed to serialize session data: ${err.message}`); - }) + }), + error: error && try_serialize(props.error) }; let script = `__SAPPER__={${[ - error && `error:1`, + error && `error:${serialized.error},status:${status}`, `baseUrl:"${req.baseUrl}"`, serialized.preloaded && `preloaded:${serialized.preloaded}`, serialized.session && `session:${serialized.session}` diff --git a/src/cli.ts b/src/cli.ts index 5ebacc96f..e0b5bace4 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -292,4 +292,4 @@ async function _build( console.log(event.result.print()); } }); -} \ No newline at end of file +} diff --git a/test/apps/errors/src/routes/_error.svelte b/test/apps/errors/src/routes/_error.svelte index 4cd55d28d..d76724e3e 100644 --- a/test/apps/errors/src/routes/_error.svelte +++ b/test/apps/errors/src/routes/_error.svelte @@ -1,3 +1,17 @@ + +

{status}

-

{error.message}

\ No newline at end of file +

{mounted}

+ +

{error.message}

diff --git a/test/apps/errors/test.ts b/test/apps/errors/test.ts index 539587d40..d69470c99 100644 --- a/test/apps/errors/test.ts +++ b/test/apps/errors/test.ts @@ -112,6 +112,17 @@ describe('errors', function() { ); }); + it('execute error page hooks', async () => { + await page.goto(`${base}/some-throw-page`); + await start(); + await wait(50); + + assert.equal( + await page.$eval('h2', node => node.textContent), + 'success' + ); + }) + it('does not serve error page for async non-page error', async () => { await page.goto(`${base}/async-throw.json`); @@ -134,4 +145,4 @@ describe('errors', function() { await wait(50); assert.equal(await title(), 'No error here'); }); -}); \ No newline at end of file +});