From 0ea4a986e207238bf0ac1db841b2a5d5b567d84d Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Mon, 26 Apr 2021 16:42:11 -0400 Subject: [PATCH] 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 --- README.md | 10 ++ docs/cli.md | 2 + docs/dev.md | 51 +++++++++ examples/blog/snowpack.config.js | 3 + examples/kitchen-sink/snowpack.config.js | 3 + examples/snowpack/package-lock.json | 1 + examples/tailwindcss/snowpack.config.js | 3 + package.json | 2 +- src/dev.ts | 18 ++-- src/frontend/500.astro | 128 +++++++++++++++++++++++ src/runtime.ts | 9 ++ src/search.ts | 18 ++-- 12 files changed, 233 insertions(+), 15 deletions(-) create mode 100644 docs/dev.md create mode 100644 examples/blog/snowpack.config.js create mode 100644 examples/kitchen-sink/snowpack.config.js create mode 100644 examples/tailwindcss/snowpack.config.js create mode 100644 src/frontend/500.astro diff --git a/README.md b/README.md index eb55208eb0..7b3c6257eb 100644 --- a/README.md +++ b/README.md @@ -210,9 +210,18 @@ Now upload the contents of `/_site_` to your favorite static site host. 👉 [**Full API Reference**][docs-api] +## 👩🏽‍💻 CLI + +👉 [**Command Line Docs**][docs-cli] + +## 🏗 Development Server + +👉 [**Dev Server Docs**][docs-dev] + [config]: #%EF%B8%8F-configuration [docs-api]: ./docs/api.md [docs-collections]: ./docs/collections.md +[docs-dev]: ./docs/dev.md [docs-styling]: ./docs/styling.md [example-blog]: ./examples/blog [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-ric]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback [routing]: #-routing +[docs-cli]: ./docs/cli.md diff --git a/docs/cli.md b/docs/cli.md index c413323d57..67f0ae6311 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -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)). +See the [dev server](./dev.md) docs for more information on how the dev server works. + __Flags__ ##### `--port` diff --git a/docs/dev.md b/docs/dev.md new file mode 100644 index 0000000000..7b811d7d3f --- /dev/null +++ b/docs/dev.md @@ -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'); +--- + +{error} +``` + +A default error page is included with Astro so you will get pretty error messages even without adding a custom 500 page. \ No newline at end of file diff --git a/examples/blog/snowpack.config.js b/examples/blog/snowpack.config.js new file mode 100644 index 0000000000..5a800b10f0 --- /dev/null +++ b/examples/blog/snowpack.config.js @@ -0,0 +1,3 @@ +module.exports = { + workspaceRoot: '../../' +}; diff --git a/examples/kitchen-sink/snowpack.config.js b/examples/kitchen-sink/snowpack.config.js new file mode 100644 index 0000000000..5a800b10f0 --- /dev/null +++ b/examples/kitchen-sink/snowpack.config.js @@ -0,0 +1,3 @@ +module.exports = { + workspaceRoot: '../../' +}; diff --git a/examples/snowpack/package-lock.json b/examples/snowpack/package-lock.json index b802bc6876..22de838420 100644 --- a/examples/snowpack/package-lock.json +++ b/examples/snowpack/package-lock.json @@ -975,6 +975,7 @@ "astro": { "version": "file:../..", "requires": { + "@babel/code-frame": "^7.12.13", "@babel/generator": "^7.13.9", "@babel/parser": "^7.13.15", "@babel/traverse": "^7.13.15", diff --git a/examples/tailwindcss/snowpack.config.js b/examples/tailwindcss/snowpack.config.js new file mode 100644 index 0000000000..5a800b10f0 --- /dev/null +++ b/examples/tailwindcss/snowpack.config.js @@ -0,0 +1,3 @@ +module.exports = { + workspaceRoot: '../../' +}; diff --git a/package.json b/package.json index dbb929b3e3..f8a4895715 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ ], "scripts": { "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", "postbuild:parser": "echo '{ \"type\": \"commonjs\" }' > parser/package.json", "dev": "tsc --watch", diff --git a/src/dev.ts b/src/dev.ts index 505a994259..4ca8e28e94 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -72,7 +72,17 @@ export default async function dev(astroConfig: AstroConfig) { } } 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; } } @@ -85,9 +95,3 @@ export default async function dev(astroConfig: AstroConfig) { info(logging, 'dev server', `${green('Local:')} http://${hostname}:${port}/`); }); } - -/** Format error message */ -function formatErrorForBrowser(err: Error) { - // TODO make this pretty. - return err.toString(); -} diff --git a/src/frontend/500.astro b/src/frontend/500.astro new file mode 100644 index 0000000000..af7b901e9f --- /dev/null +++ b/src/frontend/500.astro @@ -0,0 +1,128 @@ +--- +import Prism from 'astro/components/Prism.astro'; +let title = 'Uh oh...'; + +const error = import.meta.request.url.searchParams.get('error'); +--- + + + + + Error 500 + + + + + + + +
+
+ + + +

500 Error {title}

+
+ +
+

Astro had some trouble loading this page.

+ +
+ +
+
+
+ + diff --git a/src/runtime.ts b/src/runtime.ts index 330ca013b5..a394d93c4e 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -195,11 +195,20 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro 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({ request: { host: fullurl.hostname, path: fullurl.pathname, href: fullurl.toString(), + url: requestURL }, children: [], props: { collection }, diff --git a/src/search.ts b/src/search.ts index d4ed73f96a..c141e4a773 100644 --- a/src/search.ts +++ b/src/search.ts @@ -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 { 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; -}