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

feat: enable experimental websockets support (thanks to nitro) #212

Merged
merged 2 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading