colyseus 0.17.9 → 0.17.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/vite.cjs CHANGED
@@ -167,11 +167,14 @@ function colyseus(options) {
167
167
  currentAppHandler(req, res, next);
168
168
  });
169
169
  return async () => {
170
- if (!server.httpServer) {
171
- throw new Error("[colyseus] Vite HTTP server not available.");
170
+ const httpServer = options.httpServer ?? server.httpServer;
171
+ if (!httpServer) {
172
+ throw new Error(
173
+ "[colyseus] No HTTP server available. When running Vite in middlewareMode, pass `httpServer` to the colyseus() plugin."
174
+ );
172
175
  }
173
- await loadServerModule();
174
- console.log("[colyseus] Server ready on Vite's HTTP server");
176
+ await loadServerModule(httpServer);
177
+ console.log("[colyseus] Server ready on " + (options.httpServer ? "user-provided" : "Vite's") + " HTTP server");
175
178
  };
176
179
  }
177
180
  },
@@ -190,7 +193,7 @@ function colyseus(options) {
190
193
  }
191
194
  }
192
195
  ];
193
- async function loadServerModule() {
196
+ async function loadServerModule(httpServer) {
194
197
  const env = viteServer.environments.colyseus;
195
198
  if (!env) {
196
199
  console.error("[colyseus] Environment not found");
@@ -208,7 +211,7 @@ function colyseus(options) {
208
211
  if (typeof transport.attachToServer !== "function") {
209
212
  throw new Error("[colyseus] Vite dev mode requires a transport with attachToServer().");
210
213
  }
211
- transport.attachToServer(viteServer.httpServer, {
214
+ transport.attachToServer(httpServer ?? viteServer.httpServer, {
212
215
  filter(req) {
213
216
  return /^\/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+\/?$/.test(
214
217
  new URL(req.url || "", "http://localhost").pathname
@@ -223,9 +226,10 @@ function colyseus(options) {
223
226
  const router = config?.router;
224
227
  if (!expressApp && config?.options?.express) {
225
228
  try {
226
- const express = (await (0, import_core.dynamicImport)("express")).default;
229
+ const expressModule = await (0, import_core.dynamicImport)("express");
230
+ const express = expressModule?.default ?? expressModule;
227
231
  expressApp = express();
228
- config.options.express(expressApp);
232
+ await config.options.express(expressApp);
229
233
  } catch (e) {
230
234
  console.warn("[colyseus] Express not available. Install express to use the express option.");
231
235
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/vite.ts"],
4
- "sourcesContent": ["/**\n * Colyseus Vite Plugin\n *\n * Integrates a Colyseus game server into Vite's dev server and build pipeline.\n *\n * ## Architecture\n *\n * Colyseus packages are externalized in the runner environment so they share\n * the same module instances \u2014 and therefore the same matchMaker singleton \u2014\n * as the plugin process. This lets user code (monitor, playground, custom\n * middleware) access the real matchMaker with actual room data.\n *\n * In dev mode, defineServer() returns a config-only object (no Server\n * instance). The plugin manages the matchMaker lifecycle, transport, and\n * HMR directly.\n *\n * On HMR:\n * 1. Re-import user module (defineServer returns fresh config)\n * 2. Swap router handler + re-register room definitions\n * 3. matchMaker.hotReload() \u2014 cache rooms, dispose, restore\n */\nimport * as matchMaker from '@colyseus/core/MatchMaker';\nimport {\n setDevMode,\n createNodeMatchmakingMiddleware,\n dynamicImport,\n registerRoomDefinitions,\n unregisterRoomDefinitions,\n toNodeHandler,\n type RoomDefinitions,\n type ServerOptions,\n type Transport,\n type Router,\n} from '@colyseus/core';\nimport { setTransport } from '@colyseus/core/Transport';\nimport type { Plugin } from 'vite';\n\n// \u2500\u2500\u2500 Virtual module IDs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst VIRTUAL_SERVER_ENTRY = 'virtual:colyseus-server-entry';\nconst RESOLVED_VIRTUAL_SERVER_ENTRY = '\\0' + VIRTUAL_SERVER_ENTRY;\n\n// \u2500\u2500\u2500 Options \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ColyseusViteOptions {\n serverEntry: string;\n port?: number;\n quiet?: boolean;\n /**\n * Serve the built client files via express.static() in the production\n * server entry. Adds a SPA fallback that serves index.html for\n * unmatched GET requests.\n *\n * Has no effect in dev mode (Vite serves the frontend).\n */\n serveClient?: boolean;\n loadWsTransport?: () => Promise<{\n WebSocketTransport: new (options?: any) => Transport & {\n attachToServer(server: any, options?: { filter?: (req: any) => boolean }): any;\n };\n }>;\n}\n\n// \u2500\u2500\u2500 Internal types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype ServerConfig = {\n options?: ServerOptions;\n router?: Router;\n '~rooms'?: RoomDefinitions;\n};\n\ntype ServerModule = {\n server?: ServerConfig;\n rooms?: RoomDefinitions;\n default?: {\n server?: ServerConfig;\n rooms?: RoomDefinitions;\n };\n};\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction getServerExport(mod: ServerModule): ServerConfig | undefined {\n return mod.server || mod.default?.server;\n}\n\nfunction getRoomsExport(mod: ServerModule): RoomDefinitions | undefined {\n return mod.rooms || mod.default?.rooms;\n}\n\n// \u2500\u2500\u2500 Virtual module generators \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Production build entry \u2014 standalone server that imports the user's\n * server entry and calls `server.listen()`.\n */\nexport function createColyseusViteServerEntry(options: ColyseusViteOptions) {\n const port = options.port ?? 2567;\n\n const lines: string[] = [\n `import { Server, registerRoomDefinitions } from \"colyseus\";`,\n ];\n\n if (options.serveClient) {\n lines.push(\n `import express from \"express\";`,\n `import { fileURLToPath } from \"url\";`,\n `import { dirname, join } from \"path\";`,\n ``,\n `const __dirname = dirname(fileURLToPath(import.meta.url));`,\n `const clientDir = join(__dirname, \"../client\");`,\n );\n }\n\n lines.push(\n ``,\n `const entry = await import(${JSON.stringify(options.serverEntry)});`,\n `const server = entry.server ?? entry.default?.server;`,\n `const rooms = entry.rooms ?? entry.default?.rooms;`,\n ``,\n `if (server) {`,\n );\n\n if (options.serveClient) {\n lines.push(\n ` await server[\"_onTransportReady\"];`,\n ` if (server.transport.getExpressApp) {`,\n ` const app = server.transport.getExpressApp();`,\n ` app.use(express.static(clientDir));`,\n ` app.get(\"*all\", (req, res) => res.sendFile(join(clientDir, \"index.html\")));`,\n ` }`,\n );\n }\n\n lines.push(\n ` server.listen(${port});`,\n `} else if (rooms) {`,\n ` const gameServer = new Server();`,\n ` registerRoomDefinitions(rooms);`,\n ` gameServer.listen(${port});`,\n `} else {`,\n ` throw new Error('[colyseus] Server entry should export \\`server = defineServer(...)\\` or \\`rooms\\`.');`,\n `}`,\n );\n\n return lines.join('\\n');\n}\n\n// \u2500\u2500\u2500 Exported helpers (for testing) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function reloadColyseusViteRooms(\n importModule: (specifier: string) => Promise<any>,\n serverEntry: string,\n currentRoomNames: string[] = [],\n) {\n const mod = await importModule(serverEntry);\n\n unregisterRoomDefinitions(currentRoomNames);\n\n const server = getServerExport(mod);\n const rooms: RoomDefinitions | undefined = getRoomsExport(mod)\n || server?.['~rooms'];\n\n if (!rooms) {\n return {\n roomNames: [],\n hasRooms: false,\n server,\n };\n }\n\n return {\n roomNames: registerRoomDefinitions(rooms),\n hasRooms: true,\n server,\n };\n}\n\n// \u2500\u2500\u2500 Plugin \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function colyseus(options: ColyseusViteOptions): Plugin[] {\n let viteServer: any;\n let currentRoomNames: string[] = [];\n let currentAppHandler: ((req: any, res: any, next: any) => void) | null = null;\n let expressApp: any = null;\n let isStarted = false;\n\n return [\n {\n name: 'colyseus:config',\n config() {\n return {\n builder: {},\n build: { outDir: 'dist/client' },\n environments: {\n colyseus: {\n consumer: 'server' as const,\n resolve: {\n // Externalize all dependencies so they share the same module\n // instances (and matchMaker singleton) with the plugin process.\n // Without this, Vite re-evaluates workspace/linked packages in\n // the runner, creating isolated singletons \u2014 breaking monitor, etc.\n external: true,\n },\n build: {\n outDir: 'dist/server',\n ssr: true,\n rollupOptions: {\n input: VIRTUAL_SERVER_ENTRY,\n output: { entryFileNames: 'server.mjs' },\n },\n },\n },\n },\n };\n },\n resolveId(id: string) {\n if (id === VIRTUAL_SERVER_ENTRY) { return RESOLVED_VIRTUAL_SERVER_ENTRY; }\n },\n load(id: string) {\n if (id === RESOLVED_VIRTUAL_SERVER_ENTRY) {\n return createColyseusViteServerEntry(options);\n }\n },\n },\n\n {\n name: 'colyseus:dev-server',\n configureServer(server: any) {\n viteServer = server;\n server.middlewares.use(createNodeMatchmakingMiddleware());\n\n // Dynamic application middleware \u2014 handler is swapped on each HMR reload.\n server.middlewares.use((req: any, res: any, next: any) => {\n if (!currentAppHandler) { return next(); }\n currentAppHandler(req, res, next);\n });\n\n return async () => {\n if (!server.httpServer) {\n throw new Error('[colyseus] Vite HTTP server not available.');\n }\n await loadServerModule();\n console.log(\"[colyseus] Server ready on Vite's HTTP server\");\n };\n },\n },\n\n {\n name: 'colyseus:hmr',\n hotUpdate({ file, modules }) {\n if (this.environment?.name === 'colyseus' && modules.length > 0) {\n loadServerModule().then(() => {\n if (!options.quiet) {\n console.log(`[colyseus] Server code reloaded (${file})`);\n }\n }).catch((e) => {\n console.error('[colyseus] Failed to reload server module:', e);\n });\n }\n },\n },\n ];\n\n /**\n * Import (or re-import) the user's server entry and configure the\n * matchMaker, transport, rooms, and middleware.\n *\n * On initial load: sets up matchMaker, creates transport, registers rooms.\n * On HMR reload: re-imports user code, swaps rooms/router, hot-reloads\n * running rooms (cache \u2192 dispose \u2192 restore).\n */\n async function loadServerModule() {\n const env = viteServer.environments.colyseus;\n if (!env) {\n console.error('[colyseus] Environment not found');\n return;\n }\n\n try {\n // Clear the runner's evaluated module cache so re-import picks up\n // fresh user code. External packages (@colyseus/*) are cached by\n // Node's module system \u2014 they keep their singleton state.\n if (isStarted && env.runner.evaluatedModules) {\n env.runner.evaluatedModules.clear();\n }\n\n // \u2500\u2500 Step 1: Set up matchMaker + transport (initial load only) \u2500\u2500\n if (!isStarted) {\n setDevMode(true);\n await matchMaker.setup();\n\n const wsModule = await (options.loadWsTransport\n ? options.loadWsTransport()\n : dynamicImport<typeof import('@colyseus/ws-transport')>('@colyseus/ws-transport'));\n\n const transport = new wsModule.WebSocketTransport({ noServer: true });\n\n if (typeof (transport as any).attachToServer !== 'function') {\n throw new Error('[colyseus] Vite dev mode requires a transport with attachToServer().');\n }\n\n (transport as any).attachToServer(viteServer.httpServer, {\n filter(req: any) {\n return /^\\/[a-zA-Z0-9_-]+\\/[a-zA-Z0-9_-]+\\/?$/.test(\n new URL(req.url || '', 'http://localhost').pathname,\n );\n },\n });\n setTransport(transport);\n }\n\n // \u2500\u2500 Step 2: Import user module \u2500\u2500\n // In dev mode, defineServer() returns a config object (no Server\n // instance, no matchMaker.setup() call) because isDevMode is true.\n const mod = await env.runner.import(options.serverEntry);\n\n const config = getServerExport(mod);\n const rooms: RoomDefinitions | undefined = getRoomsExport(mod)\n || config?.['~rooms'];\n\n // \u2500\u2500 Step 3: Build application middleware (router + express) \u2500\u2500\n const router = config?.router;\n\n // Set up express once \u2014 persistent across HMR reloads.\n if (!expressApp && config?.options?.express) {\n try {\n const express = (await dynamicImport<any>('express')).default;\n expressApp = express();\n config.options.express(expressApp);\n } catch (e) {\n console.warn('[colyseus] Express not available. Install express to use the express option.');\n }\n }\n\n // Build combined handler: router (hot-swappable) + express (persistent).\n if (router || expressApp) {\n const routerHandler = router ? toNodeHandler(router.handler) : null;\n currentAppHandler = (req: any, res: any, next: any) => {\n if (router?.findRoute(req.method, req.url?.split('?')[0]) !== undefined) {\n routerHandler!(req, res);\n } else if (expressApp) {\n expressApp(req, res, next);\n } else {\n next();\n }\n };\n } else {\n currentAppHandler = null;\n }\n\n // \u2500\u2500 Step 4: Register room definitions \u2500\u2500\n // Must happen BEFORE hotReload() because reloadFromCache() needs\n // the new handlers to recreate room instances.\n unregisterRoomDefinitions(currentRoomNames);\n if (rooms) {\n currentRoomNames = registerRoomDefinitions(rooms);\n } else {\n currentRoomNames = [];\n console.warn(\n '[colyseus] Server entry should export `server = defineServer(...)` or `rooms`.',\n );\n }\n\n // \u2500\u2500 Step 5: Accept connections or hot-reload rooms \u2500\u2500\n if (!isStarted) {\n await matchMaker.accept();\n isStarted = true;\n } else {\n await matchMaker.hotReload();\n }\n\n if (!options.quiet) {\n for (const roomName of currentRoomNames) {\n console.log(`[colyseus] Room defined: \"${roomName}\"`);\n }\n }\n\n } catch (e) {\n console.error('[colyseus] Failed to load server module:', e);\n }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Colyseus Vite Plugin\n *\n * Integrates a Colyseus game server into Vite's dev server and build pipeline.\n *\n * ## Architecture\n *\n * Colyseus packages are externalized in the runner environment so they share\n * the same module instances \u2014 and therefore the same matchMaker singleton \u2014\n * as the plugin process. This lets user code (monitor, playground, custom\n * middleware) access the real matchMaker with actual room data.\n *\n * In dev mode, defineServer() returns a config-only object (no Server\n * instance). The plugin manages the matchMaker lifecycle, transport, and\n * HMR directly.\n *\n * On HMR:\n * 1. Re-import user module (defineServer returns fresh config)\n * 2. Swap router handler + re-register room definitions\n * 3. matchMaker.hotReload() \u2014 cache rooms, dispose, restore\n */\nimport * as matchMaker from '@colyseus/core/MatchMaker';\nimport {\n setDevMode,\n createNodeMatchmakingMiddleware,\n dynamicImport,\n registerRoomDefinitions,\n unregisterRoomDefinitions,\n toNodeHandler,\n type RoomDefinitions,\n type ServerOptions,\n type Transport,\n type Router,\n} from '@colyseus/core';\nimport { setTransport } from '@colyseus/core/Transport';\nimport type { Plugin } from 'vite';\n\n// \u2500\u2500\u2500 Virtual module IDs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst VIRTUAL_SERVER_ENTRY = 'virtual:colyseus-server-entry';\nconst RESOLVED_VIRTUAL_SERVER_ENTRY = '\\0' + VIRTUAL_SERVER_ENTRY;\n\n// \u2500\u2500\u2500 Options \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ColyseusViteOptions {\n serverEntry: string;\n port?: number;\n quiet?: boolean;\n /**\n * Serve the built client files via express.static() in the production\n * server entry. Adds a SPA fallback that serves index.html for\n * unmatched GET requests.\n *\n * Has no effect in dev mode (Vite serves the frontend).\n */\n serveClient?: boolean;\n loadWsTransport?: () => Promise<{\n WebSocketTransport: new (options?: any) => Transport & {\n attachToServer(server: any, options?: { filter?: (req: any) => boolean }): any;\n };\n }>;\n /**\n * HTTP server to attach the WebSocket transport to.\n *\n * Required when running Vite in middleware mode (`server.middlewareMode`),\n * where the HTTP server is owned by the parent process (e.g. Express).\n * In standalone Vite dev mode this is ignored \u2014 the plugin uses Vite's\n * own HTTP server.\n */\n httpServer?: import('http').Server;\n}\n\n// \u2500\u2500\u2500 Internal types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype ServerConfig = {\n options?: ServerOptions;\n router?: Router;\n '~rooms'?: RoomDefinitions;\n};\n\ntype ServerModule = {\n server?: ServerConfig;\n rooms?: RoomDefinitions;\n default?: {\n server?: ServerConfig;\n rooms?: RoomDefinitions;\n };\n};\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction getServerExport(mod: ServerModule): ServerConfig | undefined {\n return mod.server || mod.default?.server;\n}\n\nfunction getRoomsExport(mod: ServerModule): RoomDefinitions | undefined {\n return mod.rooms || mod.default?.rooms;\n}\n\n// \u2500\u2500\u2500 Virtual module generators \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Production build entry \u2014 standalone server that imports the user's\n * server entry and calls `server.listen()`.\n */\nexport function createColyseusViteServerEntry(options: ColyseusViteOptions) {\n const port = options.port ?? 2567;\n\n const lines: string[] = [\n `import { Server, registerRoomDefinitions } from \"colyseus\";`,\n ];\n\n if (options.serveClient) {\n lines.push(\n `import express from \"express\";`,\n `import { fileURLToPath } from \"url\";`,\n `import { dirname, join } from \"path\";`,\n ``,\n `const __dirname = dirname(fileURLToPath(import.meta.url));`,\n `const clientDir = join(__dirname, \"../client\");`,\n );\n }\n\n lines.push(\n ``,\n `const entry = await import(${JSON.stringify(options.serverEntry)});`,\n `const server = entry.server ?? entry.default?.server;`,\n `const rooms = entry.rooms ?? entry.default?.rooms;`,\n ``,\n `if (server) {`,\n );\n\n if (options.serveClient) {\n lines.push(\n ` await server[\"_onTransportReady\"];`,\n ` if (server.transport.getExpressApp) {`,\n ` const app = server.transport.getExpressApp();`,\n ` app.use(express.static(clientDir));`,\n ` app.get(\"*all\", (req, res) => res.sendFile(join(clientDir, \"index.html\")));`,\n ` }`,\n );\n }\n\n lines.push(\n ` server.listen(${port});`,\n `} else if (rooms) {`,\n ` const gameServer = new Server();`,\n ` registerRoomDefinitions(rooms);`,\n ` gameServer.listen(${port});`,\n `} else {`,\n ` throw new Error('[colyseus] Server entry should export \\`server = defineServer(...)\\` or \\`rooms\\`.');`,\n `}`,\n );\n\n return lines.join('\\n');\n}\n\n// \u2500\u2500\u2500 Exported helpers (for testing) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function reloadColyseusViteRooms(\n importModule: (specifier: string) => Promise<any>,\n serverEntry: string,\n currentRoomNames: string[] = [],\n) {\n const mod = await importModule(serverEntry);\n\n unregisterRoomDefinitions(currentRoomNames);\n\n const server = getServerExport(mod);\n const rooms: RoomDefinitions | undefined = getRoomsExport(mod)\n || server?.['~rooms'];\n\n if (!rooms) {\n return {\n roomNames: [],\n hasRooms: false,\n server,\n };\n }\n\n return {\n roomNames: registerRoomDefinitions(rooms),\n hasRooms: true,\n server,\n };\n}\n\n// \u2500\u2500\u2500 Plugin \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function colyseus(options: ColyseusViteOptions): Plugin[] {\n let viteServer: any;\n let currentRoomNames: string[] = [];\n let currentAppHandler: ((req: any, res: any, next: any) => void) | null = null;\n let expressApp: any = null;\n let isStarted = false;\n\n return [\n {\n name: 'colyseus:config',\n config() {\n return {\n builder: {},\n build: { outDir: 'dist/client' },\n environments: {\n colyseus: {\n consumer: 'server' as const,\n resolve: {\n // Externalize all dependencies so they share the same module\n // instances (and matchMaker singleton) with the plugin process.\n // Without this, Vite re-evaluates workspace/linked packages in\n // the runner, creating isolated singletons \u2014 breaking monitor, etc.\n external: true,\n },\n build: {\n outDir: 'dist/server',\n ssr: true,\n rollupOptions: {\n input: VIRTUAL_SERVER_ENTRY,\n output: { entryFileNames: 'server.mjs' },\n },\n },\n },\n },\n };\n },\n resolveId(id: string) {\n if (id === VIRTUAL_SERVER_ENTRY) { return RESOLVED_VIRTUAL_SERVER_ENTRY; }\n },\n load(id: string) {\n if (id === RESOLVED_VIRTUAL_SERVER_ENTRY) {\n return createColyseusViteServerEntry(options);\n }\n },\n },\n\n {\n name: 'colyseus:dev-server',\n configureServer(server: any) {\n viteServer = server;\n server.middlewares.use(createNodeMatchmakingMiddleware());\n\n // Dynamic application middleware \u2014 handler is swapped on each HMR reload.\n server.middlewares.use((req: any, res: any, next: any) => {\n if (!currentAppHandler) { return next(); }\n currentAppHandler(req, res, next);\n });\n\n return async () => {\n const httpServer = options.httpServer ?? server.httpServer;\n if (!httpServer) {\n throw new Error(\n '[colyseus] No HTTP server available. When running Vite in ' +\n 'middlewareMode, pass `httpServer` to the colyseus() plugin.'\n );\n }\n await loadServerModule(httpServer);\n console.log(\"[colyseus] Server ready on \" + (options.httpServer ? 'user-provided' : \"Vite's\") + ' HTTP server');\n };\n },\n },\n\n {\n name: 'colyseus:hmr',\n hotUpdate({ file, modules }) {\n if (this.environment?.name === 'colyseus' && modules.length > 0) {\n loadServerModule().then(() => {\n if (!options.quiet) {\n console.log(`[colyseus] Server code reloaded (${file})`);\n }\n }).catch((e) => {\n console.error('[colyseus] Failed to reload server module:', e);\n });\n }\n },\n },\n ];\n\n /**\n * Import (or re-import) the user's server entry and configure the\n * matchMaker, transport, rooms, and middleware.\n *\n * On initial load: sets up matchMaker, creates transport, registers rooms.\n * On HMR reload: re-imports user code, swaps rooms/router, hot-reloads\n * running rooms (cache \u2192 dispose \u2192 restore).\n */\n async function loadServerModule(httpServer?: import('http').Server) {\n const env = viteServer.environments.colyseus;\n if (!env) {\n console.error('[colyseus] Environment not found');\n return;\n }\n\n try {\n // Clear the runner's evaluated module cache so re-import picks up\n // fresh user code. External packages (@colyseus/*) are cached by\n // Node's module system \u2014 they keep their singleton state.\n if (isStarted && env.runner.evaluatedModules) {\n env.runner.evaluatedModules.clear();\n }\n\n // \u2500\u2500 Step 1: Set up matchMaker + transport (initial load only) \u2500\u2500\n if (!isStarted) {\n setDevMode(true);\n await matchMaker.setup();\n\n const wsModule = await (options.loadWsTransport\n ? options.loadWsTransport()\n : dynamicImport<typeof import('@colyseus/ws-transport')>('@colyseus/ws-transport'));\n\n const transport = new wsModule.WebSocketTransport({ noServer: true });\n\n if (typeof (transport as any).attachToServer !== 'function') {\n throw new Error('[colyseus] Vite dev mode requires a transport with attachToServer().');\n }\n\n (transport as any).attachToServer(httpServer ?? viteServer.httpServer, {\n filter(req: any) {\n return /^\\/[a-zA-Z0-9_-]+\\/[a-zA-Z0-9_-]+\\/?$/.test(\n new URL(req.url || '', 'http://localhost').pathname,\n );\n },\n });\n setTransport(transport);\n }\n\n // \u2500\u2500 Step 2: Import user module \u2500\u2500\n // In dev mode, defineServer() returns a config object (no Server\n // instance, no matchMaker.setup() call) because isDevMode is true.\n const mod = await env.runner.import(options.serverEntry);\n\n const config = getServerExport(mod);\n const rooms: RoomDefinitions | undefined = getRoomsExport(mod)\n || config?.['~rooms'];\n\n // \u2500\u2500 Step 3: Build application middleware (router + express) \u2500\u2500\n const router = config?.router;\n\n // Set up express once \u2014 persistent across HMR reloads.\n if (!expressApp && config?.options?.express) {\n try {\n const expressModule = await dynamicImport<any>('express');\n const express = expressModule?.default ?? expressModule;\n expressApp = express();\n await config.options.express(expressApp);\n } catch (e) {\n console.warn('[colyseus] Express not available. Install express to use the express option.');\n }\n }\n\n // Build combined handler: router (hot-swappable) + express (persistent).\n if (router || expressApp) {\n const routerHandler = router ? toNodeHandler(router.handler) : null;\n currentAppHandler = (req: any, res: any, next: any) => {\n if (router?.findRoute(req.method, req.url?.split('?')[0]) !== undefined) {\n routerHandler!(req, res);\n } else if (expressApp) {\n expressApp(req, res, next);\n } else {\n next();\n }\n };\n } else {\n currentAppHandler = null;\n }\n\n // \u2500\u2500 Step 4: Register room definitions \u2500\u2500\n // Must happen BEFORE hotReload() because reloadFromCache() needs\n // the new handlers to recreate room instances.\n unregisterRoomDefinitions(currentRoomNames);\n if (rooms) {\n currentRoomNames = registerRoomDefinitions(rooms);\n } else {\n currentRoomNames = [];\n console.warn(\n '[colyseus] Server entry should export `server = defineServer(...)` or `rooms`.',\n );\n }\n\n // \u2500\u2500 Step 5: Accept connections or hot-reload rooms \u2500\u2500\n if (!isStarted) {\n await matchMaker.accept();\n isStarted = true;\n } else {\n await matchMaker.hotReload();\n }\n\n if (!options.quiet) {\n for (const roomName of currentRoomNames) {\n console.log(`[colyseus] Room defined: \"${roomName}\"`);\n }\n }\n\n } catch (e) {\n console.error('[colyseus] Failed to load server module:', e);\n }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/build/vite.d.ts CHANGED
@@ -19,6 +19,15 @@ export interface ColyseusViteOptions {
19
19
  }): any;
20
20
  };
21
21
  }>;
22
+ /**
23
+ * HTTP server to attach the WebSocket transport to.
24
+ *
25
+ * Required when running Vite in middleware mode (`server.middlewareMode`),
26
+ * where the HTTP server is owned by the parent process (e.g. Express).
27
+ * In standalone Vite dev mode this is ignored — the plugin uses Vite's
28
+ * own HTTP server.
29
+ */
30
+ httpServer?: import('http').Server;
22
31
  }
23
32
  type ServerConfig = {
24
33
  options?: ServerOptions;
package/build/vite.mjs CHANGED
@@ -138,11 +138,14 @@ function colyseus(options) {
138
138
  currentAppHandler(req, res, next);
139
139
  });
140
140
  return async () => {
141
- if (!server.httpServer) {
142
- throw new Error("[colyseus] Vite HTTP server not available.");
141
+ const httpServer = options.httpServer ?? server.httpServer;
142
+ if (!httpServer) {
143
+ throw new Error(
144
+ "[colyseus] No HTTP server available. When running Vite in middlewareMode, pass `httpServer` to the colyseus() plugin."
145
+ );
143
146
  }
144
- await loadServerModule();
145
- console.log("[colyseus] Server ready on Vite's HTTP server");
147
+ await loadServerModule(httpServer);
148
+ console.log("[colyseus] Server ready on " + (options.httpServer ? "user-provided" : "Vite's") + " HTTP server");
146
149
  };
147
150
  }
148
151
  },
@@ -161,7 +164,7 @@ function colyseus(options) {
161
164
  }
162
165
  }
163
166
  ];
164
- async function loadServerModule() {
167
+ async function loadServerModule(httpServer) {
165
168
  const env = viteServer.environments.colyseus;
166
169
  if (!env) {
167
170
  console.error("[colyseus] Environment not found");
@@ -179,7 +182,7 @@ function colyseus(options) {
179
182
  if (typeof transport.attachToServer !== "function") {
180
183
  throw new Error("[colyseus] Vite dev mode requires a transport with attachToServer().");
181
184
  }
182
- transport.attachToServer(viteServer.httpServer, {
185
+ transport.attachToServer(httpServer ?? viteServer.httpServer, {
183
186
  filter(req) {
184
187
  return /^\/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+\/?$/.test(
185
188
  new URL(req.url || "", "http://localhost").pathname
@@ -194,9 +197,10 @@ function colyseus(options) {
194
197
  const router = config?.router;
195
198
  if (!expressApp && config?.options?.express) {
196
199
  try {
197
- const express = (await dynamicImport("express")).default;
200
+ const expressModule = await dynamicImport("express");
201
+ const express = expressModule?.default ?? expressModule;
198
202
  expressApp = express();
199
- config.options.express(expressApp);
203
+ await config.options.express(expressApp);
200
204
  } catch (e) {
201
205
  console.warn("[colyseus] Express not available. Install express to use the express option.");
202
206
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/vite.ts"],
4
- "sourcesContent": ["/**\n * Colyseus Vite Plugin\n *\n * Integrates a Colyseus game server into Vite's dev server and build pipeline.\n *\n * ## Architecture\n *\n * Colyseus packages are externalized in the runner environment so they share\n * the same module instances \u2014 and therefore the same matchMaker singleton \u2014\n * as the plugin process. This lets user code (monitor, playground, custom\n * middleware) access the real matchMaker with actual room data.\n *\n * In dev mode, defineServer() returns a config-only object (no Server\n * instance). The plugin manages the matchMaker lifecycle, transport, and\n * HMR directly.\n *\n * On HMR:\n * 1. Re-import user module (defineServer returns fresh config)\n * 2. Swap router handler + re-register room definitions\n * 3. matchMaker.hotReload() \u2014 cache rooms, dispose, restore\n */\nimport * as matchMaker from '@colyseus/core/MatchMaker';\nimport {\n setDevMode,\n createNodeMatchmakingMiddleware,\n dynamicImport,\n registerRoomDefinitions,\n unregisterRoomDefinitions,\n toNodeHandler,\n type RoomDefinitions,\n type ServerOptions,\n type Transport,\n type Router,\n} from '@colyseus/core';\nimport { setTransport } from '@colyseus/core/Transport';\nimport type { Plugin } from 'vite';\n\n// \u2500\u2500\u2500 Virtual module IDs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst VIRTUAL_SERVER_ENTRY = 'virtual:colyseus-server-entry';\nconst RESOLVED_VIRTUAL_SERVER_ENTRY = '\\0' + VIRTUAL_SERVER_ENTRY;\n\n// \u2500\u2500\u2500 Options \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ColyseusViteOptions {\n serverEntry: string;\n port?: number;\n quiet?: boolean;\n /**\n * Serve the built client files via express.static() in the production\n * server entry. Adds a SPA fallback that serves index.html for\n * unmatched GET requests.\n *\n * Has no effect in dev mode (Vite serves the frontend).\n */\n serveClient?: boolean;\n loadWsTransport?: () => Promise<{\n WebSocketTransport: new (options?: any) => Transport & {\n attachToServer(server: any, options?: { filter?: (req: any) => boolean }): any;\n };\n }>;\n}\n\n// \u2500\u2500\u2500 Internal types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype ServerConfig = {\n options?: ServerOptions;\n router?: Router;\n '~rooms'?: RoomDefinitions;\n};\n\ntype ServerModule = {\n server?: ServerConfig;\n rooms?: RoomDefinitions;\n default?: {\n server?: ServerConfig;\n rooms?: RoomDefinitions;\n };\n};\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction getServerExport(mod: ServerModule): ServerConfig | undefined {\n return mod.server || mod.default?.server;\n}\n\nfunction getRoomsExport(mod: ServerModule): RoomDefinitions | undefined {\n return mod.rooms || mod.default?.rooms;\n}\n\n// \u2500\u2500\u2500 Virtual module generators \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Production build entry \u2014 standalone server that imports the user's\n * server entry and calls `server.listen()`.\n */\nexport function createColyseusViteServerEntry(options: ColyseusViteOptions) {\n const port = options.port ?? 2567;\n\n const lines: string[] = [\n `import { Server, registerRoomDefinitions } from \"colyseus\";`,\n ];\n\n if (options.serveClient) {\n lines.push(\n `import express from \"express\";`,\n `import { fileURLToPath } from \"url\";`,\n `import { dirname, join } from \"path\";`,\n ``,\n `const __dirname = dirname(fileURLToPath(import.meta.url));`,\n `const clientDir = join(__dirname, \"../client\");`,\n );\n }\n\n lines.push(\n ``,\n `const entry = await import(${JSON.stringify(options.serverEntry)});`,\n `const server = entry.server ?? entry.default?.server;`,\n `const rooms = entry.rooms ?? entry.default?.rooms;`,\n ``,\n `if (server) {`,\n );\n\n if (options.serveClient) {\n lines.push(\n ` await server[\"_onTransportReady\"];`,\n ` if (server.transport.getExpressApp) {`,\n ` const app = server.transport.getExpressApp();`,\n ` app.use(express.static(clientDir));`,\n ` app.get(\"*all\", (req, res) => res.sendFile(join(clientDir, \"index.html\")));`,\n ` }`,\n );\n }\n\n lines.push(\n ` server.listen(${port});`,\n `} else if (rooms) {`,\n ` const gameServer = new Server();`,\n ` registerRoomDefinitions(rooms);`,\n ` gameServer.listen(${port});`,\n `} else {`,\n ` throw new Error('[colyseus] Server entry should export \\`server = defineServer(...)\\` or \\`rooms\\`.');`,\n `}`,\n );\n\n return lines.join('\\n');\n}\n\n// \u2500\u2500\u2500 Exported helpers (for testing) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function reloadColyseusViteRooms(\n importModule: (specifier: string) => Promise<any>,\n serverEntry: string,\n currentRoomNames: string[] = [],\n) {\n const mod = await importModule(serverEntry);\n\n unregisterRoomDefinitions(currentRoomNames);\n\n const server = getServerExport(mod);\n const rooms: RoomDefinitions | undefined = getRoomsExport(mod)\n || server?.['~rooms'];\n\n if (!rooms) {\n return {\n roomNames: [],\n hasRooms: false,\n server,\n };\n }\n\n return {\n roomNames: registerRoomDefinitions(rooms),\n hasRooms: true,\n server,\n };\n}\n\n// \u2500\u2500\u2500 Plugin \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function colyseus(options: ColyseusViteOptions): Plugin[] {\n let viteServer: any;\n let currentRoomNames: string[] = [];\n let currentAppHandler: ((req: any, res: any, next: any) => void) | null = null;\n let expressApp: any = null;\n let isStarted = false;\n\n return [\n {\n name: 'colyseus:config',\n config() {\n return {\n builder: {},\n build: { outDir: 'dist/client' },\n environments: {\n colyseus: {\n consumer: 'server' as const,\n resolve: {\n // Externalize all dependencies so they share the same module\n // instances (and matchMaker singleton) with the plugin process.\n // Without this, Vite re-evaluates workspace/linked packages in\n // the runner, creating isolated singletons \u2014 breaking monitor, etc.\n external: true,\n },\n build: {\n outDir: 'dist/server',\n ssr: true,\n rollupOptions: {\n input: VIRTUAL_SERVER_ENTRY,\n output: { entryFileNames: 'server.mjs' },\n },\n },\n },\n },\n };\n },\n resolveId(id: string) {\n if (id === VIRTUAL_SERVER_ENTRY) { return RESOLVED_VIRTUAL_SERVER_ENTRY; }\n },\n load(id: string) {\n if (id === RESOLVED_VIRTUAL_SERVER_ENTRY) {\n return createColyseusViteServerEntry(options);\n }\n },\n },\n\n {\n name: 'colyseus:dev-server',\n configureServer(server: any) {\n viteServer = server;\n server.middlewares.use(createNodeMatchmakingMiddleware());\n\n // Dynamic application middleware \u2014 handler is swapped on each HMR reload.\n server.middlewares.use((req: any, res: any, next: any) => {\n if (!currentAppHandler) { return next(); }\n currentAppHandler(req, res, next);\n });\n\n return async () => {\n if (!server.httpServer) {\n throw new Error('[colyseus] Vite HTTP server not available.');\n }\n await loadServerModule();\n console.log(\"[colyseus] Server ready on Vite's HTTP server\");\n };\n },\n },\n\n {\n name: 'colyseus:hmr',\n hotUpdate({ file, modules }) {\n if (this.environment?.name === 'colyseus' && modules.length > 0) {\n loadServerModule().then(() => {\n if (!options.quiet) {\n console.log(`[colyseus] Server code reloaded (${file})`);\n }\n }).catch((e) => {\n console.error('[colyseus] Failed to reload server module:', e);\n });\n }\n },\n },\n ];\n\n /**\n * Import (or re-import) the user's server entry and configure the\n * matchMaker, transport, rooms, and middleware.\n *\n * On initial load: sets up matchMaker, creates transport, registers rooms.\n * On HMR reload: re-imports user code, swaps rooms/router, hot-reloads\n * running rooms (cache \u2192 dispose \u2192 restore).\n */\n async function loadServerModule() {\n const env = viteServer.environments.colyseus;\n if (!env) {\n console.error('[colyseus] Environment not found');\n return;\n }\n\n try {\n // Clear the runner's evaluated module cache so re-import picks up\n // fresh user code. External packages (@colyseus/*) are cached by\n // Node's module system \u2014 they keep their singleton state.\n if (isStarted && env.runner.evaluatedModules) {\n env.runner.evaluatedModules.clear();\n }\n\n // \u2500\u2500 Step 1: Set up matchMaker + transport (initial load only) \u2500\u2500\n if (!isStarted) {\n setDevMode(true);\n await matchMaker.setup();\n\n const wsModule = await (options.loadWsTransport\n ? options.loadWsTransport()\n : dynamicImport<typeof import('@colyseus/ws-transport')>('@colyseus/ws-transport'));\n\n const transport = new wsModule.WebSocketTransport({ noServer: true });\n\n if (typeof (transport as any).attachToServer !== 'function') {\n throw new Error('[colyseus] Vite dev mode requires a transport with attachToServer().');\n }\n\n (transport as any).attachToServer(viteServer.httpServer, {\n filter(req: any) {\n return /^\\/[a-zA-Z0-9_-]+\\/[a-zA-Z0-9_-]+\\/?$/.test(\n new URL(req.url || '', 'http://localhost').pathname,\n );\n },\n });\n setTransport(transport);\n }\n\n // \u2500\u2500 Step 2: Import user module \u2500\u2500\n // In dev mode, defineServer() returns a config object (no Server\n // instance, no matchMaker.setup() call) because isDevMode is true.\n const mod = await env.runner.import(options.serverEntry);\n\n const config = getServerExport(mod);\n const rooms: RoomDefinitions | undefined = getRoomsExport(mod)\n || config?.['~rooms'];\n\n // \u2500\u2500 Step 3: Build application middleware (router + express) \u2500\u2500\n const router = config?.router;\n\n // Set up express once \u2014 persistent across HMR reloads.\n if (!expressApp && config?.options?.express) {\n try {\n const express = (await dynamicImport<any>('express')).default;\n expressApp = express();\n config.options.express(expressApp);\n } catch (e) {\n console.warn('[colyseus] Express not available. Install express to use the express option.');\n }\n }\n\n // Build combined handler: router (hot-swappable) + express (persistent).\n if (router || expressApp) {\n const routerHandler = router ? toNodeHandler(router.handler) : null;\n currentAppHandler = (req: any, res: any, next: any) => {\n if (router?.findRoute(req.method, req.url?.split('?')[0]) !== undefined) {\n routerHandler!(req, res);\n } else if (expressApp) {\n expressApp(req, res, next);\n } else {\n next();\n }\n };\n } else {\n currentAppHandler = null;\n }\n\n // \u2500\u2500 Step 4: Register room definitions \u2500\u2500\n // Must happen BEFORE hotReload() because reloadFromCache() needs\n // the new handlers to recreate room instances.\n unregisterRoomDefinitions(currentRoomNames);\n if (rooms) {\n currentRoomNames = registerRoomDefinitions(rooms);\n } else {\n currentRoomNames = [];\n console.warn(\n '[colyseus] Server entry should export `server = defineServer(...)` or `rooms`.',\n );\n }\n\n // \u2500\u2500 Step 5: Accept connections or hot-reload rooms \u2500\u2500\n if (!isStarted) {\n await matchMaker.accept();\n isStarted = true;\n } else {\n await matchMaker.hotReload();\n }\n\n if (!options.quiet) {\n for (const roomName of currentRoomNames) {\n console.log(`[colyseus] Room defined: \"${roomName}\"`);\n }\n }\n\n } catch (e) {\n console.error('[colyseus] Failed to load server module:', e);\n }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Colyseus Vite Plugin\n *\n * Integrates a Colyseus game server into Vite's dev server and build pipeline.\n *\n * ## Architecture\n *\n * Colyseus packages are externalized in the runner environment so they share\n * the same module instances \u2014 and therefore the same matchMaker singleton \u2014\n * as the plugin process. This lets user code (monitor, playground, custom\n * middleware) access the real matchMaker with actual room data.\n *\n * In dev mode, defineServer() returns a config-only object (no Server\n * instance). The plugin manages the matchMaker lifecycle, transport, and\n * HMR directly.\n *\n * On HMR:\n * 1. Re-import user module (defineServer returns fresh config)\n * 2. Swap router handler + re-register room definitions\n * 3. matchMaker.hotReload() \u2014 cache rooms, dispose, restore\n */\nimport * as matchMaker from '@colyseus/core/MatchMaker';\nimport {\n setDevMode,\n createNodeMatchmakingMiddleware,\n dynamicImport,\n registerRoomDefinitions,\n unregisterRoomDefinitions,\n toNodeHandler,\n type RoomDefinitions,\n type ServerOptions,\n type Transport,\n type Router,\n} from '@colyseus/core';\nimport { setTransport } from '@colyseus/core/Transport';\nimport type { Plugin } from 'vite';\n\n// \u2500\u2500\u2500 Virtual module IDs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst VIRTUAL_SERVER_ENTRY = 'virtual:colyseus-server-entry';\nconst RESOLVED_VIRTUAL_SERVER_ENTRY = '\\0' + VIRTUAL_SERVER_ENTRY;\n\n// \u2500\u2500\u2500 Options \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ColyseusViteOptions {\n serverEntry: string;\n port?: number;\n quiet?: boolean;\n /**\n * Serve the built client files via express.static() in the production\n * server entry. Adds a SPA fallback that serves index.html for\n * unmatched GET requests.\n *\n * Has no effect in dev mode (Vite serves the frontend).\n */\n serveClient?: boolean;\n loadWsTransport?: () => Promise<{\n WebSocketTransport: new (options?: any) => Transport & {\n attachToServer(server: any, options?: { filter?: (req: any) => boolean }): any;\n };\n }>;\n /**\n * HTTP server to attach the WebSocket transport to.\n *\n * Required when running Vite in middleware mode (`server.middlewareMode`),\n * where the HTTP server is owned by the parent process (e.g. Express).\n * In standalone Vite dev mode this is ignored \u2014 the plugin uses Vite's\n * own HTTP server.\n */\n httpServer?: import('http').Server;\n}\n\n// \u2500\u2500\u2500 Internal types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype ServerConfig = {\n options?: ServerOptions;\n router?: Router;\n '~rooms'?: RoomDefinitions;\n};\n\ntype ServerModule = {\n server?: ServerConfig;\n rooms?: RoomDefinitions;\n default?: {\n server?: ServerConfig;\n rooms?: RoomDefinitions;\n };\n};\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction getServerExport(mod: ServerModule): ServerConfig | undefined {\n return mod.server || mod.default?.server;\n}\n\nfunction getRoomsExport(mod: ServerModule): RoomDefinitions | undefined {\n return mod.rooms || mod.default?.rooms;\n}\n\n// \u2500\u2500\u2500 Virtual module generators \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Production build entry \u2014 standalone server that imports the user's\n * server entry and calls `server.listen()`.\n */\nexport function createColyseusViteServerEntry(options: ColyseusViteOptions) {\n const port = options.port ?? 2567;\n\n const lines: string[] = [\n `import { Server, registerRoomDefinitions } from \"colyseus\";`,\n ];\n\n if (options.serveClient) {\n lines.push(\n `import express from \"express\";`,\n `import { fileURLToPath } from \"url\";`,\n `import { dirname, join } from \"path\";`,\n ``,\n `const __dirname = dirname(fileURLToPath(import.meta.url));`,\n `const clientDir = join(__dirname, \"../client\");`,\n );\n }\n\n lines.push(\n ``,\n `const entry = await import(${JSON.stringify(options.serverEntry)});`,\n `const server = entry.server ?? entry.default?.server;`,\n `const rooms = entry.rooms ?? entry.default?.rooms;`,\n ``,\n `if (server) {`,\n );\n\n if (options.serveClient) {\n lines.push(\n ` await server[\"_onTransportReady\"];`,\n ` if (server.transport.getExpressApp) {`,\n ` const app = server.transport.getExpressApp();`,\n ` app.use(express.static(clientDir));`,\n ` app.get(\"*all\", (req, res) => res.sendFile(join(clientDir, \"index.html\")));`,\n ` }`,\n );\n }\n\n lines.push(\n ` server.listen(${port});`,\n `} else if (rooms) {`,\n ` const gameServer = new Server();`,\n ` registerRoomDefinitions(rooms);`,\n ` gameServer.listen(${port});`,\n `} else {`,\n ` throw new Error('[colyseus] Server entry should export \\`server = defineServer(...)\\` or \\`rooms\\`.');`,\n `}`,\n );\n\n return lines.join('\\n');\n}\n\n// \u2500\u2500\u2500 Exported helpers (for testing) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport async function reloadColyseusViteRooms(\n importModule: (specifier: string) => Promise<any>,\n serverEntry: string,\n currentRoomNames: string[] = [],\n) {\n const mod = await importModule(serverEntry);\n\n unregisterRoomDefinitions(currentRoomNames);\n\n const server = getServerExport(mod);\n const rooms: RoomDefinitions | undefined = getRoomsExport(mod)\n || server?.['~rooms'];\n\n if (!rooms) {\n return {\n roomNames: [],\n hasRooms: false,\n server,\n };\n }\n\n return {\n roomNames: registerRoomDefinitions(rooms),\n hasRooms: true,\n server,\n };\n}\n\n// \u2500\u2500\u2500 Plugin \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function colyseus(options: ColyseusViteOptions): Plugin[] {\n let viteServer: any;\n let currentRoomNames: string[] = [];\n let currentAppHandler: ((req: any, res: any, next: any) => void) | null = null;\n let expressApp: any = null;\n let isStarted = false;\n\n return [\n {\n name: 'colyseus:config',\n config() {\n return {\n builder: {},\n build: { outDir: 'dist/client' },\n environments: {\n colyseus: {\n consumer: 'server' as const,\n resolve: {\n // Externalize all dependencies so they share the same module\n // instances (and matchMaker singleton) with the plugin process.\n // Without this, Vite re-evaluates workspace/linked packages in\n // the runner, creating isolated singletons \u2014 breaking monitor, etc.\n external: true,\n },\n build: {\n outDir: 'dist/server',\n ssr: true,\n rollupOptions: {\n input: VIRTUAL_SERVER_ENTRY,\n output: { entryFileNames: 'server.mjs' },\n },\n },\n },\n },\n };\n },\n resolveId(id: string) {\n if (id === VIRTUAL_SERVER_ENTRY) { return RESOLVED_VIRTUAL_SERVER_ENTRY; }\n },\n load(id: string) {\n if (id === RESOLVED_VIRTUAL_SERVER_ENTRY) {\n return createColyseusViteServerEntry(options);\n }\n },\n },\n\n {\n name: 'colyseus:dev-server',\n configureServer(server: any) {\n viteServer = server;\n server.middlewares.use(createNodeMatchmakingMiddleware());\n\n // Dynamic application middleware \u2014 handler is swapped on each HMR reload.\n server.middlewares.use((req: any, res: any, next: any) => {\n if (!currentAppHandler) { return next(); }\n currentAppHandler(req, res, next);\n });\n\n return async () => {\n const httpServer = options.httpServer ?? server.httpServer;\n if (!httpServer) {\n throw new Error(\n '[colyseus] No HTTP server available. When running Vite in ' +\n 'middlewareMode, pass `httpServer` to the colyseus() plugin.'\n );\n }\n await loadServerModule(httpServer);\n console.log(\"[colyseus] Server ready on \" + (options.httpServer ? 'user-provided' : \"Vite's\") + ' HTTP server');\n };\n },\n },\n\n {\n name: 'colyseus:hmr',\n hotUpdate({ file, modules }) {\n if (this.environment?.name === 'colyseus' && modules.length > 0) {\n loadServerModule().then(() => {\n if (!options.quiet) {\n console.log(`[colyseus] Server code reloaded (${file})`);\n }\n }).catch((e) => {\n console.error('[colyseus] Failed to reload server module:', e);\n });\n }\n },\n },\n ];\n\n /**\n * Import (or re-import) the user's server entry and configure the\n * matchMaker, transport, rooms, and middleware.\n *\n * On initial load: sets up matchMaker, creates transport, registers rooms.\n * On HMR reload: re-imports user code, swaps rooms/router, hot-reloads\n * running rooms (cache \u2192 dispose \u2192 restore).\n */\n async function loadServerModule(httpServer?: import('http').Server) {\n const env = viteServer.environments.colyseus;\n if (!env) {\n console.error('[colyseus] Environment not found');\n return;\n }\n\n try {\n // Clear the runner's evaluated module cache so re-import picks up\n // fresh user code. External packages (@colyseus/*) are cached by\n // Node's module system \u2014 they keep their singleton state.\n if (isStarted && env.runner.evaluatedModules) {\n env.runner.evaluatedModules.clear();\n }\n\n // \u2500\u2500 Step 1: Set up matchMaker + transport (initial load only) \u2500\u2500\n if (!isStarted) {\n setDevMode(true);\n await matchMaker.setup();\n\n const wsModule = await (options.loadWsTransport\n ? options.loadWsTransport()\n : dynamicImport<typeof import('@colyseus/ws-transport')>('@colyseus/ws-transport'));\n\n const transport = new wsModule.WebSocketTransport({ noServer: true });\n\n if (typeof (transport as any).attachToServer !== 'function') {\n throw new Error('[colyseus] Vite dev mode requires a transport with attachToServer().');\n }\n\n (transport as any).attachToServer(httpServer ?? viteServer.httpServer, {\n filter(req: any) {\n return /^\\/[a-zA-Z0-9_-]+\\/[a-zA-Z0-9_-]+\\/?$/.test(\n new URL(req.url || '', 'http://localhost').pathname,\n );\n },\n });\n setTransport(transport);\n }\n\n // \u2500\u2500 Step 2: Import user module \u2500\u2500\n // In dev mode, defineServer() returns a config object (no Server\n // instance, no matchMaker.setup() call) because isDevMode is true.\n const mod = await env.runner.import(options.serverEntry);\n\n const config = getServerExport(mod);\n const rooms: RoomDefinitions | undefined = getRoomsExport(mod)\n || config?.['~rooms'];\n\n // \u2500\u2500 Step 3: Build application middleware (router + express) \u2500\u2500\n const router = config?.router;\n\n // Set up express once \u2014 persistent across HMR reloads.\n if (!expressApp && config?.options?.express) {\n try {\n const expressModule = await dynamicImport<any>('express');\n const express = expressModule?.default ?? expressModule;\n expressApp = express();\n await config.options.express(expressApp);\n } catch (e) {\n console.warn('[colyseus] Express not available. Install express to use the express option.');\n }\n }\n\n // Build combined handler: router (hot-swappable) + express (persistent).\n if (router || expressApp) {\n const routerHandler = router ? toNodeHandler(router.handler) : null;\n currentAppHandler = (req: any, res: any, next: any) => {\n if (router?.findRoute(req.method, req.url?.split('?')[0]) !== undefined) {\n routerHandler!(req, res);\n } else if (expressApp) {\n expressApp(req, res, next);\n } else {\n next();\n }\n };\n } else {\n currentAppHandler = null;\n }\n\n // \u2500\u2500 Step 4: Register room definitions \u2500\u2500\n // Must happen BEFORE hotReload() because reloadFromCache() needs\n // the new handlers to recreate room instances.\n unregisterRoomDefinitions(currentRoomNames);\n if (rooms) {\n currentRoomNames = registerRoomDefinitions(rooms);\n } else {\n currentRoomNames = [];\n console.warn(\n '[colyseus] Server entry should export `server = defineServer(...)` or `rooms`.',\n );\n }\n\n // \u2500\u2500 Step 5: Accept connections or hot-reload rooms \u2500\u2500\n if (!isStarted) {\n await matchMaker.accept();\n isStarted = true;\n } else {\n await matchMaker.hotReload();\n }\n\n if (!options.quiet) {\n for (const roomName of currentRoomNames) {\n console.log(`[colyseus] Room defined: \"${roomName}\"`);\n }\n }\n\n } catch (e) {\n console.error('[colyseus] Failed to load server module:', e);\n }\n }\n}\n"],
5
+ "mappings": ";AAqBA,YAAY,gBAAgB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP,SAAS,oBAAoB;AAK7B,IAAM,uBAAuB;AAC7B,IAAM,gCAAgC,OAAO;AAmD7C,SAAS,gBAAgB,KAA6C;AACpE,SAAO,IAAI,UAAU,IAAI,SAAS;AACpC;AAEA,SAAS,eAAe,KAAgD;AACtE,SAAO,IAAI,SAAS,IAAI,SAAS;AACnC;AAQO,SAAS,8BAA8B,SAA8B;AAC1E,QAAM,OAAO,QAAQ,QAAQ;AAE7B,QAAM,QAAkB;AAAA,IACtB;AAAA,EACF;AAEA,MAAI,QAAQ,aAAa;AACvB,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM;AAAA,IACJ;AAAA,IACA,8BAA8B,KAAK,UAAU,QAAQ,WAAW,CAAC;AAAA,IACjE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,QAAQ,aAAa;AACvB,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM;AAAA,IACJ,mBAAmB,IAAI;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA,uBAAuB,IAAI;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAIA,eAAsB,wBACpB,cACA,aACA,mBAA6B,CAAC,GAC9B;AACA,QAAM,MAAM,MAAM,aAAa,WAAW;AAE1C,4BAA0B,gBAAgB;AAE1C,QAAM,SAAS,gBAAgB,GAAG;AAClC,QAAM,QAAqC,eAAe,GAAG,KACxD,SAAS,QAAQ;AAEtB,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,MACL,WAAW,CAAC;AAAA,MACZ,UAAU;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,WAAW,wBAAwB,KAAK;AAAA,IACxC,UAAU;AAAA,IACV;AAAA,EACF;AACF;AAIO,SAAS,SAAS,SAAwC;AAC/D,MAAI;AACJ,MAAI,mBAA6B,CAAC;AAClC,MAAI,oBAAsE;AAC1E,MAAI,aAAkB;AACtB,MAAI,YAAY;AAEhB,SAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AACP,eAAO;AAAA,UACL,SAAS,CAAC;AAAA,UACV,OAAO,EAAE,QAAQ,cAAc;AAAA,UAC/B,cAAc;AAAA,YACZ,UAAU;AAAA,cACR,UAAU;AAAA,cACV,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,gBAKP,UAAU;AAAA,cACZ;AAAA,cACA,OAAO;AAAA,gBACL,QAAQ;AAAA,gBACR,KAAK;AAAA,gBACL,eAAe;AAAA,kBACb,OAAO;AAAA,kBACP,QAAQ,EAAE,gBAAgB,aAAa;AAAA,gBACzC;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA,UAAU,IAAY;AACpB,YAAI,OAAO,sBAAsB;AAAE,iBAAO;AAAA,QAA+B;AAAA,MAC3E;AAAA,MACA,KAAK,IAAY;AACf,YAAI,OAAO,+BAA+B;AACxC,iBAAO,8BAA8B,OAAO;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AAAA,IAEA;AAAA,MACE,MAAM;AAAA,MACN,gBAAgB,QAAa;AAC3B,qBAAa;AACb,eAAO,YAAY,IAAI,gCAAgC,CAAC;AAGxD,eAAO,YAAY,IAAI,CAAC,KAAU,KAAU,SAAc;AACxD,cAAI,CAAC,mBAAmB;AAAE,mBAAO,KAAK;AAAA,UAAG;AACzC,4BAAkB,KAAK,KAAK,IAAI;AAAA,QAClC,CAAC;AAED,eAAO,YAAY;AACjB,gBAAM,aAAa,QAAQ,cAAc,OAAO;AAChD,cAAI,CAAC,YAAY;AACf,kBAAM,IAAI;AAAA,cACR;AAAA,YAEF;AAAA,UACF;AACA,gBAAM,iBAAiB,UAAU;AACjC,kBAAQ,IAAI,iCAAiC,QAAQ,aAAa,kBAAkB,YAAY,cAAc;AAAA,QAChH;AAAA,MACF;AAAA,IACF;AAAA,IAEA;AAAA,MACE,MAAM;AAAA,MACN,UAAU,EAAE,MAAM,QAAQ,GAAG;AAC3B,YAAI,KAAK,aAAa,SAAS,cAAc,QAAQ,SAAS,GAAG;AAC/D,2BAAiB,EAAE,KAAK,MAAM;AAC5B,gBAAI,CAAC,QAAQ,OAAO;AAClB,sBAAQ,IAAI,oCAAoC,IAAI,GAAG;AAAA,YACzD;AAAA,UACF,CAAC,EAAE,MAAM,CAAC,MAAM;AACd,oBAAQ,MAAM,8CAA8C,CAAC;AAAA,UAC/D,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAUA,iBAAe,iBAAiB,YAAoC;AAClE,UAAM,MAAM,WAAW,aAAa;AACpC,QAAI,CAAC,KAAK;AACR,cAAQ,MAAM,kCAAkC;AAChD;AAAA,IACF;AAEA,QAAI;AAIF,UAAI,aAAa,IAAI,OAAO,kBAAkB;AAC5C,YAAI,OAAO,iBAAiB,MAAM;AAAA,MACpC;AAGA,UAAI,CAAC,WAAW;AACd,mBAAW,IAAI;AACf,cAAiB,iBAAM;AAEvB,cAAM,WAAW,OAAO,QAAQ,kBAC5B,QAAQ,gBAAgB,IACxB,cAAuD,wBAAwB;AAEnF,cAAM,YAAY,IAAI,SAAS,mBAAmB,EAAE,UAAU,KAAK,CAAC;AAEpE,YAAI,OAAQ,UAAkB,mBAAmB,YAAY;AAC3D,gBAAM,IAAI,MAAM,sEAAsE;AAAA,QACxF;AAEA,QAAC,UAAkB,eAAe,cAAc,WAAW,YAAY;AAAA,UACrE,OAAO,KAAU;AACf,mBAAO,wCAAwC;AAAA,cAC7C,IAAI,IAAI,IAAI,OAAO,IAAI,kBAAkB,EAAE;AAAA,YAC7C;AAAA,UACF;AAAA,QACF,CAAC;AACD,qBAAa,SAAS;AAAA,MACxB;AAKA,YAAM,MAAM,MAAM,IAAI,OAAO,OAAO,QAAQ,WAAW;AAEvD,YAAM,SAAS,gBAAgB,GAAG;AAClC,YAAM,QAAqC,eAAe,GAAG,KACxD,SAAS,QAAQ;AAGtB,YAAM,SAAS,QAAQ;AAGvB,UAAI,CAAC,cAAc,QAAQ,SAAS,SAAS;AAC3C,YAAI;AACF,gBAAM,gBAAgB,MAAM,cAAmB,SAAS;AACxD,gBAAM,UAAU,eAAe,WAAW;AAC1C,uBAAa,QAAQ;AACrB,gBAAM,OAAO,QAAQ,QAAQ,UAAU;AAAA,QACzC,SAAS,GAAG;AACV,kBAAQ,KAAK,8EAA8E;AAAA,QAC7F;AAAA,MACF;AAGA,UAAI,UAAU,YAAY;AACxB,cAAM,gBAAgB,SAAS,cAAc,OAAO,OAAO,IAAI;AAC/D,4BAAoB,CAAC,KAAU,KAAU,SAAc;AACrD,cAAI,QAAQ,UAAU,IAAI,QAAQ,IAAI,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC,MAAM,QAAW;AACvE,0BAAe,KAAK,GAAG;AAAA,UACzB,WAAW,YAAY;AACrB,uBAAW,KAAK,KAAK,IAAI;AAAA,UAC3B,OAAO;AACL,iBAAK;AAAA,UACP;AAAA,QACF;AAAA,MACF,OAAO;AACL,4BAAoB;AAAA,MACtB;AAKA,gCAA0B,gBAAgB;AAC1C,UAAI,OAAO;AACT,2BAAmB,wBAAwB,KAAK;AAAA,MAClD,OAAO;AACL,2BAAmB,CAAC;AACpB,gBAAQ;AAAA,UACN;AAAA,QACF;AAAA,MACF;AAGA,UAAI,CAAC,WAAW;AACd,cAAiB,kBAAO;AACxB,oBAAY;AAAA,MACd,OAAO;AACL,cAAiB,qBAAU;AAAA,MAC7B;AAEA,UAAI,CAAC,QAAQ,OAAO;AAClB,mBAAW,YAAY,kBAAkB;AACvC,kBAAQ,IAAI,6BAA6B,QAAQ,GAAG;AAAA,QACtD;AAAA,MACF;AAAA,IAEF,SAAS,GAAG;AACV,cAAQ,MAAM,4CAA4C,CAAC;AAAA,IAC7D;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "colyseus",
3
- "version": "0.17.9",
3
+ "version": "0.17.10",
4
4
  "description": "Multiplayer Framework for Node.js",
5
5
  "type": "module",
6
6
  "input": "./src/index.ts",
@@ -42,14 +42,14 @@
42
42
  "node": ">= 20.x"
43
43
  },
44
44
  "dependencies": {
45
+ "@colyseus/core": "^0.17.42",
46
+ "@colyseus/auth": "^0.17.9",
45
47
  "@colyseus/monitor": "^0.17.8",
46
- "@colyseus/core": "^0.17.41",
47
48
  "@colyseus/playground": "^0.17.12",
48
- "@colyseus/auth": "^0.17.9",
49
- "@colyseus/redis-driver": "^0.17.7",
50
49
  "@colyseus/redis-presence": "^0.17.7",
50
+ "@colyseus/tools": "^0.17.19",
51
51
  "@colyseus/ws-transport": "^0.17.13",
52
- "@colyseus/tools": "^0.17.19"
52
+ "@colyseus/redis-driver": "^0.17.7"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@colyseus/msgpackr": "*",
@@ -59,11 +59,11 @@
59
59
  "ws": "^8.18.0",
60
60
  "zod": "^4.1.12",
61
61
  "@colyseus/better-call": "^1.3.1",
62
+ "@colyseus/core": "^0.17.42",
62
63
  "@colyseus/drizzle-driver": "^0.17.7",
63
- "@colyseus/sdk": "^0.17.39",
64
64
  "@colyseus/tools": "^0.17.19",
65
- "@colyseus/core": "^0.17.41",
66
- "@colyseus/uwebsockets-transport": "^0.17.20"
65
+ "@colyseus/uwebsockets-transport": "^0.17.20",
66
+ "@colyseus/sdk": "^0.17.41"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "@colyseus/auth": "0.17.x",
package/src/vite.ts CHANGED
@@ -59,6 +59,15 @@ export interface ColyseusViteOptions {
59
59
  attachToServer(server: any, options?: { filter?: (req: any) => boolean }): any;
60
60
  };
61
61
  }>;
62
+ /**
63
+ * HTTP server to attach the WebSocket transport to.
64
+ *
65
+ * Required when running Vite in middleware mode (`server.middlewareMode`),
66
+ * where the HTTP server is owned by the parent process (e.g. Express).
67
+ * In standalone Vite dev mode this is ignored — the plugin uses Vite's
68
+ * own HTTP server.
69
+ */
70
+ httpServer?: import('http').Server;
62
71
  }
63
72
 
64
73
  // ─── Internal types ───────────────────────────────────────────────────
@@ -237,11 +246,15 @@ export function colyseus(options: ColyseusViteOptions): Plugin[] {
237
246
  });
238
247
 
239
248
  return async () => {
240
- if (!server.httpServer) {
241
- throw new Error('[colyseus] Vite HTTP server not available.');
249
+ const httpServer = options.httpServer ?? server.httpServer;
250
+ if (!httpServer) {
251
+ throw new Error(
252
+ '[colyseus] No HTTP server available. When running Vite in ' +
253
+ 'middlewareMode, pass `httpServer` to the colyseus() plugin.'
254
+ );
242
255
  }
243
- await loadServerModule();
244
- console.log("[colyseus] Server ready on Vite's HTTP server");
256
+ await loadServerModule(httpServer);
257
+ console.log("[colyseus] Server ready on " + (options.httpServer ? 'user-provided' : "Vite's") + ' HTTP server');
245
258
  };
246
259
  },
247
260
  },
@@ -270,7 +283,7 @@ export function colyseus(options: ColyseusViteOptions): Plugin[] {
270
283
  * On HMR reload: re-imports user code, swaps rooms/router, hot-reloads
271
284
  * running rooms (cache → dispose → restore).
272
285
  */
273
- async function loadServerModule() {
286
+ async function loadServerModule(httpServer?: import('http').Server) {
274
287
  const env = viteServer.environments.colyseus;
275
288
  if (!env) {
276
289
  console.error('[colyseus] Environment not found');
@@ -300,7 +313,7 @@ export function colyseus(options: ColyseusViteOptions): Plugin[] {
300
313
  throw new Error('[colyseus] Vite dev mode requires a transport with attachToServer().');
301
314
  }
302
315
 
303
- (transport as any).attachToServer(viteServer.httpServer, {
316
+ (transport as any).attachToServer(httpServer ?? viteServer.httpServer, {
304
317
  filter(req: any) {
305
318
  return /^\/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+\/?$/.test(
306
319
  new URL(req.url || '', 'http://localhost').pathname,
@@ -325,9 +338,10 @@ export function colyseus(options: ColyseusViteOptions): Plugin[] {
325
338
  // Set up express once — persistent across HMR reloads.
326
339
  if (!expressApp && config?.options?.express) {
327
340
  try {
328
- const express = (await dynamicImport<any>('express')).default;
341
+ const expressModule = await dynamicImport<any>('express');
342
+ const express = expressModule?.default ?? expressModule;
329
343
  expressApp = express();
330
- config.options.express(expressApp);
344
+ await config.options.express(expressApp);
331
345
  } catch (e) {
332
346
  console.warn('[colyseus] Express not available. Install express to use the express option.');
333
347
  }