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 +12 -8
- package/build/vite.cjs.map +2 -2
- package/build/vite.d.ts +9 -0
- package/build/vite.mjs +12 -8
- package/build/vite.mjs.map +2 -2
- package/package.json +8 -8
- package/src/vite.ts +22 -8
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
|
-
|
|
171
|
-
|
|
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
|
|
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
|
}
|
package/build/vite.cjs.map
CHANGED
|
@@ -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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBA,iBAA4B;AAC5B,kBAWO;AACP,uBAA6B;AAK7B,IAAM,uBAAuB;AAC7B,IAAM,gCAAgC,OAAO;
|
|
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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBA,iBAA4B;AAC5B,kBAWO;AACP,uBAA6B;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,6CAA0B,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,eAAW,qCAAwB,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,QAAI,6CAAgC,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,oCAAW,IAAI;AACf,cAAiB,iBAAM;AAEvB,cAAM,WAAW,OAAO,QAAQ,kBAC5B,QAAQ,gBAAgB,QACxB,2BAAuD,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,2CAAa,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,UAAM,2BAAmB,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,aAAS,2BAAc,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,iDAA0B,gBAAgB;AAC1C,UAAI,OAAO;AACT,+BAAmB,qCAAwB,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/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
|
-
|
|
142
|
-
|
|
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
|
|
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
|
}
|
package/build/vite.mjs.map
CHANGED
|
@@ -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": ";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;
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "colyseus",
|
|
3
|
-
"version": "0.17.
|
|
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/
|
|
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/
|
|
66
|
-
"@colyseus/
|
|
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
|
-
|
|
241
|
-
|
|
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
|
|
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
|
}
|