summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/wild-falcons-sparkle.md5
-rw-r--r--packages/astro/src/runtime/server/render/astro.ts18
-rw-r--r--packages/astro/test/units/dev/hydration.test.js53
3 files changed, 75 insertions, 1 deletions
diff --git a/.changeset/wild-falcons-sparkle.md b/.changeset/wild-falcons-sparkle.md
new file mode 100644
index 000000000..a6ad3290b
--- /dev/null
+++ b/.changeset/wild-falcons-sparkle.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Prevent dev from crashing when there are errors in template
diff --git a/packages/astro/src/runtime/server/render/astro.ts b/packages/astro/src/runtime/server/render/astro.ts
index d6da03007..9d28a6f81 100644
--- a/packages/astro/src/runtime/server/render/astro.ts
+++ b/packages/astro/src/runtime/server/render/astro.ts
@@ -6,6 +6,7 @@ import { HTMLBytes, markHTMLString } from '../escape.js';
import { HydrationDirectiveProps } from '../hydration.js';
import { renderChild } from './any.js';
import { HTMLParts } from './common.js';
+import { isPromise } from '../util.js';
// In dev mode, check props and make sure they are valid for an Astro component
function validateComponentProps(props: any, displayName: string) {
@@ -26,10 +27,25 @@ function validateComponentProps(props: any, displayName: string) {
export class AstroComponent {
private htmlParts: TemplateStringsArray;
private expressions: any[];
+ private error: Error | undefined;
constructor(htmlParts: TemplateStringsArray, expressions: any[]) {
this.htmlParts = htmlParts;
- this.expressions = expressions;
+ this.error = undefined;
+ this.expressions = expressions.map(expression => {
+ // Wrap Promise expressions so we can catch errors
+ // There can only be 1 error that we rethrow from an Astro component,
+ // so this keeps track of whether or not we have already done so.
+ if(isPromise(expression)) {
+ return Promise.resolve(expression).catch(err => {
+ if(!this.error) {
+ this.error = err;
+ throw err;
+ }
+ });
+ }
+ return expression;
+ })
}
get [Symbol.toStringTag]() {
diff --git a/packages/astro/test/units/dev/hydration.test.js b/packages/astro/test/units/dev/hydration.test.js
new file mode 100644
index 000000000..d1ae0460c
--- /dev/null
+++ b/packages/astro/test/units/dev/hydration.test.js
@@ -0,0 +1,53 @@
+
+import { expect } from 'chai';
+
+import { runInContainer } from '../../../dist/core/dev/index.js';
+import { createFs, createRequestAndResponse } from '../test-utils.js';
+import svelte from '../../../../integrations/svelte/dist/index.js';
+import { defaultLogging } from '../../test-utils.js';
+
+const root = new URL('../../fixtures/alias/', import.meta.url);
+
+describe('dev container', () => {
+ it('should not crash when reassigning a hydrated component', async () => {
+ const fs = createFs(
+ {
+ '/src/pages/index.astro': `
+ ---
+ import Svelte from '../components/Client.svelte';
+ const Foo = Svelte;
+ const Bar = Svelte;
+ ---
+ <html>
+ <head><title>testing</title></head>
+ <body>
+ <Foo client:load />
+ <Bar client:load />
+ </body>
+ </html>
+ `
+ },
+ root
+ );
+
+ await runInContainer({
+ fs, root,
+ logging: {
+ ...defaultLogging,
+ // Error is expected in this test
+ level: 'silent'
+ },
+ userConfig: {
+ integrations: [svelte()]
+ }
+ }, async (container) => {
+ const { req, res, done } = createRequestAndResponse({
+ method: 'GET',
+ url: '/',
+ });
+ container.handle(req, res);
+ const html = await done;
+ expect(res.statusCode).to.equal(200, 'We get a 200 because the error occurs in the template, but we didn\'t crash!');
+ });
+ });
+});