diff --git a/.changeset/tame-rabbits-cross.md b/.changeset/tame-rabbits-cross.md
new file mode 100644
index 0000000..cdb6f6a
--- /dev/null
+++ b/.changeset/tame-rabbits-cross.md
@@ -0,0 +1,5 @@
+---
+'@hono/vite-dev-server': minor
+---
+
+feat: supports Bun
diff --git a/.github/workflows/ci-dev-server.yml b/.github/workflows/ci-dev-server.yml
index ca55945..5e3eed5 100644
--- a/.github/workflows/ci-dev-server.yml
+++ b/.github/workflows/ci-dev-server.yml
@@ -24,3 +24,16 @@ jobs:
- run: yarn build
- run: npx playwright install --with-deps
- run: yarn test
+ ci-bun:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./packages/dev-server
+ steps:
+ - uses: actions/checkout@v4
+ - uses: oven-sh/setup-bun@v1
+ with:
+ bun-version: '1.0.25'
+ - run: bun install
+ - run: npx playwright install --with-deps
+ - run: bun run test:e2e:bun
diff --git a/packages/dev-server/e2e-bun/e2e.test.ts b/packages/dev-server/e2e-bun/e2e.test.ts
new file mode 100644
index 0000000..9cc1c1a
--- /dev/null
+++ b/packages/dev-server/e2e-bun/e2e.test.ts
@@ -0,0 +1,70 @@
+import { test, expect } from '@playwright/test'
+
+test('Should return 200 response', async ({ page }) => {
+ const response = await page.goto('/')
+ expect(response?.status()).toBe(200)
+
+ const headers = response?.headers() ?? {}
+ expect(headers['x-via']).toBe('vite')
+
+ const content = await page.textContent('h1')
+ expect(content).toBe('Hello Vite!')
+})
+
+test('Should contain an injected script tag', async ({ page }) => {
+ await page.goto('/')
+
+ const lastScriptTag = await page.$('script:last-of-type')
+ expect(lastScriptTag).not.toBeNull()
+
+ const content = await lastScriptTag?.textContent()
+ expect(content).toBe('import("/@vite/client")')
+})
+
+test('Should exclude the file specified in the config file', async ({ page }) => {
+ let response = await page.goto('/file.ts')
+ expect(response?.status()).toBe(404)
+
+ response = await page.goto('/ends-in-ts')
+ expect(response?.status()).toBe(200)
+
+ response = await page.goto('/app/foo')
+ expect(response?.status()).toBe(404)
+
+ response = await page.goto('/favicon.ico')
+ expect(response?.status()).toBe(404)
+
+ response = await page.goto('/static/foo.png')
+ expect(response?.status()).toBe(404)
+})
+
+test('Should return 200 response - /stream', async ({ page }) => {
+ const response = await page.goto('/stream')
+ expect(response?.status()).toBe(200)
+
+ const headers = response?.headers() ?? {}
+ expect(headers['x-via']).toBe('vite')
+
+ const content = await page.textContent('h1')
+ expect(content).toBe('Hello Vite!')
+})
+
+test('Should serve static files in `public/static`', async ({ page }) => {
+ const response = await page.goto('/static/hello.json')
+ expect(response?.status()).toBe(200)
+
+ const data = await response?.json()
+ expect(data['message']).toBe('Hello')
+})
+
+test('Should return a vite error page - /invalid-response', async ({ page }) => {
+ const response = await page.goto('/invalid-response')
+ expect(response?.status()).toBe(500)
+ expect(await response?.text()).toContain('ErrorOverlay')
+})
+
+test('Should set `workerd` as a runtime key', async ({ page }) => {
+ const res = await page.goto('/runtime')
+ expect(res?.ok()).toBe(true)
+ expect(await res?.text()).toBe('bun')
+})
diff --git a/packages/dev-server/e2e-bun/mock/app.ts b/packages/dev-server/e2e-bun/mock/app.ts
new file mode 100644
index 0000000..4c0c421
--- /dev/null
+++ b/packages/dev-server/e2e-bun/mock/app.ts
@@ -0,0 +1,62 @@
+import { Hono } from 'hono'
+import { getRuntimeKey } from 'hono/adapter'
+
+const app = new Hono()
+
+app.get('/', (c) => {
+ c.header('x-via', 'vite')
+ return c.html('
Hello Vite!
')
+})
+
+app.get('/file.ts', (c) => {
+ return c.text('console.log("exclude me!")')
+})
+
+app.get('/app/foo', (c) => {
+ return c.html('exclude me!
')
+})
+
+app.get('/ends-in-ts', (c) => {
+ return c.text('this should not be excluded')
+})
+
+app.get('/favicon.ico', (c) => {
+ return c.text('a good favicon')
+})
+
+app.get('/static/foo.png', (c) => {
+ return c.text('a good image')
+})
+
+app.get('/stream', () => {
+ const html = new TextEncoder().encode('Hello Vite!
')
+ const stream = new ReadableStream({
+ start(controller) {
+ controller.enqueue(html)
+ controller.close()
+ },
+ })
+ return new Response(stream, {
+ headers: { 'Content-Type': 'text/html', 'x-via': 'vite' },
+ })
+})
+
+// @ts-expect-error the response is string
+app.get('/invalid-response', () => {
+ return 'Hello!
'
+})
+
+app.get('/invalid-error-response', (c) => {
+ try {
+ // @ts-expect-error the variable does not exist, intentionally
+ doesNotExist = true
+
+ return c.html('Hello Vite!
')
+ } catch (err) {
+ return err
+ }
+})
+
+app.get('/runtime', (c) => c.text(getRuntimeKey()))
+
+export default app
diff --git a/packages/dev-server/e2e-bun/playwright.config.ts b/packages/dev-server/e2e-bun/playwright.config.ts
new file mode 100644
index 0000000..8c50c7a
--- /dev/null
+++ b/packages/dev-server/e2e-bun/playwright.config.ts
@@ -0,0 +1,24 @@
+import { defineConfig, devices } from '@playwright/test'
+
+export default defineConfig({
+ fullyParallel: true,
+ forbidOnly: !!process.env.CI,
+ retries: process.env.CI ? 2 : 0,
+ workers: process.env.CI ? 1 : undefined,
+ use: {
+ baseURL: 'http://localhost:6173',
+ },
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ timeout: 5000,
+ retries: 2,
+ },
+ ],
+ webServer: {
+ command: 'bun --bun vite --port 6173 -c ./vite.config.ts',
+ port: 6173,
+ reuseExistingServer: !process.env.CI,
+ },
+})
diff --git a/packages/dev-server/e2e-bun/public/static/hello.json b/packages/dev-server/e2e-bun/public/static/hello.json
new file mode 100644
index 0000000..92aaf63
--- /dev/null
+++ b/packages/dev-server/e2e-bun/public/static/hello.json
@@ -0,0 +1,3 @@
+{
+ "message": "Hello"
+}
\ No newline at end of file
diff --git a/packages/dev-server/e2e-bun/vite.config.ts b/packages/dev-server/e2e-bun/vite.config.ts
new file mode 100644
index 0000000..3dfe571
--- /dev/null
+++ b/packages/dev-server/e2e-bun/vite.config.ts
@@ -0,0 +1,13 @@
+import { defineConfig } from 'vite'
+import devServer, { defaultOptions } from '../src'
+
+export default defineConfig(async () => {
+ return {
+ plugins: [
+ devServer({
+ entry: './mock/app.ts',
+ exclude: [...defaultOptions.exclude, '/app/**'],
+ }),
+ ],
+ }
+})
diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json
index 4e98a52..7e9493a 100644
--- a/packages/dev-server/package.json
+++ b/packages/dev-server/package.json
@@ -6,8 +6,9 @@
"module": "dist/index.js",
"type": "module",
"scripts": {
- "test:unit": "vitest --run",
+ "test:unit": "vitest --run ./src",
"test:e2e": "playwright test -c e2e/playwright.config.ts e2e/e2e.test.ts",
+ "test:e2e:bun": "playwright test -c e2e-bun/playwright.config.ts e2e-bun/e2e.test.ts",
"test": "yarn test:unit && yarn test:e2e",
"build": "rimraf dist && tsup --format esm,cjs --dts && publint",
"watch": "tsup --watch",
@@ -82,7 +83,7 @@
"node": ">=18.14.1"
},
"dependencies": {
- "@hono/node-server": "^1.8.2",
+ "@hono/node-server": "^1.10.0",
"miniflare": "^3.20231218.2",
"minimatch": "^9.0.3"
}
diff --git a/packages/dev-server/src/dev-server.ts b/packages/dev-server/src/dev-server.ts
index fc525f4..31e54bf 100644
--- a/packages/dev-server/src/dev-server.ts
+++ b/packages/dev-server/src/dev-server.ts
@@ -169,6 +169,7 @@ export function devServer(options?: DevServerOptions): VitePlugin {
return response
},
{
+ overrideGlobalObjects: false,
errorHandler: (e) => {
let err: Error
if (e instanceof Error) {
diff --git a/yarn.lock b/yarn.lock
index 061766a..f500753 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -950,10 +950,10 @@ __metadata:
languageName: node
linkType: hard
-"@hono/node-server@npm:^1.8.2":
- version: 1.8.2
- resolution: "@hono/node-server@npm:1.8.2"
- checksum: dda9a37a9f73ebae64edd653d348dd63c5bd8205e368a8767c8f21bee5e0cbe555c8b279e98a4805ad27ae00517bbeae4fccf27119c6c06af8a268e126d82d0d
+"@hono/node-server@npm:^1.10.0":
+ version: 1.10.0
+ resolution: "@hono/node-server@npm:1.10.0"
+ checksum: d6ed27c41cf3fd10873cd3c927c3459ed9d596d28e4870ad145660e6783f0de9109ec0fe97b45ba55844c6fb41acb748005cff76cdbd78a944a98179489d3cba
languageName: node
linkType: hard
@@ -977,7 +977,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "@hono/vite-dev-server@workspace:packages/dev-server"
dependencies:
- "@hono/node-server": ^1.8.2
+ "@hono/node-server": ^1.10.0
"@playwright/test": ^1.37.1
glob: ^10.3.10
hono: ^4.0.1