diff --git a/.changeset/wild-falcons-sparkle.md b/.changeset/wild-falcons-sparkle.md new file mode 100644 index 0000000000..a6ad3290bd --- /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 d6da03007e..9d28a6f819 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 0000000000..d1ae0460cb --- /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; + --- + +