diff --git a/.changeset/poor-spoons-pump.md b/.changeset/poor-spoons-pump.md new file mode 100644 index 0000000..750a176 --- /dev/null +++ b/.changeset/poor-spoons-pump.md @@ -0,0 +1,5 @@ +--- +'@hono/vite-dev-server': patch +--- + +fix: handle invalid response diff --git a/packages/dev-server/e2e/e2e.test.ts b/packages/dev-server/e2e/e2e.test.ts index 4030db8..cfbf338 100644 --- a/packages/dev-server/e2e/e2e.test.ts +++ b/packages/dev-server/e2e/e2e.test.ts @@ -80,3 +80,10 @@ test('Should handle `env.ASSETS.fetch` function', async ({ page }) => { const data = await response?.json() expect(data['message']).toBe('Hello') }) + +test('Should return an error page - /invalid-response', async ({ page }) => { + const response = await page.goto('/invalid-response') + expect(response?.status()).toBe(500) + expect(await response?.text()).not.toBe('') + expect(await response?.headerValue('content-type')).toMatch(/^text\/html/) +}) diff --git a/packages/dev-server/e2e/mock/worker.ts b/packages/dev-server/e2e/mock/worker.ts index c0e274d..eb224f0 100644 --- a/packages/dev-server/e2e/mock/worker.ts +++ b/packages/dev-server/e2e/mock/worker.ts @@ -59,4 +59,9 @@ app.get('/assets/hello.json', async (c) => { return c.json(data) }) +// @ts-expect-error the response is string +app.get('/invalid-response', () => { + return '

Hello!

' +}) + export default app diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index a8e7725..40b7477 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -74,7 +74,7 @@ }, "dependencies": { "@hono/node-server": "^1.2.0", - "miniflare": "^3.20231016.0", + "miniflare": "^3.20231030.4", "minimatch": "^9.0.3" } -} \ No newline at end of file +} diff --git a/packages/dev-server/src/dev-server.ts b/packages/dev-server/src/dev-server.ts index 092d3fe..f87bac2 100644 --- a/packages/dev-server/src/dev-server.ts +++ b/packages/dev-server/src/dev-server.ts @@ -84,12 +84,22 @@ export function devServer(options?: DevServerOptions): Plugin { env = await cloudflarePagesGetEnv(options.cf)() } - const response = await app.fetch(request, env, { + let response = await app.fetch(request, env, { waitUntil: async (fn) => fn, passThroughOnException: () => { throw new Error('`passThroughOnException` is not supported') }, }) + + /** + * If the response is not instance of `Response`, it returns simple HTML with error messages. + */ + if (!(response instanceof Response)) { + const message = 'The response is not an instance of "Response", but: ' + response + console.error(message) + response = createErrorResponse(message) + } + if ( options?.injectClientScript !== false && response.headers.get('content-type')?.match(/^text\/html/) @@ -108,6 +118,29 @@ export function devServer(options?: DevServerOptions): Plugin { return plugin } +function escapeHtml(str: string): string { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} + +function createErrorResponse(body: BodyInit) { + return new Response( + `
${escapeHtml(
+      body.toString()
+    )}
`, + { + status: 500, + headers: { + 'content-type': 'text/html;charset=utf-8', + }, + } + ) +} + function injectStringToResponse(response: Response, content: string) { const stream = response.body const newContent = new TextEncoder().encode(content) diff --git a/yarn.lock b/yarn.lock index cd6f090..7b05a91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -752,7 +752,7 @@ __metadata: "@playwright/test": ^1.37.1 glob: ^10.3.10 hono: ^3.11.7 - miniflare: ^3.20231016.0 + miniflare: ^3.20231030.4 minimatch: ^9.0.3 playwright: ^1.39.0 publint: ^0.2.5 @@ -3973,7 +3973,7 @@ __metadata: languageName: node linkType: hard -"miniflare@npm:^3.20231016.0": +"miniflare@npm:^3.20231030.4": version: 3.20231030.4 resolution: "miniflare@npm:3.20231030.4" dependencies: