Skip to content

Commit

Permalink
Merge pull request #212 from nksaraf/websocket
Browse files Browse the repository at this point in the history
feat: enable experimental websockets support (thanks to nitro)
  • Loading branch information
nksaraf authored Feb 27, 2024
2 parents 7265cf4 + fec7d4f commit 5db67f3
Show file tree
Hide file tree
Showing 12 changed files with 1,030 additions and 164 deletions.
6 changes: 6 additions & 0 deletions .changeset/witty-days-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"vinxi": patch
"react-ssr-basic": patch
---

feat: enable experimental websockets support (thanks to nitro)
13 changes: 13 additions & 0 deletions examples/react/ssr/basic/app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import reactRefresh from "@vitejs/plugin-react";
import { createApp } from "vinxi";

export default createApp({
server: {
experimental: {
websocket: true,
},
},
routers: [
{
name: "public",
Expand All @@ -17,6 +22,14 @@ export default createApp({
plugins: () => [reactRefresh()],
base: "/_build",
},
{
name: "websocket",
type: "http",
handler: "./app/ws.ts",
target: "server",
base: "/_ws",
plugins: () => [reactRefresh()],
},
{
name: "ssr",
type: "http",
Expand Down
31 changes: 30 additions & 1 deletion examples/react/ssr/basic/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,31 @@ import React from "react";

import "./style.css";

function useWebSocket() {
const [message, setMessage] = React.useState("");
const [ws, setWs] = React.useState<WebSocket | null>(null);

React.useEffect(() => {
const ws = new WebSocket("ws://localhost:3000/_ws");
ws.onopen = () => {
console.log("WebSocket opened");
};
ws.onmessage = (event) => {
console.log("WebSocket message", event);
setMessage(event.data);
};
setWs(ws);
return () => {
ws.close();
};
}, []);

return { message, ws };
}

export default function App({ assets }) {
const [count, setCount] = React.useState(0);
const { message, ws } = useWebSocket();
return (
<html lang="en">
<head>
Expand All @@ -13,9 +36,15 @@ export default function App({ assets }) {
<body>
<section>
<h1>Hello AgentConf with ya asdo!!!</h1>
<button onClick={() => setCount(count + 1)}>
<button
onClick={() => {
setCount(count + 1);
ws.send("Hello from the client!");
}}
>
Click me: {count}!
</button>
<div>Message from server: {message}</div>
</section>
</body>
</html>
Expand Down
57 changes: 35 additions & 22 deletions examples/react/ssr/basic/app/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,45 @@
import { renderAsset } from "@vinxi/react";
import { Suspense } from "react";
import { renderToPipeableStream } from "react-dom/server";
import { eventHandler } from "vinxi/http";
import { defineWebSocket, eventHandler } from "vinxi/http";
import { getManifest } from "vinxi/manifest";

import App from "./app";

export default eventHandler(async (event) => {
const clientManifest = getManifest("client");
const assets = await clientManifest.inputs[clientManifest.handler].assets();
const events = {};
const stream = await new Promise(async (resolve) => {
const stream = renderToPipeableStream(
<App assets={<Suspense>{assets.map((m) => renderAsset(m))}</Suspense>} />,
{
onShellReady() {
resolve(stream);
export default eventHandler({
handler: async (event) => {
const clientManifest = getManifest("client");
const assets = await clientManifest.inputs[clientManifest.handler].assets();
const events = {};
const stream = await new Promise(async (resolve) => {
const stream = renderToPipeableStream(
<App
assets={<Suspense>{assets.map((m) => renderAsset(m))}</Suspense>}
/>,
{
onShellReady() {
resolve(stream);
},
bootstrapModules: [
clientManifest.inputs[clientManifest.handler].output.path,
],
bootstrapScriptContent: `window.manifest = ${JSON.stringify(
await clientManifest.json(),
)}`,
},
bootstrapModules: [
clientManifest.inputs[clientManifest.handler].output.path,
],
bootstrapScriptContent: `window.manifest = ${JSON.stringify(
await clientManifest.json(),
)}`,
},
);
});
);
});

event.node.res.setHeader("Content-Type", "text/html");
return stream;
event.node.res.setHeader("Content-Type", "text/html");
return stream;
},
websocket: defineWebSocket({
open: (event) => {
console.log("WebSocket opened");
},
message: (event) => {
console.log("WebSocket message", event);
event.send("Hello");
},
}),
});
17 changes: 17 additions & 0 deletions examples/react/ssr/basic/app/ws.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineWebSocket, eventHandler } from "vinxi/http";

export default eventHandler({
handler: () => {},
websocket: defineWebSocket({
async open(event) {
console.log("WebSocket opened");
},
async message(peer, event) {
console.log("WebSocket message", event);
peer.send("YOOO");
},
async close(event) {
console.log("WebSocket closed 3");
},
}),
});
2 changes: 1 addition & 1 deletion packages/vinxi/lib/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ const routerModePlugin = {
import middleware from "${join(config.router.root, config.router.middleware)}";
import handler from "${join(config.router.root, config.router.handler)}";
import { eventHandler } from "vinxi/http";
export default eventHandler({ onRequest: middleware.onRequest, onBeforeResponse: middleware.onBeforeResponse, handler});`;
export default eventHandler({ onRequest: middleware.onRequest, onBeforeResponse: middleware.onBeforeResponse, handler });`;
}
return `import handler from "${join(
config.router.root,
Expand Down
24 changes: 24 additions & 0 deletions packages/vinxi/lib/nitro-dev.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { listen } from "@vinxi/listhen";
import { watch } from "chokidar";
import wsAdapter from "crossws/adapters/node";
import {
H3Event,
createApp,
Expand Down Expand Up @@ -181,12 +182,19 @@ export async function createDevServer(nitro) {
);
}

const wsApp = createApp();

// Dev-only handlers
for (const handler of nitro.options.devHandlers) {
console.log(handler);
app.use(
joinURL(nitro.options.runtimeConfig.app.baseURL, handler.route ?? "/"),
handler.handler,
);
wsApp.use(
joinURL(nitro.options.runtimeConfig.app.baseURL, handler.route ?? "/"),
handler.handler,
);
}

// Main worker proxy
Expand Down Expand Up @@ -252,6 +260,14 @@ export async function createDevServer(nitro) {
// }),
// );

const adapter = wsAdapter({
...wsApp.websocket,
adapterHooks: {
"node:message": (event) => {
event.ctx.node.req.url = event.ctx.node.req.originalUrl;
},
},
});
// Listen
/** @type {import("@vinxi/listhen").Listener[]} */
let listeners = [];
Expand All @@ -268,6 +284,14 @@ export async function createDevServer(nitro) {
...opts,
});
listeners.push(listener);
import.meta._websocket = nitro.options.experimental?.websocket;
if (import.meta._websocket) {
console.log("enabling websockets");
listener.server.on("upgrade", (req, sock, head) => {
console.log("upgrading");
adapter.handleUpgrade(req, sock, head);
});
}
return listener;
};

Expand Down
37 changes: 28 additions & 9 deletions packages/vinxi/lib/router-modes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as z from "zod";

import { isMainThread } from "node:worker_threads";

import { defineWebSocket } from "../runtime/http.js";
import invariant from "./invariant.js";
import { handlerModule, join } from "./path.js";
import { resolve } from "./resolve.js";
Expand Down Expand Up @@ -256,15 +257,33 @@ const routerModes = {
const viteServer = await createViteHandler(router, app, serveConfig);
const viteMiddleware = fromNodeMiddleware(viteServer.middlewares);

const handler = eventHandler(async (event) => {
await viteMiddleware(event);
if (event.handled) {
return;
}
const { default: handler } = await viteServer.ssrLoadModule(
handlerModule(router),
);
return handler(event);
function createHook(hook) {
return async function callWebSocketHook(...args) {
const { default: handler } = await viteServer.ssrLoadModule(
handlerModule(router),
);
return handler.__websocket__?.[hook]?.(...args);
};
}

const handler = eventHandler({
handler: async (event) => {
await viteMiddleware(event);
if (event.handled) {
return;
}
const { default: handler } = await viteServer.ssrLoadModule(
handlerModule(router),
);
return handler(event);
},
websocket: defineWebSocket({
open: createHook("open"),
close: createHook("close"),
message: createHook("message"),
error: createHook("error"),
upgrade: createHook("upgrade"),
}),
});
return [
{
Expand Down
5 changes: 3 additions & 2 deletions packages/vinxi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@
"citty": "^0.1.4",
"consola": "^3.2.3",
"cookie-es": "^1.0.0",
"crossws": "^0.2.4",
"dax-sh": "^0.39.1",
"defu": "^6.1.2",
"dts-buddy": "^0.2.4",
Expand All @@ -187,12 +188,12 @@
"fast-glob": "^3.3.1",
"get-port": "^6.1.2",
"get-port-please": "^3.1.1",
"h3": "1.10.1",
"h3": "1.11.1",
"hookable": "^5.5.3",
"http-proxy": "^1.18.1",
"micromatch": "^4.0.5",
"mri": "^1.2.0",
"nitropack": "2.8.1",
"nitropack": "npm:nitropack-nightly@latest",
"node-fetch-native": "^1.4.0",
"path-to-regexp": "^6.2.1",
"pathe": "^1.1.1",
Expand Down
1 change: 1 addition & 0 deletions packages/vinxi/runtime/http-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export {
isEventHandler,
isWebResponse,
lazyEventHandler,
defineWebSocket,
promisifyNodeListener,
serveStatic,
toEventHandler,
Expand Down
1 change: 1 addition & 0 deletions packages/vinxi/runtime/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export {
defineRequestMiddleware,
defineResponseMiddleware,
dynamicEventHandler,
defineWebSocket,
eventHandler,
splitCookiesString,
fromNodeMiddleware,
Expand Down
Loading

0 comments on commit 5db67f3

Please sign in to comment.