kiliman/remix-express-vite-plugin

Allow returning node:http server as an alternative to express

jansedlon opened this issue · 5 comments

As mentioned in #4, I'd need a way to return a different server other than express app. That's because libraries like socket.io can be attached to an existing server, but i think it must be the server from node:http.

This is an example from my app

const createRequestHandler =
  MODE === "production"
    ? Sentry.wrapExpressCreateRequestHandler(createRequestHandler_)
    : createRequestHandler_;

const viteDevServer =
  MODE === "production"
    ? undefined
    : await import("vite").then((vite) =>
        vite.createServer({ server: { middlewareMode: true } }),
      );

// Create express app
const app = express();

// Setup middlewares
// ...

// Setup vite dev server
if (viteDevServer) {
  app.use(viteDevServer.middlewares);
} else {
  // Remix fingerprints its assets so we can cache forever.
  app.use(
    "/assets",
    express.static("build/client/assets", {
      immutable: true,
      maxAge: "1y",
    }),
  );

  // Everything else (like favicon.ico) is cached for an hour. You may want to be
  // more aggressive with this caching.
  app.use(express.static("build/client", { maxAge: "1h" }));
}

// More middlewares
// ...

// Create request handler
async function getBuild() {
  const build = viteDevServer
    ? viteDevServer.ssrLoadModule("virtual:remix/server-build")
    : // @ts-ignore this should exist before running the server
      // but it may not exist just yet.
      await import("../build/server/index.js");

  return build as unknown as ServerBuild;
}
app.all(
  "*",
  createRequestHandler({
    mode: MODE,
    getLoadContext: (_, res) => ({
      cspNonce: res.locals.cspNonce,
      serverBuild: getBuild(),
      io,
    }),
    build: getBuild,
  }),
);

// Create new http server and pass express into it
const httpServer = createServer(app);

// Create socket.io server
export const io = new SocketIOServer(httpServer);

io.on('connection', () => {});

// Here i have to call `listen` on the httpServer, not express, otherwise socket.io doesn't work
const server = httpServer.listen(3000, async () => {
  // More code...
})

The most important part is the last section. The function listen is called on the httpServer, not the express app.
From my understanding, socket.io attached request handlers/whatever to the node:http server, not the express, therefore if only express is used, socket.io doesn't work at all

const server = httpServer.listen(3000, async () => {
  // More code...
})

So to clarify, this socket server needs to attach to the existing server (either the Vite dev server or the production server).

BTW: both servers are based on node:net.Server (which is the base of node:http.Server, etc)

Anyway, I'll see what changes I need to make in order to get socket.io working.

Yeah so it's basically like this

import { createServer } from "node:http";
import express from "express";

// Initialize express app
const app = express();

// Create httpServer from `node:http` based on express server
const httpServer = createServer(app);

// **Attach** Socket.io to the httpServer
const io = new SocketIOServer(httpServer);

// Listen on `node:http` server
httpServer.listen();

Fixed in v0.2.5. See README for example on how to setup socket.io

Thank you for the heads up. I have to imports a few files in order to actually use the socket.io server. Many of the files use other files, etc.. and i use the tsconfigPaths plugin in order to use path aliased (eg. ~/...). I can import it like that in vite file. I have tried native nodejs import aliases (imports fields in package.json) but to no avail. Can I even imports files like that? I mean import app dir files in vite config?

I think I solved it by using this

"dev": "NODE_OPTIONS='--import tsx/esm' vite",
"build": "NODE_OPTIONS='--import tsx/esm' remix vite:build"