UNPKG

colyseus

Version:

Multiplayer Framework for Node.js

8 lines (7 loc) 18.8 kB
{ "version": 3, "sources": ["../src/vite.ts"], "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"], "mappings": "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", "names": [] }