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

Commit

Permalink
Merge pull request #620 from cudr/error_page_hooks
Browse files Browse the repository at this point in the history
Error page lifecycle
  • Loading branch information
Rich-Harris authored Apr 27, 2019
2 parents 0862d0e + 9bb8bfa commit 96a0682
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 21 deletions.
57 changes: 46 additions & 11 deletions runtime/src/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ComponentLoader,
ComponentConstructor,
Route,
Query,
Page
} from './types';
import goto from './goto';
Expand Down Expand Up @@ -80,6 +81,20 @@ export { _history as history };

export const scroll_history: Record<string, ScrollPosition> = {};

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] = [<string>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;
Expand All @@ -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<string, string | string[]> = 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] = [<string>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) : {};

Expand All @@ -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,
Expand Down
13 changes: 8 additions & 5 deletions runtime/src/app/start/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
scroll_history,
scroll_state,
select_target,
handle_error,
set_target,
uid,
set_uid,
Expand Down Expand Up @@ -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);
});
}

Expand Down Expand Up @@ -127,4 +130,4 @@ function handle_popstate(event: PopStateEvent) {
set_cid(uid);
history.replaceState({ id: cid }, '', location.href);
}
}
}
5 changes: 3 additions & 2 deletions runtime/src/server/middleware/get_page_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`
Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,4 +292,4 @@ async function _build(
console.log(event.result.print());
}
});
}
}
16 changes: 15 additions & 1 deletion test/apps/errors/src/routes/_error.svelte
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
<script>
import { onMount, onDestroy } from 'svelte';
export let status, error = {};
let mounted = false;
onMount(() => {
mounted = 'success';
})
</script>

<h1>{status}</h1>

<p>{error.message}</p>
<h2>{mounted}</h2>

<p>{error.message}</p>
13 changes: 12 additions & 1 deletion test/apps/errors/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`);

Expand All @@ -134,4 +145,4 @@ describe('errors', function() {
await wait(50);
assert.equal(await title(), 'No error here');
});
});
});

0 comments on commit 96a0682

Please sign in to comment.