Skip to content

Commit a759e75

Browse files
meck93autofix-ci[bot]yusukebe
authored
fix(build): preserve websocket export in bun adapter output (#337)
* fix(build): preserve websocket export in bun adapter output * ci: apply automated fixes * chore(changeset): add patch release note for bun websocket export fix * format * add `// eslint-disable-next-line quotes` --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Yusuke Wada <yusuke@kamawada.com>
1 parent 5b0288d commit a759e75

File tree

4 files changed

+66
-1
lines changed

4 files changed

+66
-1
lines changed

.changeset/clever-mails-warn.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@hono/vite-build': patch
3+
---
4+
5+
Fix Bun adapter build output to preserve an entry's `websocket` handler in the generated default export.
6+
This prevents Bun runtime WebSocket upgrade failures when apps export `{ fetch, websocket }`.

packages/build/src/adapter/bun/index.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,38 @@
11
import type { Plugin } from 'vite'
22
import type { BuildOptions } from '../../base.js'
3-
import buildPlugin from '../../base.js'
3+
import buildPlugin, { defaultOptions as baseDefaultOptions } from '../../base.js'
44
import { serveStaticHook } from '../../entry/serve-static.js'
55

66
export type BunBuildOptions = {
77
staticRoot?: string | undefined
88
} & BuildOptions
99

10+
export const defaultOptions: BunBuildOptions = {
11+
...baseDefaultOptions,
12+
entryContentAfterHooks: [
13+
() => `
14+
let websocket
15+
for (const [, app] of Object.entries(modules)) {
16+
if (
17+
app &&
18+
typeof app === 'object' &&
19+
'websocket' in app &&
20+
app.websocket !== undefined
21+
) {
22+
if (websocket !== undefined) {
23+
throw new Error(
24+
\`Handler "websocket" is defined in multiple entry files. Please ensure each handler is defined only once.\`
25+
)
26+
}
27+
websocket = app.websocket
28+
}
29+
}
30+
`,
31+
],
32+
entryContentDefaultExportHook: (appName) =>
33+
`export default websocket !== undefined ? { fetch: ${appName}.fetch.bind(${appName}), websocket } : ${appName}`,
34+
}
35+
1036
const bunBuildPlugin = (pluginOptions?: BunBuildOptions): Plugin => {
1137
return {
1238
...buildPlugin({
@@ -25,6 +51,11 @@ const bunBuildPlugin = (pluginOptions?: BunBuildOptions): Plugin => {
2551
],
2652
},
2753
...pluginOptions,
54+
entryContentAfterHooks:
55+
pluginOptions?.entryContentAfterHooks ?? defaultOptions.entryContentAfterHooks,
56+
entryContentDefaultExportHook:
57+
pluginOptions?.entryContentDefaultExportHook ??
58+
defaultOptions.entryContentDefaultExportHook,
2859
}),
2960
name: '@hono/vite-build/bun',
3061
}

packages/build/test/adapter.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,26 @@ describe('Build Plugin with Bun Adapter', () => {
4545
// eslint-disable-next-line quotes
4646
expect(outputJsClientJs).toContain("console.log('foo')")
4747
})
48+
49+
it('Should preserve websocket from entry default export in generated bundle', async () => {
50+
const outputFile = `${testDir}/dist/index.js`
51+
52+
await build({
53+
root: testDir,
54+
plugins: [
55+
bunBuildPlugin({
56+
entry: './src/server-fetch-with-websocket.ts',
57+
minify: false,
58+
}),
59+
],
60+
})
61+
62+
expect(existsSync(outputFile)).toBe(true)
63+
64+
const output = readFileSync(outputFile, 'utf-8')
65+
expect(output).toContain('websocket')
66+
expect(output).toMatch(/fetch:\s*mainApp\.fetch\.bind\(mainApp\)/)
67+
})
4868
})
4969

5070
describe('Build Plugin with Netlify Functions Adapter', () => {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import app from './server'
2+
3+
export default {
4+
fetch: app.fetch,
5+
websocket: {
6+
open: () => {},
7+
},
8+
}

0 commit comments

Comments
 (0)