0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00

Support 500 pages in the dev server (#131)

* Support 500 pages

* Document custom 400/500 pages

* Remove search from any pages not the 500 page

* fix(kitchen-sink): add snowpack.config.js

* fix(examples): add snowpack.config.js

* style: redesign built-in 500 page

Co-authored-by: Nate Moore <nate@skypack.dev>
This commit is contained in:
Matthew Phillips 2021-04-26 16:42:11 -04:00 committed by GitHub
parent 87af0aead8
commit 0ea4a986e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 233 additions and 15 deletions

View file

@ -210,9 +210,18 @@ Now upload the contents of `/_site_` to your favorite static site host.
👉 [**Full API Reference**][docs-api] 👉 [**Full API Reference**][docs-api]
## 👩🏽‍💻 CLI
👉 [**Command Line Docs**][docs-cli]
## 🏗 Development Server
👉 [**Dev Server Docs**][docs-dev]
[config]: #%EF%B8%8F-configuration [config]: #%EF%B8%8F-configuration
[docs-api]: ./docs/api.md [docs-api]: ./docs/api.md
[docs-collections]: ./docs/collections.md [docs-collections]: ./docs/collections.md
[docs-dev]: ./docs/dev.md
[docs-styling]: ./docs/styling.md [docs-styling]: ./docs/styling.md
[example-blog]: ./examples/blog [example-blog]: ./examples/blog
[fetch-content]: ./docs/api.md#fetchcontent [fetch-content]: ./docs/api.md#fetchcontent
@ -220,3 +229,4 @@ Now upload the contents of `/_site_` to your favorite static site host.
[mdn-io]: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API [mdn-io]: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
[mdn-ric]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback [mdn-ric]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
[routing]: #-routing [routing]: #-routing
[docs-cli]: ./docs/cli.md

View file

@ -34,6 +34,8 @@ Print the help message and exit.
Runs the Astro development server. This starts an HTTP server that responds to requests for pages stored in `astro/pages` (or which folder is specified in your [configuration](../README.md##%EF%B8%8F-configuration)). Runs the Astro development server. This starts an HTTP server that responds to requests for pages stored in `astro/pages` (or which folder is specified in your [configuration](../README.md##%EF%B8%8F-configuration)).
See the [dev server](./dev.md) docs for more information on how the dev server works.
__Flags__ __Flags__
##### `--port` ##### `--port`

51
docs/dev.md Normal file
View file

@ -0,0 +1,51 @@
# Development Server
The development server comes as part of the Astro CLI. Start the server with:
```shell
astro dev
```
In your project root. You can specify an alternative
## Special routes
The dev server will serve the following special routes:
### /400
This is a custom __400__ status code page. You can add this route by adding a page component to your `astro/pages` folder:
```
├── astro/
│ ├── components/
│ └── pages/
│ └── 400.astro
```
For any URL you visit that doesn't have a corresponding page, the `400.astro` file will be used.
### /500
This is a custom __500__ status code page. You can add this route by adding a page component to your `astro/pages` folder:
```astro
├── astro/
│ ├── components/
│ └── pages/
│ └── 500.astro
```
This page is used any time an error occurs in the dev server.
The 500 page will receive an `error` query parameter which you can access with:
```
---
const error = import.meta.request.url.searchParams.get('error');
---
<strong>{error}</strong>
```
A default error page is included with Astro so you will get pretty error messages even without adding a custom 500 page.

View file

@ -0,0 +1,3 @@
module.exports = {
workspaceRoot: '../../'
};

View file

@ -0,0 +1,3 @@
module.exports = {
workspaceRoot: '../../'
};

View file

@ -975,6 +975,7 @@
"astro": { "astro": {
"version": "file:../..", "version": "file:../..",
"requires": { "requires": {
"@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.13.9", "@babel/generator": "^7.13.9",
"@babel/parser": "^7.13.15", "@babel/parser": "^7.13.15",
"@babel/traverse": "^7.13.15", "@babel/traverse": "^7.13.15",

View file

@ -0,0 +1,3 @@
module.exports = {
workspaceRoot: '../../'
};

View file

@ -22,7 +22,7 @@
], ],
"scripts": { "scripts": {
"build": "npm run build:core && npm run build:parser", "build": "npm run build:core && npm run build:parser",
"build:core": "tsc -p tsconfig.json", "build:core": "tsc -p tsconfig.json && cp src/frontend/500.astro lib/frontend/500.astro",
"build:parser": "tsc -p tsconfig.parser.json", "build:parser": "tsc -p tsconfig.parser.json",
"postbuild:parser": "echo '{ \"type\": \"commonjs\" }' > parser/package.json", "postbuild:parser": "echo '{ \"type\": \"commonjs\" }' > parser/package.json",
"dev": "tsc --watch", "dev": "tsc --watch",

View file

@ -72,7 +72,17 @@ export default async function dev(astroConfig: AstroConfig) {
} }
} }
res.statusCode = 500; res.statusCode = 500;
res.end(formatErrorForBrowser(result.error));
let errorResult = await runtime.load(`/500?error=${encodeURIComponent(result.error.stack || result.error.toString())}`);
if(errorResult.statusCode === 200) {
if (errorResult.contentType) {
res.setHeader('Content-Type', errorResult.contentType);
}
res.write(errorResult.contents);
} else {
res.write(result.error.toString());
}
res.end();
break; break;
} }
} }
@ -85,9 +95,3 @@ export default async function dev(astroConfig: AstroConfig) {
info(logging, 'dev server', `${green('Local:')} http://${hostname}:${port}/`); info(logging, 'dev server', `${green('Local:')} http://${hostname}:${port}/`);
}); });
} }
/** Format error message */
function formatErrorForBrowser(err: Error) {
// TODO make this pretty.
return err.toString();
}

128
src/frontend/500.astro Normal file
View file

@ -0,0 +1,128 @@
---
import Prism from 'astro/components/Prism.astro';
let title = 'Uh oh...';
const error = import.meta.request.url.searchParams.get('error');
---
<!doctype html>
<html lang="en">
<head>
<title>Error 500</title>
<link rel="preconnect"href="https://fonts.gstatic.com">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&family=IBM+Plex+Sans&display=swap">
<link rel="stylesheet" href="http://cdn.skypack.dev/prism-themes/themes/prism-material-dark.css">
<style>
* {
box-sizing: border-box;
margin: 0;
}
:global(:root) {
--font-sans: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
--font-mono: "IBM Plex Mono", Consolas, "Andale Mono WT", "Andale Mono",
"Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono",
"Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco,
"Courier New", Courier, monospace;
--color-gray-800: #1F2937;
--color-gray-500: #6B7280;
--color-gray-400: #9CA3AF;
--color-gray-100: #F3F4F6;
--color-red: #FF1639;
}
html, body {
width: 100vw;
height: 100%;
min-height: 100vh;
font-family: var(--font-sans);
font-weight: 400;
background: var(--color-gray-100);
text-align: center;
}
body {
display: grid;
place-content: center;
}
header {
display: flex;
flex-direction: column;
align-items: center;
font-family: var(--font-sans);
font-size: 2.5rem;
font-size: clamp(24px, calc(2vw + 1rem), 2.5rem);
}
header h1 {
margin: 0.25em;
margin-right: 0;
font-weight: 400;
letter-spacing: -2px;
line-height: 1;
}
header h1 .title {
color: var(--color-gray-400);
white-space: nowrap;
}
header svg {
margin-bottom: -0.125em;
color: var(--color-red);
}
p {
font-size: 1.75rem;
font-size: clamp(14px, calc(2vw + 0.5rem), 1.75rem);
flex: 1;
}
.error-message {
display: grid;
justify-content: center;
margin-top: 4rem;
}
.error-message :global(code[class*="language-"]) {
background: var(--color-gray-800);
}
.error-message :global(pre) {
margin: 0;
font-family: var(--font-mono);
font-size: 0.85rem;
background: var(--color-gray-800);
border-radius: 8px;
}
.error-message :global(.token.punctuation) {
color: var(--color-gray-400);
}
.error-message :global(.token.operator) {
color: var(--color-gray-400);
}
</style>
</head>
<body>
<main>
<header>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="1.75em" height="1.75em">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<h1><span class="error">500 Error </span><span class="title">{title}</span></h1>
</header>
<article>
<p>Astro had some trouble loading this page.</p>
<div class="error-message">
<Prism lang="shell" code={error} />
</div>
</article>
</main>
</body>
</html>

View file

@ -195,11 +195,20 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro
collection.data = data; collection.data = data;
} }
const requestURL = new URL(fullurl.toString());
// For first release query params are not passed to components.
// An exception is made for dev server specific routes.
if(reqPath !== '/500') {
requestURL.search = '';
}
let html = (await mod.exports.__renderPage({ let html = (await mod.exports.__renderPage({
request: { request: {
host: fullurl.hostname, host: fullurl.hostname,
path: fullurl.pathname, path: fullurl.pathname,
href: fullurl.toString(), href: fullurl.toString(),
url: requestURL
}, },
children: [], children: [],
props: { collection }, props: { collection },

View file

@ -91,6 +91,17 @@ export function searchForPage(url: URL, astroRoot: URL): SearchResult {
} }
} }
if(reqPath === '/500') {
return {
statusCode: 200,
location: {
fileURL: new URL('./frontend/500.astro', import.meta.url),
snowpackURL: `/_astro_internal/500.astro.js`
},
pathname: reqPath
};
}
return { return {
statusCode: 404, statusCode: 404,
}; };
@ -128,10 +139,3 @@ function loadCollection(url: string, astroRoot: URL): { currentPage?: number; lo
} }
} }
} }
/** convert a value to a number, if possible */
function maybeNum(val: string): string | number {
const num = parseFloat(val);
if (num.toString() === val) return num;
return val;
}