From a9f7ff96676a40b78e22379edc8eb9ce60a29fb8 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Wed, 16 Nov 2022 12:22:35 -0500 Subject: [PATCH] Prevent dev from crashing when there are errors in template (#5417) * Prevent dev from crashing when there are errors in template * Adding a changeset --- .changeset/wild-falcons-sparkle.md | 5 ++ .../astro/src/runtime/server/render/astro.ts | 18 ++++++- .../astro/test/units/dev/hydration.test.js | 53 +++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 .changeset/wild-falcons-sparkle.md create mode 100644 packages/astro/test/units/dev/hydration.test.js 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; + --- + + testing + + + + + + ` + }, + 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!'); + }); + }); +});