UNPKG

colyseus

Version:

Multiplayer Framework for Node.js

248 lines (247 loc) 7.91 kB
// bundles/colyseus/src/vite.ts import * as matchMaker from "@colyseus/core/MatchMaker"; import { setDevMode, createNodeMatchmakingMiddleware, dynamicImport, registerRoomDefinitions, unregisterRoomDefinitions, toNodeHandler } from "@colyseus/core"; import { setTransport } from "@colyseus/core/Transport"; var VIRTUAL_SERVER_ENTRY = "virtual:colyseus-server-entry"; var RESOLVED_VIRTUAL_SERVER_ENTRY = "\0" + VIRTUAL_SERVER_ENTRY; function getServerExport(mod) { return mod.server || mod.default?.server; } function getRoomsExport(mod) { return mod.rooms || mod.default?.rooms; } function createColyseusViteServerEntry(options) { const port = options.port ?? 2567; const lines = [ `import { Server, registerRoomDefinitions } from "colyseus";` ]; if (options.serveClient) { lines.push( `import express from "express";`, `import { fileURLToPath } from "url";`, `import { dirname, join } from "path";`, ``, `const __dirname = dirname(fileURLToPath(import.meta.url));`, `const clientDir = join(__dirname, "../client");` ); } lines.push( ``, `const entry = await import(${JSON.stringify(options.serverEntry)});`, `const server = entry.server ?? entry.default?.server;`, `const rooms = entry.rooms ?? entry.default?.rooms;`, ``, `if (server) {` ); if (options.serveClient) { lines.push( ` await server["_onTransportReady"];`, ` if (server.transport.getExpressApp) {`, ` const app = server.transport.getExpressApp();`, ` app.use(express.static(clientDir));`, ` app.get("*all", (req, res) => res.sendFile(join(clientDir, "index.html")));`, ` }` ); } lines.push( ` server.listen(${port});`, `} else if (rooms) {`, ` const gameServer = new Server();`, ` registerRoomDefinitions(rooms);`, ` gameServer.listen(${port});`, `} else {`, ` throw new Error('[colyseus] Server entry should export \`server = defineServer(...)\` or \`rooms\`.');`, `}` ); return lines.join("\n"); } async function reloadColyseusViteRooms(importModule, serverEntry, currentRoomNames = []) { const mod = await importModule(serverEntry); unregisterRoomDefinitions(currentRoomNames); const server = getServerExport(mod); const rooms = getRoomsExport(mod) || server?.["~rooms"]; if (!rooms) { return { roomNames: [], hasRooms: false, server }; } return { roomNames: registerRoomDefinitions(rooms), hasRooms: true, server }; } function colyseus(options) { let viteServer; let currentRoomNames = []; let currentAppHandler = null; let expressApp = null; let isStarted = false; return [ { name: "colyseus:config", config() { return { builder: {}, build: { outDir: "dist/client" }, environments: { colyseus: { consumer: "server", resolve: { // Externalize all dependencies so they share the same module // instances (and matchMaker singleton) with the plugin process. // Without this, Vite re-evaluates workspace/linked packages in // the runner, creating isolated singletons — breaking monitor, etc. external: true }, build: { outDir: "dist/server", ssr: true, rollupOptions: { input: VIRTUAL_SERVER_ENTRY, output: { entryFileNames: "server.mjs" } } } } } }; }, resolveId(id) { if (id === VIRTUAL_SERVER_ENTRY) { return RESOLVED_VIRTUAL_SERVER_ENTRY; } }, load(id) { if (id === RESOLVED_VIRTUAL_SERVER_ENTRY) { return createColyseusViteServerEntry(options); } } }, { name: "colyseus:dev-server", configureServer(server) { viteServer = server; server.middlewares.use(createNodeMatchmakingMiddleware()); server.middlewares.use((req, res, next) => { if (!currentAppHandler) { return next(); } currentAppHandler(req, res, next); }); return async () => { if (!server.httpServer) { throw new Error("[colyseus] Vite HTTP server not available."); } await loadServerModule(); console.log("[colyseus] Server ready on Vite's HTTP server"); }; } }, { name: "colyseus:hmr", hotUpdate({ file, modules }) { if (this.environment?.name === "colyseus" && modules.length > 0) { loadServerModule().then(() => { if (!options.quiet) { console.log(`[colyseus] Server code reloaded (${file})`); } }).catch((e) => { console.error("[colyseus] Failed to reload server module:", e); }); } } } ]; async function loadServerModule() { const env = viteServer.environments.colyseus; if (!env) { console.error("[colyseus] Environment not found"); return; } try { if (isStarted && env.runner.evaluatedModules) { env.runner.evaluatedModules.clear(); } if (!isStarted) { setDevMode(true); await matchMaker.setup(); const wsModule = await (options.loadWsTransport ? options.loadWsTransport() : dynamicImport("@colyseus/ws-transport")); const transport = new wsModule.WebSocketTransport({ noServer: true }); if (typeof transport.attachToServer !== "function") { throw new Error("[colyseus] Vite dev mode requires a transport with attachToServer()."); } transport.attachToServer(viteServer.httpServer, { filter(req) { return /^\/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+\/?$/.test( new URL(req.url || "", "http://localhost").pathname ); } }); setTransport(transport); } const mod = await env.runner.import(options.serverEntry); const config = getServerExport(mod); const rooms = getRoomsExport(mod) || config?.["~rooms"]; const router = config?.router; if (!expressApp && config?.options?.express) { try { const express = (await dynamicImport("express")).default; expressApp = express(); config.options.express(expressApp); } catch (e) { console.warn("[colyseus] Express not available. Install express to use the express option."); } } if (router || expressApp) { const routerHandler = router ? toNodeHandler(router.handler) : null; currentAppHandler = (req, res, next) => { if (router?.findRoute(req.method, req.url?.split("?")[0]) !== void 0) { routerHandler(req, res); } else if (expressApp) { expressApp(req, res, next); } else { next(); } }; } else { currentAppHandler = null; } unregisterRoomDefinitions(currentRoomNames); if (rooms) { currentRoomNames = registerRoomDefinitions(rooms); } else { currentRoomNames = []; console.warn( "[colyseus] Server entry should export `server = defineServer(...)` or `rooms`." ); } if (!isStarted) { await matchMaker.accept(); isStarted = true; } else { await matchMaker.hotReload(); } if (!options.quiet) { for (const roomName of currentRoomNames) { console.log(`[colyseus] Room defined: "${roomName}"`); } } } catch (e) { console.error("[colyseus] Failed to load server module:", e); } } } export { colyseus, createColyseusViteServerEntry, reloadColyseusViteRooms };