Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the adaptor also work in development mode #25

Open
SirMorfield opened this issue Sep 9, 2023 · 4 comments
Open

Make the adaptor also work in development mode #25

SirMorfield opened this issue Sep 9, 2023 · 4 comments
Labels
enhancement New feature or request

Comments

@SirMorfield
Copy link

SirMorfield commented Sep 9, 2023

The adapter's WebSocket server currently does not work when running in vite dev

adapter-node-ws can already do this.

@gornostay25 gornostay25 added the enhancement New feature or request label Sep 9, 2023
@gevera
Copy link

gevera commented Sep 15, 2023

Wow.. I thought the example from README regarding websockets doesnt work. Only after looking through the issues and finding this one, I've build the project and it turned out that it works. It's a bummer that right now its not possible to have a real time feedback in dev mode. Hopefully this is just a temporary issue

@thiagomagro
Copy link

Hello team, any plans to make the WS work in dev mode?

@eslym
Copy link
Contributor

eslym commented Mar 26, 2024

prove of concept

// dev.ts

import { createServer } from 'vite';
import { join } from 'path';
import { EventEmitter } from 'events';
import { IncomingMessage, ServerResponse } from 'http';
import type { Server, WebSocketHandler } from 'bun';
const fakeServer = new EventEmitter();

const vite = await createServer({
	...(await import(join(process.cwd(), 'vite.config.ts'))),
	server: {
		hmr: {
			server: fakeServer as any
		},
		middlewareMode: true
	},
	appType: 'custom'
});

let bunternal = (socket: any) => {
	for (const prop of Object.getOwnPropertySymbols(socket)) {
		if (prop.toString().includes('bunternal')) {
			bunternal = () => prop;
			return prop as any;
		}
	}
};

Bun.serve({
	port: 5173,
	async fetch(request: Request, server: Server) {
		let pendingResponse: Response | undefined;
		let pendingError: Error | undefined;

		let resolve: (response: Response) => void;
		let reject: (error: Error) => void;

		function raise(err: any) {
			if (pendingError) return;
			reject?.((pendingError = err));
		}

		function respond(res: Response) {
			if (pendingResponse) return;
			resolve?.((pendingResponse = res));
		}

		const req = new IncomingMessage(request as any);
		const res = new (ServerResponse as any)(req, respond);

		const socket = req.socket as any;
		socket[bunternal(socket)] = [server, res, request];

		req.once('error', raise);
		res.once('error', raise);

		const promise = new Promise<Response | undefined>((res, rej) => {
			resolve = res;
			reject = rej;
		});

		if (request.headers.get('upgrade')) {
			if (request.headers.get('sec-websocket-protocol') === 'vite-hmr') {
				fakeServer.emit('upgrade', req, socket, Buffer.alloc(0));
				return;
			}
			const hooks = (await vite.ssrLoadModule('src/hooks.server.ts')) as any;
			if ('handleWebsocket' in hooks && hooks.handleWebsocket.upgrade(request, server)) {
				return;
			}
		}

		vite.middlewares(req, res, (err: any) => {
			if (err) {
				vite.ssrFixStacktrace(err);
				raise(err);
			}
		});

		return promise;
	},
	// this is required for bun internal ws package to work, so the hooks.server.ts must match with this format.
	// ex: server.upgrade(req, { data: { message(ws, msg) { ... } } });
	websocket: {
		open(ws) {
			return ws.data.open?.(ws);
		},
		message(ws, message) {
			return ws.data.message(ws, message);
		},
		drain(ws) {
			return ws.data.drain?.(ws);
		},
		close(ws, code, reason) {
			return ws.data.close?.(ws, code, reason);
		},
		ping(ws, buffer) {
			return ws.data.ping?.(ws, buffer);
		},
		pong(ws, buffer) {
			return ws.data.pong?.(ws, buffer);
		}
	} as WebSocketHandler<Pick<WebSocketHandler<any>, 'open' | 'message' | 'drain' | 'close' | 'ping' | 'pong'>>
});

console.log('Server running at http://localhost:5173');

then use bun dev.ts to start the dev server instead

p/s: this hack involve few bun internal stuffs, so it might broken after bun upgrade

@timootten
Copy link

timootten commented May 28, 2024

I update the dev.ts

dev.ts:

// dev.ts

import { createServer } from 'vite';
import { join } from 'path';
import { EventEmitter } from 'events';
import { IncomingMessage, ServerResponse } from 'http';
import type { Server, WebSocketHandler } from 'bun';
const fakeServer = new EventEmitter();

const vite = await createServer({
  ...(await import(join(process.cwd(), 'vite.config.ts'))),
  server: {
    hmr: {
      server: fakeServer as any
    },
    middlewareMode: true
  },
  appType: 'custom'
});

let bunternal = (socket: any) => {
  for (const prop of Object.getOwnPropertySymbols(socket)) {
    if (prop.toString().includes('bunternal')) {
      bunternal = () => prop;
      return prop as any;
    }
  }
};

const hooks = (await vite.ssrLoadModule('src/hooks.server.ts')) as any;

Bun.serve({
  port: 5173,
  async fetch(request: Request, server: Server) {
    let pendingResponse: Response | undefined;
    let pendingError: Error | undefined;

    let resolve: (response: Response) => void;
    let reject: (error: Error) => void;

    function raise(err: any) {
      if (pendingError) return;
      reject?.((pendingError = err));
    }

    function respond(res: Response) {
      if (pendingResponse) return;
      resolve?.((pendingResponse = res));
    }

    const req = new IncomingMessage(request as any);
    const res = new (ServerResponse as any)(req, respond);

    const socket = req.socket as any;
    socket[bunternal(socket)] = [server, res, request];

    req.once('error', raise);
    res.once('error', raise);

    const promise = new Promise<Response | undefined>((res, rej) => {
      resolve = res;
      reject = rej;
    });

    if (request.headers.get('upgrade')) {
      if (request.headers.get('sec-websocket-protocol') === 'vite-hmr') {
        fakeServer.emit('upgrade', req, socket, Buffer.alloc(0));
        return;
      }
      const hooks = (await vite.ssrLoadModule('src/hooks.server.ts')) as any;
      const upgradeMethod = server.upgrade.bind(server);
      if ('handleWebsocket' in hooks && hooks.handleWebsocket.upgrade(request, upgradeMethod)) {
        return;
      }
    }

    vite.middlewares(req, res, (err: any) => {
      if (err) {
        vite.ssrFixStacktrace(err);
        raise(err);
      }
    });

    return promise;
  },
  // this is required for bun internal ws package to work, so the hooks.server.ts must match with this format.
  // ex: server.upgrade(req, { data: { message(ws, msg) { ... } } });
  websocket: {
    open(ws) {
      if (ws?.data?.open) return ws.data.open?.(ws);
      return hooks?.handleWebsocket?.open(ws);
    },
    message(ws, message) {
      if (ws?.data?.message) return ws.data.message(ws, message);
      return hooks?.handleWebsocket?.message(ws, message);
    },
    drain(ws) {
      if (ws?.data?.drain) return ws.data.drain?.(ws);
      return hooks?.handleWebsocket?.drain?.(ws);
    },
    close(ws, code, reason) {
      if (ws?.data?.close) return ws.data.close?.(ws, code, reason);
      return hooks?.handleWebsocket?.drain?.(ws, code, reason);
    },
    ping(ws, buffer) {
      if (ws?.data?.ping) return ws.data.ping?.(ws, buffer);
      return hooks?.handleWebsocket?.ping?.(ws, buffer);
    },
    pong(ws, buffer) {
      if (ws?.data?.pong) return ws.data.pong?.(ws, buffer);
      return hooks?.handleWebsocket?.pong?.(ws, buffer);
    }
  } as WebSocketHandler<Pick<WebSocketHandler<any>, 'open' | 'message' | 'drain' | 'close' | 'ping' | 'pong'>>
});

console.log('Server running at http://localhost:5173');

hooks.server.ts:

export const handleWebsocket: WebSocketHandler = {
  open(ws) {
    ws.send("test");
    console.log("ws opened");
  },
  upgrade(request, upgrade) {
    const url = request.url
    console.log(url)
    console.log(upgrade)
    return upgrade(request);
  },
  message(ws, message) {
    ws.send(message);
    console.log("ws message", message);
  },
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

6 participants