colyseus 0.17.7 → 0.17.9
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/LICENSE +1 -3
- package/README.md +31 -16
- package/build/vite.cjs +277 -0
- package/build/vite.cjs.map +7 -0
- package/build/vite.d.ts +39 -0
- package/build/vite.mjs +247 -0
- package/build/vite.mjs.map +7 -0
- package/package.json +30 -18
- package/src/vite.ts +383 -0
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -7,19 +7,33 @@
|
|
|
7
7
|
<a href="https://npmjs.com/package/colyseus">
|
|
8
8
|
<img src="https://img.shields.io/npm/dm/colyseus.svg?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETESWYxR33AAAAtElEQVQoz4WQMQrCQBRE38Z0QoTcwF4Qg1h4BO0sxGOk80iCtViksrIQRRBTewWxMI1mbELYjYu+4rPMDPtn12ChMT3gavb4US5Jym0tcBIta3oDHv4Gwmr7nC4QAxBrCdzM2q6XqUnm9m9r59h7Rc0n2pFv24k4ttGMUXW+sGELTJjSr7QDKuqLS6UKFChVWWuFkZw9Z2AAvAirKT+JTlppIRnd6XgaP4goefI2Shj++OnjB3tBmHYK8z9zAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE5LTAyLTAxVDE4OjE3OjM3KzAxOjAwGQQixQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOS0wMi0wMVQxODoxNzozNyswMTowMGhZmnkAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC">
|
|
9
9
|
</a>
|
|
10
|
-
<a href="https://github.com/colyseus/colyseus/discussions" title="Discuss on Forum">
|
|
11
|
-
<img src="https://img.shields.io/badge/discuss-on%20forum-brightgreen.svg?style=for-the-badge&colorB=0069b8&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETDROxCNUzAAABB0lEQVQoz4WRvyvEARjGP193CnWRH+dHQmGwKZtFGcSmxHAL400GN95ktIpV2dzlLzDJgsGgGNRdDAzoQueS/PgY3HXHyT3T+/Y87/s89UANBKXBdoZo5J6L4K1K5ZxHfnjnlQUf3bKvkgy57a0r9hS3cXfMO1kWJMza++tj3Ac7/LY343x1NA9cNmYMwnSS/SP8JVFuSJmr44iFqvtmpjhmhBCrOOazCesq6H4P3bPBjFoIBydOk2bUA17I080Es+wSZ51B4DIA2zgjSpYcEe44Js01G0XjRcCU+y4ZMrDeLmfc9EnVd5M/o0VMeu6nJZxWJivLmhyw1WHTvrr2b4+2OFqra+ALwouTMDcqmjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDItMDFUMTg6MTM6MTkrMDE6MDAC9f6fAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTAyLTAxVDE4OjEzOjE5KzAxOjAwc6hGIwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAASUVORK5CYII=" alt="Discussion forum" />
|
|
12
|
-
</a>
|
|
13
10
|
<a href="http://chat.colyseus.io">
|
|
14
11
|
<img src="https://img.shields.io/discord/525739117951320081.svg?style=for-the-badge&colorB=7581dc&logo=discord&logoColor=white">
|
|
15
12
|
</a>
|
|
13
|
+
<a href="https://github.com/colyseus/colyseus/discussions" title="Discuss Forum">
|
|
14
|
+
<img src="https://img.shields.io/badge/discuss-forum-brightgreen.svg?style=for-the-badge&colorB=0069b8&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETDROxCNUzAAABB0lEQVQoz4WRvyvEARjGP193CnWRH+dHQmGwKZtFGcSmxHAL400GN95ktIpV2dzlLzDJgsGgGNRdDAzoQueS/PgY3HXHyT3T+/Y87/s89UANBKXBdoZo5J6L4K1K5ZxHfnjnlQUf3bKvkgy57a0r9hS3cXfMO1kWJMza++tj3Ac7/LY343x1NA9cNmYMwnSS/SP8JVFuSJmr44iFqvtmpjhmhBCrOOazCesq6H4P3bPBjFoIBydOk2bUA17I080Es+wSZ51B4DIA2zgjSpYcEe44Js01G0XjRcCU+y4ZMrDeLmfc9EnVd5M/o0VMeu6nJZxWJivLmhyw1WHTvrr2b4+2OFqra+ALwouTMDcqmjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDItMDFUMTg6MTM6MTkrMDE6MDAC9f6fAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTAyLTAxVDE4OjEzOjE5KzAxOjAwc6hGIwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAASUVORK5CYII=" alt="Discussion forum" />
|
|
15
|
+
</a>
|
|
16
16
|
<h3>
|
|
17
17
|
Multiplayer Framework for Node.js. <br /><a href="https://docs.colyseus.io/">View documentation</a>
|
|
18
18
|
</h3>
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
-
Colyseus is an Authoritative Multiplayer Framework for Node.js, with
|
|
22
|
-
available for
|
|
21
|
+
Colyseus is an Authoritative Multiplayer Framework for Node.js, with SDKs
|
|
22
|
+
available for all major platforms and engines.
|
|
23
|
+
|
|
24
|
+
## Client SDKs
|
|
25
|
+
|
|
26
|
+
| Platform | Install | Documentation | Demo |
|
|
27
|
+
|----------|---------|---------------|------|
|
|
28
|
+
| TypeScript | `npm install @colyseus/sdk` | [Getting Started](https://docs.colyseus.io/getting-started/typescript) | [PlayCanvas](https://github.com/endel/tank-battle-multiplayer/tree/master/web-playcanvas) |
|
|
29
|
+
| React | `npm install @colyseus/react` | [Getting Started](https://docs.colyseus.io/getting-started/react) | [R3F Lobby](https://github.com/endel/r3f-lobby-car-prototype) |
|
|
30
|
+
| Unity | [Download](https://github.com/colyseus/colyseus-unity3d/releases/latest/download/Colyseus_Plugin.unitypackage) | [Getting Started](https://docs.colyseus.io/getting-started/unity) | [Tank Battle](https://github.com/endel/tank-battle-multiplayer/tree/master/unity) |
|
|
31
|
+
| Godot | [Download](https://github.com/colyseus/native-sdk/releases?q=godot+sdk&expanded=true) | [Getting Started](https://docs.colyseus.io/getting-started/godot) | [Tank Battle](https://github.com/endel/tank-battle-multiplayer/tree/master/godot) |
|
|
32
|
+
| GameMaker | [Download](https://github.com/colyseus/native-sdk/releases?q=gamemaker+sdk&expanded=true) | [Getting Started](https://docs.colyseus.io/getting-started/gamemaker) | [Tank Battle](https://github.com/endel/tank-battle-multiplayer/tree/master/gamemaker) |
|
|
33
|
+
| Defold | See documentation → | [Getting Started](https://docs.colyseus.io/getting-started/defold) | [Tank Battle](https://github.com/endel/tank-battle-multiplayer/tree/master/defold) |
|
|
34
|
+
| Construct | [Download](https://www.construct.net/en/make-games/addons/111/colyseus-multiplayer-sdk) | [Getting Started](https://docs.colyseus.io/getting-started/construct3) | [Raw Demo](https://github.com/colyseus/construct3-demo) |
|
|
35
|
+
| Haxe | `haxelib install colyseus` | [Getting Started](https://docs.colyseus.io/getting-started/haxe) | [Tank Battle](https://github.com/endel/tank-battle-multiplayer/tree/master/haxe) |
|
|
36
|
+
| C / Static Libraries | [Download](https://github.com/colyseus/native-sdk/releases?q=%22Colyseus+Native+SDK+-+Static+Library%22&expanded=true) | [GitHub](https://github.com/colyseus/native-sdk) | [raylib](https://github.com/colyseus/native-sdk/tree/main/platforms/raylib) |
|
|
23
37
|
|
|
24
38
|
The project focuses on providing synchronizable data structures for realtime and
|
|
25
39
|
turn-based games, matchmaking, and ease of usage both on the server-side and
|
|
@@ -28,23 +42,24 @@ client-side.
|
|
|
28
42
|
The mission of the framework is to be a standard netcode & matchmaking solution
|
|
29
43
|
for any kind of project you can think of!
|
|
30
44
|
|
|
31
|
-
##
|
|
45
|
+
## Why developers choose Colyseus:
|
|
32
46
|
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
47
|
+
- ⚡️ **Real-time state sync that just works** → Define your state on the server and it automatically synchronizes to all clients, delta-compressed and binary-encoded.
|
|
48
|
+
- ⚔️ **Built-in matchmaking** → Room-based architecture with filtering, queuing, and reconnection support out of the box.
|
|
49
|
+
- 📈 **Scalable** → Go from 10 to 10,000+ CCU by scaling vertically or horizontally with Redis and load balancers.
|
|
50
|
+
- 🛡️ **Cheat-proof by design** → Authoritative server model ensures game logic runs on the server, not the client.
|
|
51
|
+
- 🛠️ **Use the tools you already know** → Built on Node.js and TypeScript with a simple, familiar API on both server and client.
|
|
52
|
+
- 💙 **Free forever** → MIT licensed, even for commercial games.
|
|
38
53
|
|
|
39
|
-
See [public roadmap](https://
|
|
54
|
+
See [public roadmap](https://docs.colyseus.io/roadmap) for version 1.0.
|
|
40
55
|
|
|
41
56
|
# 🚀 Quickstart
|
|
42
57
|
|
|
43
|
-
|
|
58
|
+
Set up your own Colyseus server project for your game using `npm create colyseus-app@latest`:
|
|
44
59
|
|
|
45
60
|
```
|
|
46
|
-
npm create colyseus-app@latest my-
|
|
47
|
-
cd my-
|
|
61
|
+
npm create colyseus-app@latest ./my-server
|
|
62
|
+
cd my-server
|
|
48
63
|
npm start
|
|
49
64
|
```
|
|
50
65
|
|
|
@@ -87,7 +102,7 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds
|
|
|
87
102
|
<td align="center"><a href="https://github.com/TinyDobbins"><img src="https://avatars2.githubusercontent.com/u/20824844?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nikita Borisov</b></sub></a><br /><a href="https://github.com/colyseus/colyseus/issues?q=author%3ATinyDobbins" title="Bug reports">🐛</a> <a href="https://github.com/colyseus/colyseus/commits?author=TinyDobbins" title="Code">💻</a> <a href="#business-TinyDobbins" title="Business development">💼</a> <a href="#ideas-TinyDobbins" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
88
103
|
<td align="center"><a href="https://acemobe.com/"><img src="https://avatars2.githubusercontent.com/u/232101?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Phil Harvey</b></sub></a><br /><a href="https://github.com/colyseus/colyseus/commits?author=filharvey" title="Documentation">📖</a></td>
|
|
89
104
|
<td align="center"><a href="https://github.com/serjek"><img src="https://avatars2.githubusercontent.com/u/18265157?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sergey</b></sub></a><br /><a href="https://github.com/colyseus/colyseus/issues?q=author%3Aserjek" title="Bug reports">🐛</a> <a href="https://github.com/colyseus/colyseus/commits?author=serjek" title="Code">💻</a></td>
|
|
90
|
-
<td align="center"><a href="https://
|
|
105
|
+
<td align="center"><a href="https://devlsh.com"><img src="https://avatars0.githubusercontent.com/u/853683?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sophie</b></sub></a><br /><a href="#question-devlsh" title="Answering Questions">💬</a> <a href="https://github.com/colyseus/colyseus/issues?q=author%3Adevlsh" title="Bug reports">🐛</a> <a href="#ideas-devlsh" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
91
106
|
<td align="center"><a href="https://github.com/supertommy"><img src="https://avatars0.githubusercontent.com/u/2236153?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tommy Leung</b></sub></a><br /><a href="#mentoring-supertommy" title="Mentoring">🧑🏫</a></td>
|
|
92
107
|
<td align="center"><a href="https://github.com/digimbyte"><img src="https://avatars2.githubusercontent.com/u/6645396?v=4?s=100" width="100px;" alt=""/><br /><sub><b>digimbyte</b></sub></a><br /><a href="https://github.com/colyseus/colyseus/commits?author=digimbyte" title="Documentation">📖</a></td>
|
|
93
108
|
</tr>
|
package/build/vite.cjs
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// bundles/colyseus/src/vite.ts
|
|
31
|
+
var vite_exports = {};
|
|
32
|
+
__export(vite_exports, {
|
|
33
|
+
colyseus: () => colyseus,
|
|
34
|
+
createColyseusViteServerEntry: () => createColyseusViteServerEntry,
|
|
35
|
+
reloadColyseusViteRooms: () => reloadColyseusViteRooms
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(vite_exports);
|
|
38
|
+
var matchMaker = __toESM(require("@colyseus/core/MatchMaker"), 1);
|
|
39
|
+
var import_core = require("@colyseus/core");
|
|
40
|
+
var import_Transport = require("@colyseus/core/Transport");
|
|
41
|
+
var VIRTUAL_SERVER_ENTRY = "virtual:colyseus-server-entry";
|
|
42
|
+
var RESOLVED_VIRTUAL_SERVER_ENTRY = "\0" + VIRTUAL_SERVER_ENTRY;
|
|
43
|
+
function getServerExport(mod) {
|
|
44
|
+
return mod.server || mod.default?.server;
|
|
45
|
+
}
|
|
46
|
+
function getRoomsExport(mod) {
|
|
47
|
+
return mod.rooms || mod.default?.rooms;
|
|
48
|
+
}
|
|
49
|
+
function createColyseusViteServerEntry(options) {
|
|
50
|
+
const port = options.port ?? 2567;
|
|
51
|
+
const lines = [
|
|
52
|
+
`import { Server, registerRoomDefinitions } from "colyseus";`
|
|
53
|
+
];
|
|
54
|
+
if (options.serveClient) {
|
|
55
|
+
lines.push(
|
|
56
|
+
`import express from "express";`,
|
|
57
|
+
`import { fileURLToPath } from "url";`,
|
|
58
|
+
`import { dirname, join } from "path";`,
|
|
59
|
+
``,
|
|
60
|
+
`const __dirname = dirname(fileURLToPath(import.meta.url));`,
|
|
61
|
+
`const clientDir = join(__dirname, "../client");`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
lines.push(
|
|
65
|
+
``,
|
|
66
|
+
`const entry = await import(${JSON.stringify(options.serverEntry)});`,
|
|
67
|
+
`const server = entry.server ?? entry.default?.server;`,
|
|
68
|
+
`const rooms = entry.rooms ?? entry.default?.rooms;`,
|
|
69
|
+
``,
|
|
70
|
+
`if (server) {`
|
|
71
|
+
);
|
|
72
|
+
if (options.serveClient) {
|
|
73
|
+
lines.push(
|
|
74
|
+
` await server["_onTransportReady"];`,
|
|
75
|
+
` if (server.transport.getExpressApp) {`,
|
|
76
|
+
` const app = server.transport.getExpressApp();`,
|
|
77
|
+
` app.use(express.static(clientDir));`,
|
|
78
|
+
` app.get("*all", (req, res) => res.sendFile(join(clientDir, "index.html")));`,
|
|
79
|
+
` }`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
lines.push(
|
|
83
|
+
` server.listen(${port});`,
|
|
84
|
+
`} else if (rooms) {`,
|
|
85
|
+
` const gameServer = new Server();`,
|
|
86
|
+
` registerRoomDefinitions(rooms);`,
|
|
87
|
+
` gameServer.listen(${port});`,
|
|
88
|
+
`} else {`,
|
|
89
|
+
` throw new Error('[colyseus] Server entry should export \`server = defineServer(...)\` or \`rooms\`.');`,
|
|
90
|
+
`}`
|
|
91
|
+
);
|
|
92
|
+
return lines.join("\n");
|
|
93
|
+
}
|
|
94
|
+
async function reloadColyseusViteRooms(importModule, serverEntry, currentRoomNames = []) {
|
|
95
|
+
const mod = await importModule(serverEntry);
|
|
96
|
+
(0, import_core.unregisterRoomDefinitions)(currentRoomNames);
|
|
97
|
+
const server = getServerExport(mod);
|
|
98
|
+
const rooms = getRoomsExport(mod) || server?.["~rooms"];
|
|
99
|
+
if (!rooms) {
|
|
100
|
+
return {
|
|
101
|
+
roomNames: [],
|
|
102
|
+
hasRooms: false,
|
|
103
|
+
server
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
roomNames: (0, import_core.registerRoomDefinitions)(rooms),
|
|
108
|
+
hasRooms: true,
|
|
109
|
+
server
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function colyseus(options) {
|
|
113
|
+
let viteServer;
|
|
114
|
+
let currentRoomNames = [];
|
|
115
|
+
let currentAppHandler = null;
|
|
116
|
+
let expressApp = null;
|
|
117
|
+
let isStarted = false;
|
|
118
|
+
return [
|
|
119
|
+
{
|
|
120
|
+
name: "colyseus:config",
|
|
121
|
+
config() {
|
|
122
|
+
return {
|
|
123
|
+
builder: {},
|
|
124
|
+
build: { outDir: "dist/client" },
|
|
125
|
+
environments: {
|
|
126
|
+
colyseus: {
|
|
127
|
+
consumer: "server",
|
|
128
|
+
resolve: {
|
|
129
|
+
// Externalize all dependencies so they share the same module
|
|
130
|
+
// instances (and matchMaker singleton) with the plugin process.
|
|
131
|
+
// Without this, Vite re-evaluates workspace/linked packages in
|
|
132
|
+
// the runner, creating isolated singletons — breaking monitor, etc.
|
|
133
|
+
external: true
|
|
134
|
+
},
|
|
135
|
+
build: {
|
|
136
|
+
outDir: "dist/server",
|
|
137
|
+
ssr: true,
|
|
138
|
+
rollupOptions: {
|
|
139
|
+
input: VIRTUAL_SERVER_ENTRY,
|
|
140
|
+
output: { entryFileNames: "server.mjs" }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
resolveId(id) {
|
|
148
|
+
if (id === VIRTUAL_SERVER_ENTRY) {
|
|
149
|
+
return RESOLVED_VIRTUAL_SERVER_ENTRY;
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
load(id) {
|
|
153
|
+
if (id === RESOLVED_VIRTUAL_SERVER_ENTRY) {
|
|
154
|
+
return createColyseusViteServerEntry(options);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "colyseus:dev-server",
|
|
160
|
+
configureServer(server) {
|
|
161
|
+
viteServer = server;
|
|
162
|
+
server.middlewares.use((0, import_core.createNodeMatchmakingMiddleware)());
|
|
163
|
+
server.middlewares.use((req, res, next) => {
|
|
164
|
+
if (!currentAppHandler) {
|
|
165
|
+
return next();
|
|
166
|
+
}
|
|
167
|
+
currentAppHandler(req, res, next);
|
|
168
|
+
});
|
|
169
|
+
return async () => {
|
|
170
|
+
if (!server.httpServer) {
|
|
171
|
+
throw new Error("[colyseus] Vite HTTP server not available.");
|
|
172
|
+
}
|
|
173
|
+
await loadServerModule();
|
|
174
|
+
console.log("[colyseus] Server ready on Vite's HTTP server");
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: "colyseus:hmr",
|
|
180
|
+
hotUpdate({ file, modules }) {
|
|
181
|
+
if (this.environment?.name === "colyseus" && modules.length > 0) {
|
|
182
|
+
loadServerModule().then(() => {
|
|
183
|
+
if (!options.quiet) {
|
|
184
|
+
console.log(`[colyseus] Server code reloaded (${file})`);
|
|
185
|
+
}
|
|
186
|
+
}).catch((e) => {
|
|
187
|
+
console.error("[colyseus] Failed to reload server module:", e);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
];
|
|
193
|
+
async function loadServerModule() {
|
|
194
|
+
const env = viteServer.environments.colyseus;
|
|
195
|
+
if (!env) {
|
|
196
|
+
console.error("[colyseus] Environment not found");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
if (isStarted && env.runner.evaluatedModules) {
|
|
201
|
+
env.runner.evaluatedModules.clear();
|
|
202
|
+
}
|
|
203
|
+
if (!isStarted) {
|
|
204
|
+
(0, import_core.setDevMode)(true);
|
|
205
|
+
await matchMaker.setup();
|
|
206
|
+
const wsModule = await (options.loadWsTransport ? options.loadWsTransport() : (0, import_core.dynamicImport)("@colyseus/ws-transport"));
|
|
207
|
+
const transport = new wsModule.WebSocketTransport({ noServer: true });
|
|
208
|
+
if (typeof transport.attachToServer !== "function") {
|
|
209
|
+
throw new Error("[colyseus] Vite dev mode requires a transport with attachToServer().");
|
|
210
|
+
}
|
|
211
|
+
transport.attachToServer(viteServer.httpServer, {
|
|
212
|
+
filter(req) {
|
|
213
|
+
return /^\/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+\/?$/.test(
|
|
214
|
+
new URL(req.url || "", "http://localhost").pathname
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
(0, import_Transport.setTransport)(transport);
|
|
219
|
+
}
|
|
220
|
+
const mod = await env.runner.import(options.serverEntry);
|
|
221
|
+
const config = getServerExport(mod);
|
|
222
|
+
const rooms = getRoomsExport(mod) || config?.["~rooms"];
|
|
223
|
+
const router = config?.router;
|
|
224
|
+
if (!expressApp && config?.options?.express) {
|
|
225
|
+
try {
|
|
226
|
+
const express = (await (0, import_core.dynamicImport)("express")).default;
|
|
227
|
+
expressApp = express();
|
|
228
|
+
config.options.express(expressApp);
|
|
229
|
+
} catch (e) {
|
|
230
|
+
console.warn("[colyseus] Express not available. Install express to use the express option.");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (router || expressApp) {
|
|
234
|
+
const routerHandler = router ? (0, import_core.toNodeHandler)(router.handler) : null;
|
|
235
|
+
currentAppHandler = (req, res, next) => {
|
|
236
|
+
if (router?.findRoute(req.method, req.url?.split("?")[0]) !== void 0) {
|
|
237
|
+
routerHandler(req, res);
|
|
238
|
+
} else if (expressApp) {
|
|
239
|
+
expressApp(req, res, next);
|
|
240
|
+
} else {
|
|
241
|
+
next();
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
} else {
|
|
245
|
+
currentAppHandler = null;
|
|
246
|
+
}
|
|
247
|
+
(0, import_core.unregisterRoomDefinitions)(currentRoomNames);
|
|
248
|
+
if (rooms) {
|
|
249
|
+
currentRoomNames = (0, import_core.registerRoomDefinitions)(rooms);
|
|
250
|
+
} else {
|
|
251
|
+
currentRoomNames = [];
|
|
252
|
+
console.warn(
|
|
253
|
+
"[colyseus] Server entry should export `server = defineServer(...)` or `rooms`."
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
if (!isStarted) {
|
|
257
|
+
await matchMaker.accept();
|
|
258
|
+
isStarted = true;
|
|
259
|
+
} else {
|
|
260
|
+
await matchMaker.hotReload();
|
|
261
|
+
}
|
|
262
|
+
if (!options.quiet) {
|
|
263
|
+
for (const roomName of currentRoomNames) {
|
|
264
|
+
console.log(`[colyseus] Room defined: "${roomName}"`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} catch (e) {
|
|
268
|
+
console.error("[colyseus] Failed to load server module:", e);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
273
|
+
0 && (module.exports = {
|
|
274
|
+
colyseus,
|
|
275
|
+
createColyseusViteServerEntry,
|
|
276
|
+
reloadColyseusViteRooms
|
|
277
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/build/vite.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type RoomDefinitions, type ServerOptions, type Transport, type Router } from '@colyseus/core';
|
|
2
|
+
import type { Plugin } from 'vite';
|
|
3
|
+
export interface ColyseusViteOptions {
|
|
4
|
+
serverEntry: string;
|
|
5
|
+
port?: number;
|
|
6
|
+
quiet?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Serve the built client files via express.static() in the production
|
|
9
|
+
* server entry. Adds a SPA fallback that serves index.html for
|
|
10
|
+
* unmatched GET requests.
|
|
11
|
+
*
|
|
12
|
+
* Has no effect in dev mode (Vite serves the frontend).
|
|
13
|
+
*/
|
|
14
|
+
serveClient?: boolean;
|
|
15
|
+
loadWsTransport?: () => Promise<{
|
|
16
|
+
WebSocketTransport: new (options?: any) => Transport & {
|
|
17
|
+
attachToServer(server: any, options?: {
|
|
18
|
+
filter?: (req: any) => boolean;
|
|
19
|
+
}): any;
|
|
20
|
+
};
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
type ServerConfig = {
|
|
24
|
+
options?: ServerOptions;
|
|
25
|
+
router?: Router;
|
|
26
|
+
'~rooms'?: RoomDefinitions;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Production build entry — standalone server that imports the user's
|
|
30
|
+
* server entry and calls `server.listen()`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createColyseusViteServerEntry(options: ColyseusViteOptions): string;
|
|
33
|
+
export declare function reloadColyseusViteRooms(importModule: (specifier: string) => Promise<any>, serverEntry: string, currentRoomNames?: string[]): Promise<{
|
|
34
|
+
roomNames: string[];
|
|
35
|
+
hasRooms: boolean;
|
|
36
|
+
server: ServerConfig;
|
|
37
|
+
}>;
|
|
38
|
+
export declare function colyseus(options: ColyseusViteOptions): Plugin[];
|
|
39
|
+
export {};
|
package/build/vite.mjs
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// bundles/colyseus/src/vite.ts
|
|
2
|
+
import * as matchMaker from "@colyseus/core/MatchMaker";
|
|
3
|
+
import {
|
|
4
|
+
setDevMode,
|
|
5
|
+
createNodeMatchmakingMiddleware,
|
|
6
|
+
dynamicImport,
|
|
7
|
+
registerRoomDefinitions,
|
|
8
|
+
unregisterRoomDefinitions,
|
|
9
|
+
toNodeHandler
|
|
10
|
+
} from "@colyseus/core";
|
|
11
|
+
import { setTransport } from "@colyseus/core/Transport";
|
|
12
|
+
var VIRTUAL_SERVER_ENTRY = "virtual:colyseus-server-entry";
|
|
13
|
+
var RESOLVED_VIRTUAL_SERVER_ENTRY = "\0" + VIRTUAL_SERVER_ENTRY;
|
|
14
|
+
function getServerExport(mod) {
|
|
15
|
+
return mod.server || mod.default?.server;
|
|
16
|
+
}
|
|
17
|
+
function getRoomsExport(mod) {
|
|
18
|
+
return mod.rooms || mod.default?.rooms;
|
|
19
|
+
}
|
|
20
|
+
function createColyseusViteServerEntry(options) {
|
|
21
|
+
const port = options.port ?? 2567;
|
|
22
|
+
const lines = [
|
|
23
|
+
`import { Server, registerRoomDefinitions } from "colyseus";`
|
|
24
|
+
];
|
|
25
|
+
if (options.serveClient) {
|
|
26
|
+
lines.push(
|
|
27
|
+
`import express from "express";`,
|
|
28
|
+
`import { fileURLToPath } from "url";`,
|
|
29
|
+
`import { dirname, join } from "path";`,
|
|
30
|
+
``,
|
|
31
|
+
`const __dirname = dirname(fileURLToPath(import.meta.url));`,
|
|
32
|
+
`const clientDir = join(__dirname, "../client");`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
lines.push(
|
|
36
|
+
``,
|
|
37
|
+
`const entry = await import(${JSON.stringify(options.serverEntry)});`,
|
|
38
|
+
`const server = entry.server ?? entry.default?.server;`,
|
|
39
|
+
`const rooms = entry.rooms ?? entry.default?.rooms;`,
|
|
40
|
+
``,
|
|
41
|
+
`if (server) {`
|
|
42
|
+
);
|
|
43
|
+
if (options.serveClient) {
|
|
44
|
+
lines.push(
|
|
45
|
+
` await server["_onTransportReady"];`,
|
|
46
|
+
` if (server.transport.getExpressApp) {`,
|
|
47
|
+
` const app = server.transport.getExpressApp();`,
|
|
48
|
+
` app.use(express.static(clientDir));`,
|
|
49
|
+
` app.get("*all", (req, res) => res.sendFile(join(clientDir, "index.html")));`,
|
|
50
|
+
` }`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
lines.push(
|
|
54
|
+
` server.listen(${port});`,
|
|
55
|
+
`} else if (rooms) {`,
|
|
56
|
+
` const gameServer = new Server();`,
|
|
57
|
+
` registerRoomDefinitions(rooms);`,
|
|
58
|
+
` gameServer.listen(${port});`,
|
|
59
|
+
`} else {`,
|
|
60
|
+
` throw new Error('[colyseus] Server entry should export \`server = defineServer(...)\` or \`rooms\`.');`,
|
|
61
|
+
`}`
|
|
62
|
+
);
|
|
63
|
+
return lines.join("\n");
|
|
64
|
+
}
|
|
65
|
+
async function reloadColyseusViteRooms(importModule, serverEntry, currentRoomNames = []) {
|
|
66
|
+
const mod = await importModule(serverEntry);
|
|
67
|
+
unregisterRoomDefinitions(currentRoomNames);
|
|
68
|
+
const server = getServerExport(mod);
|
|
69
|
+
const rooms = getRoomsExport(mod) || server?.["~rooms"];
|
|
70
|
+
if (!rooms) {
|
|
71
|
+
return {
|
|
72
|
+
roomNames: [],
|
|
73
|
+
hasRooms: false,
|
|
74
|
+
server
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
roomNames: registerRoomDefinitions(rooms),
|
|
79
|
+
hasRooms: true,
|
|
80
|
+
server
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function colyseus(options) {
|
|
84
|
+
let viteServer;
|
|
85
|
+
let currentRoomNames = [];
|
|
86
|
+
let currentAppHandler = null;
|
|
87
|
+
let expressApp = null;
|
|
88
|
+
let isStarted = false;
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
name: "colyseus:config",
|
|
92
|
+
config() {
|
|
93
|
+
return {
|
|
94
|
+
builder: {},
|
|
95
|
+
build: { outDir: "dist/client" },
|
|
96
|
+
environments: {
|
|
97
|
+
colyseus: {
|
|
98
|
+
consumer: "server",
|
|
99
|
+
resolve: {
|
|
100
|
+
// Externalize all dependencies so they share the same module
|
|
101
|
+
// instances (and matchMaker singleton) with the plugin process.
|
|
102
|
+
// Without this, Vite re-evaluates workspace/linked packages in
|
|
103
|
+
// the runner, creating isolated singletons — breaking monitor, etc.
|
|
104
|
+
external: true
|
|
105
|
+
},
|
|
106
|
+
build: {
|
|
107
|
+
outDir: "dist/server",
|
|
108
|
+
ssr: true,
|
|
109
|
+
rollupOptions: {
|
|
110
|
+
input: VIRTUAL_SERVER_ENTRY,
|
|
111
|
+
output: { entryFileNames: "server.mjs" }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
resolveId(id) {
|
|
119
|
+
if (id === VIRTUAL_SERVER_ENTRY) {
|
|
120
|
+
return RESOLVED_VIRTUAL_SERVER_ENTRY;
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
load(id) {
|
|
124
|
+
if (id === RESOLVED_VIRTUAL_SERVER_ENTRY) {
|
|
125
|
+
return createColyseusViteServerEntry(options);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "colyseus:dev-server",
|
|
131
|
+
configureServer(server) {
|
|
132
|
+
viteServer = server;
|
|
133
|
+
server.middlewares.use(createNodeMatchmakingMiddleware());
|
|
134
|
+
server.middlewares.use((req, res, next) => {
|
|
135
|
+
if (!currentAppHandler) {
|
|
136
|
+
return next();
|
|
137
|
+
}
|
|
138
|
+
currentAppHandler(req, res, next);
|
|
139
|
+
});
|
|
140
|
+
return async () => {
|
|
141
|
+
if (!server.httpServer) {
|
|
142
|
+
throw new Error("[colyseus] Vite HTTP server not available.");
|
|
143
|
+
}
|
|
144
|
+
await loadServerModule();
|
|
145
|
+
console.log("[colyseus] Server ready on Vite's HTTP server");
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "colyseus:hmr",
|
|
151
|
+
hotUpdate({ file, modules }) {
|
|
152
|
+
if (this.environment?.name === "colyseus" && modules.length > 0) {
|
|
153
|
+
loadServerModule().then(() => {
|
|
154
|
+
if (!options.quiet) {
|
|
155
|
+
console.log(`[colyseus] Server code reloaded (${file})`);
|
|
156
|
+
}
|
|
157
|
+
}).catch((e) => {
|
|
158
|
+
console.error("[colyseus] Failed to reload server module:", e);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
];
|
|
164
|
+
async function loadServerModule() {
|
|
165
|
+
const env = viteServer.environments.colyseus;
|
|
166
|
+
if (!env) {
|
|
167
|
+
console.error("[colyseus] Environment not found");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
if (isStarted && env.runner.evaluatedModules) {
|
|
172
|
+
env.runner.evaluatedModules.clear();
|
|
173
|
+
}
|
|
174
|
+
if (!isStarted) {
|
|
175
|
+
setDevMode(true);
|
|
176
|
+
await matchMaker.setup();
|
|
177
|
+
const wsModule = await (options.loadWsTransport ? options.loadWsTransport() : dynamicImport("@colyseus/ws-transport"));
|
|
178
|
+
const transport = new wsModule.WebSocketTransport({ noServer: true });
|
|
179
|
+
if (typeof transport.attachToServer !== "function") {
|
|
180
|
+
throw new Error("[colyseus] Vite dev mode requires a transport with attachToServer().");
|
|
181
|
+
}
|
|
182
|
+
transport.attachToServer(viteServer.httpServer, {
|
|
183
|
+
filter(req) {
|
|
184
|
+
return /^\/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+\/?$/.test(
|
|
185
|
+
new URL(req.url || "", "http://localhost").pathname
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
setTransport(transport);
|
|
190
|
+
}
|
|
191
|
+
const mod = await env.runner.import(options.serverEntry);
|
|
192
|
+
const config = getServerExport(mod);
|
|
193
|
+
const rooms = getRoomsExport(mod) || config?.["~rooms"];
|
|
194
|
+
const router = config?.router;
|
|
195
|
+
if (!expressApp && config?.options?.express) {
|
|
196
|
+
try {
|
|
197
|
+
const express = (await dynamicImport("express")).default;
|
|
198
|
+
expressApp = express();
|
|
199
|
+
config.options.express(expressApp);
|
|
200
|
+
} catch (e) {
|
|
201
|
+
console.warn("[colyseus] Express not available. Install express to use the express option.");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (router || expressApp) {
|
|
205
|
+
const routerHandler = router ? toNodeHandler(router.handler) : null;
|
|
206
|
+
currentAppHandler = (req, res, next) => {
|
|
207
|
+
if (router?.findRoute(req.method, req.url?.split("?")[0]) !== void 0) {
|
|
208
|
+
routerHandler(req, res);
|
|
209
|
+
} else if (expressApp) {
|
|
210
|
+
expressApp(req, res, next);
|
|
211
|
+
} else {
|
|
212
|
+
next();
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
} else {
|
|
216
|
+
currentAppHandler = null;
|
|
217
|
+
}
|
|
218
|
+
unregisterRoomDefinitions(currentRoomNames);
|
|
219
|
+
if (rooms) {
|
|
220
|
+
currentRoomNames = registerRoomDefinitions(rooms);
|
|
221
|
+
} else {
|
|
222
|
+
currentRoomNames = [];
|
|
223
|
+
console.warn(
|
|
224
|
+
"[colyseus] Server entry should export `server = defineServer(...)` or `rooms`."
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
if (!isStarted) {
|
|
228
|
+
await matchMaker.accept();
|
|
229
|
+
isStarted = true;
|
|
230
|
+
} else {
|
|
231
|
+
await matchMaker.hotReload();
|
|
232
|
+
}
|
|
233
|
+
if (!options.quiet) {
|
|
234
|
+
for (const roomName of currentRoomNames) {
|
|
235
|
+
console.log(`[colyseus] Room defined: "${roomName}"`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} catch (e) {
|
|
239
|
+
console.error("[colyseus] Failed to load server module:", e);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
export {
|
|
244
|
+
colyseus,
|
|
245
|
+
createColyseusViteServerEntry,
|
|
246
|
+
reloadColyseusViteRooms
|
|
247
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "colyseus",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.9",
|
|
4
4
|
"description": "Multiplayer Framework for Node.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"input": "./src/index.ts",
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
"types": "./build/index.d.ts",
|
|
13
13
|
"import": "./build/index.mjs",
|
|
14
14
|
"require": "./build/index.cjs"
|
|
15
|
+
},
|
|
16
|
+
"./vite": {
|
|
17
|
+
"types": "./build/vite.d.ts",
|
|
18
|
+
"import": "./build/vite.mjs",
|
|
19
|
+
"require": "./build/vite.cjs"
|
|
15
20
|
}
|
|
16
21
|
},
|
|
17
22
|
"author": "Endel Dreyer",
|
|
@@ -37,36 +42,43 @@
|
|
|
37
42
|
"node": ">= 20.x"
|
|
38
43
|
},
|
|
39
44
|
"dependencies": {
|
|
40
|
-
"@colyseus/
|
|
41
|
-
"@colyseus/
|
|
42
|
-
"@colyseus/playground": "^0.17.
|
|
43
|
-
"@colyseus/
|
|
44
|
-
"@colyseus/
|
|
45
|
-
"@colyseus/
|
|
46
|
-
"@colyseus/
|
|
47
|
-
"@colyseus/
|
|
45
|
+
"@colyseus/monitor": "^0.17.8",
|
|
46
|
+
"@colyseus/core": "^0.17.41",
|
|
47
|
+
"@colyseus/playground": "^0.17.12",
|
|
48
|
+
"@colyseus/auth": "^0.17.9",
|
|
49
|
+
"@colyseus/redis-driver": "^0.17.7",
|
|
50
|
+
"@colyseus/redis-presence": "^0.17.7",
|
|
51
|
+
"@colyseus/ws-transport": "^0.17.13",
|
|
52
|
+
"@colyseus/tools": "^0.17.19"
|
|
48
53
|
},
|
|
49
54
|
"devDependencies": {
|
|
50
55
|
"@colyseus/msgpackr": "*",
|
|
51
|
-
"@colyseus/schema": "^4.0.
|
|
56
|
+
"@colyseus/schema": "^4.0.7",
|
|
52
57
|
"@types/ws": "^8.5.14",
|
|
58
|
+
"ioredis": "^5.9.3",
|
|
53
59
|
"ws": "^8.18.0",
|
|
54
60
|
"zod": "^4.1.12",
|
|
55
|
-
"@colyseus/
|
|
56
|
-
"@colyseus/
|
|
57
|
-
"@colyseus/
|
|
58
|
-
"@colyseus/tools": "^0.17.
|
|
59
|
-
"@colyseus/
|
|
60
|
-
"@colyseus/
|
|
61
|
+
"@colyseus/better-call": "^1.3.1",
|
|
62
|
+
"@colyseus/drizzle-driver": "^0.17.7",
|
|
63
|
+
"@colyseus/sdk": "^0.17.39",
|
|
64
|
+
"@colyseus/tools": "^0.17.19",
|
|
65
|
+
"@colyseus/core": "^0.17.41",
|
|
66
|
+
"@colyseus/uwebsockets-transport": "^0.17.20"
|
|
61
67
|
},
|
|
62
68
|
"peerDependencies": {
|
|
63
69
|
"@colyseus/auth": "0.17.x",
|
|
64
70
|
"@colyseus/core": "0.17.x",
|
|
65
71
|
"@colyseus/redis-driver": "0.17.x",
|
|
66
72
|
"@colyseus/redis-presence": "0.17.x",
|
|
67
|
-
"@colyseus/schema": "^4.0.
|
|
73
|
+
"@colyseus/schema": "^4.0.7",
|
|
68
74
|
"@colyseus/uwebsockets-transport": "0.17.x",
|
|
69
|
-
"@colyseus/ws-transport": "0.17.x"
|
|
75
|
+
"@colyseus/ws-transport": "0.17.x",
|
|
76
|
+
"vite": ">=6.0.0"
|
|
77
|
+
},
|
|
78
|
+
"peerDependenciesMeta": {
|
|
79
|
+
"vite": {
|
|
80
|
+
"optional": true
|
|
81
|
+
}
|
|
70
82
|
},
|
|
71
83
|
"gitHead": "25ba61e283429bb4fa02db0454f804ea218259eb",
|
|
72
84
|
"scripts": {
|
package/src/vite.ts
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colyseus Vite Plugin
|
|
3
|
+
*
|
|
4
|
+
* Integrates a Colyseus game server into Vite's dev server and build pipeline.
|
|
5
|
+
*
|
|
6
|
+
* ## Architecture
|
|
7
|
+
*
|
|
8
|
+
* Colyseus packages are externalized in the runner environment so they share
|
|
9
|
+
* the same module instances — and therefore the same matchMaker singleton —
|
|
10
|
+
* as the plugin process. This lets user code (monitor, playground, custom
|
|
11
|
+
* middleware) access the real matchMaker with actual room data.
|
|
12
|
+
*
|
|
13
|
+
* In dev mode, defineServer() returns a config-only object (no Server
|
|
14
|
+
* instance). The plugin manages the matchMaker lifecycle, transport, and
|
|
15
|
+
* HMR directly.
|
|
16
|
+
*
|
|
17
|
+
* On HMR:
|
|
18
|
+
* 1. Re-import user module (defineServer returns fresh config)
|
|
19
|
+
* 2. Swap router handler + re-register room definitions
|
|
20
|
+
* 3. matchMaker.hotReload() — cache rooms, dispose, restore
|
|
21
|
+
*/
|
|
22
|
+
import * as matchMaker from '@colyseus/core/MatchMaker';
|
|
23
|
+
import {
|
|
24
|
+
setDevMode,
|
|
25
|
+
createNodeMatchmakingMiddleware,
|
|
26
|
+
dynamicImport,
|
|
27
|
+
registerRoomDefinitions,
|
|
28
|
+
unregisterRoomDefinitions,
|
|
29
|
+
toNodeHandler,
|
|
30
|
+
type RoomDefinitions,
|
|
31
|
+
type ServerOptions,
|
|
32
|
+
type Transport,
|
|
33
|
+
type Router,
|
|
34
|
+
} from '@colyseus/core';
|
|
35
|
+
import { setTransport } from '@colyseus/core/Transport';
|
|
36
|
+
import type { Plugin } from 'vite';
|
|
37
|
+
|
|
38
|
+
// ─── Virtual module IDs ───────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
const VIRTUAL_SERVER_ENTRY = 'virtual:colyseus-server-entry';
|
|
41
|
+
const RESOLVED_VIRTUAL_SERVER_ENTRY = '\0' + VIRTUAL_SERVER_ENTRY;
|
|
42
|
+
|
|
43
|
+
// ─── Options ──────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
export interface ColyseusViteOptions {
|
|
46
|
+
serverEntry: string;
|
|
47
|
+
port?: number;
|
|
48
|
+
quiet?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Serve the built client files via express.static() in the production
|
|
51
|
+
* server entry. Adds a SPA fallback that serves index.html for
|
|
52
|
+
* unmatched GET requests.
|
|
53
|
+
*
|
|
54
|
+
* Has no effect in dev mode (Vite serves the frontend).
|
|
55
|
+
*/
|
|
56
|
+
serveClient?: boolean;
|
|
57
|
+
loadWsTransport?: () => Promise<{
|
|
58
|
+
WebSocketTransport: new (options?: any) => Transport & {
|
|
59
|
+
attachToServer(server: any, options?: { filter?: (req: any) => boolean }): any;
|
|
60
|
+
};
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Internal types ───────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
type ServerConfig = {
|
|
67
|
+
options?: ServerOptions;
|
|
68
|
+
router?: Router;
|
|
69
|
+
'~rooms'?: RoomDefinitions;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type ServerModule = {
|
|
73
|
+
server?: ServerConfig;
|
|
74
|
+
rooms?: RoomDefinitions;
|
|
75
|
+
default?: {
|
|
76
|
+
server?: ServerConfig;
|
|
77
|
+
rooms?: RoomDefinitions;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
function getServerExport(mod: ServerModule): ServerConfig | undefined {
|
|
84
|
+
return mod.server || mod.default?.server;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getRoomsExport(mod: ServerModule): RoomDefinitions | undefined {
|
|
88
|
+
return mod.rooms || mod.default?.rooms;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── Virtual module generators ────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Production build entry — standalone server that imports the user's
|
|
95
|
+
* server entry and calls `server.listen()`.
|
|
96
|
+
*/
|
|
97
|
+
export function createColyseusViteServerEntry(options: ColyseusViteOptions) {
|
|
98
|
+
const port = options.port ?? 2567;
|
|
99
|
+
|
|
100
|
+
const lines: string[] = [
|
|
101
|
+
`import { Server, registerRoomDefinitions } from "colyseus";`,
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
if (options.serveClient) {
|
|
105
|
+
lines.push(
|
|
106
|
+
`import express from "express";`,
|
|
107
|
+
`import { fileURLToPath } from "url";`,
|
|
108
|
+
`import { dirname, join } from "path";`,
|
|
109
|
+
``,
|
|
110
|
+
`const __dirname = dirname(fileURLToPath(import.meta.url));`,
|
|
111
|
+
`const clientDir = join(__dirname, "../client");`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
lines.push(
|
|
116
|
+
``,
|
|
117
|
+
`const entry = await import(${JSON.stringify(options.serverEntry)});`,
|
|
118
|
+
`const server = entry.server ?? entry.default?.server;`,
|
|
119
|
+
`const rooms = entry.rooms ?? entry.default?.rooms;`,
|
|
120
|
+
``,
|
|
121
|
+
`if (server) {`,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (options.serveClient) {
|
|
125
|
+
lines.push(
|
|
126
|
+
` await server["_onTransportReady"];`,
|
|
127
|
+
` if (server.transport.getExpressApp) {`,
|
|
128
|
+
` const app = server.transport.getExpressApp();`,
|
|
129
|
+
` app.use(express.static(clientDir));`,
|
|
130
|
+
` app.get("*all", (req, res) => res.sendFile(join(clientDir, "index.html")));`,
|
|
131
|
+
` }`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
lines.push(
|
|
136
|
+
` server.listen(${port});`,
|
|
137
|
+
`} else if (rooms) {`,
|
|
138
|
+
` const gameServer = new Server();`,
|
|
139
|
+
` registerRoomDefinitions(rooms);`,
|
|
140
|
+
` gameServer.listen(${port});`,
|
|
141
|
+
`} else {`,
|
|
142
|
+
` throw new Error('[colyseus] Server entry should export \`server = defineServer(...)\` or \`rooms\`.');`,
|
|
143
|
+
`}`,
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ─── Exported helpers (for testing) ───────────────────────────────────
|
|
150
|
+
|
|
151
|
+
export async function reloadColyseusViteRooms(
|
|
152
|
+
importModule: (specifier: string) => Promise<any>,
|
|
153
|
+
serverEntry: string,
|
|
154
|
+
currentRoomNames: string[] = [],
|
|
155
|
+
) {
|
|
156
|
+
const mod = await importModule(serverEntry);
|
|
157
|
+
|
|
158
|
+
unregisterRoomDefinitions(currentRoomNames);
|
|
159
|
+
|
|
160
|
+
const server = getServerExport(mod);
|
|
161
|
+
const rooms: RoomDefinitions | undefined = getRoomsExport(mod)
|
|
162
|
+
|| server?.['~rooms'];
|
|
163
|
+
|
|
164
|
+
if (!rooms) {
|
|
165
|
+
return {
|
|
166
|
+
roomNames: [],
|
|
167
|
+
hasRooms: false,
|
|
168
|
+
server,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
roomNames: registerRoomDefinitions(rooms),
|
|
174
|
+
hasRooms: true,
|
|
175
|
+
server,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ─── Plugin ───────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
export function colyseus(options: ColyseusViteOptions): Plugin[] {
|
|
182
|
+
let viteServer: any;
|
|
183
|
+
let currentRoomNames: string[] = [];
|
|
184
|
+
let currentAppHandler: ((req: any, res: any, next: any) => void) | null = null;
|
|
185
|
+
let expressApp: any = null;
|
|
186
|
+
let isStarted = false;
|
|
187
|
+
|
|
188
|
+
return [
|
|
189
|
+
{
|
|
190
|
+
name: 'colyseus:config',
|
|
191
|
+
config() {
|
|
192
|
+
return {
|
|
193
|
+
builder: {},
|
|
194
|
+
build: { outDir: 'dist/client' },
|
|
195
|
+
environments: {
|
|
196
|
+
colyseus: {
|
|
197
|
+
consumer: 'server' as const,
|
|
198
|
+
resolve: {
|
|
199
|
+
// Externalize all dependencies so they share the same module
|
|
200
|
+
// instances (and matchMaker singleton) with the plugin process.
|
|
201
|
+
// Without this, Vite re-evaluates workspace/linked packages in
|
|
202
|
+
// the runner, creating isolated singletons — breaking monitor, etc.
|
|
203
|
+
external: true,
|
|
204
|
+
},
|
|
205
|
+
build: {
|
|
206
|
+
outDir: 'dist/server',
|
|
207
|
+
ssr: true,
|
|
208
|
+
rollupOptions: {
|
|
209
|
+
input: VIRTUAL_SERVER_ENTRY,
|
|
210
|
+
output: { entryFileNames: 'server.mjs' },
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
},
|
|
217
|
+
resolveId(id: string) {
|
|
218
|
+
if (id === VIRTUAL_SERVER_ENTRY) { return RESOLVED_VIRTUAL_SERVER_ENTRY; }
|
|
219
|
+
},
|
|
220
|
+
load(id: string) {
|
|
221
|
+
if (id === RESOLVED_VIRTUAL_SERVER_ENTRY) {
|
|
222
|
+
return createColyseusViteServerEntry(options);
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
{
|
|
228
|
+
name: 'colyseus:dev-server',
|
|
229
|
+
configureServer(server: any) {
|
|
230
|
+
viteServer = server;
|
|
231
|
+
server.middlewares.use(createNodeMatchmakingMiddleware());
|
|
232
|
+
|
|
233
|
+
// Dynamic application middleware — handler is swapped on each HMR reload.
|
|
234
|
+
server.middlewares.use((req: any, res: any, next: any) => {
|
|
235
|
+
if (!currentAppHandler) { return next(); }
|
|
236
|
+
currentAppHandler(req, res, next);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return async () => {
|
|
240
|
+
if (!server.httpServer) {
|
|
241
|
+
throw new Error('[colyseus] Vite HTTP server not available.');
|
|
242
|
+
}
|
|
243
|
+
await loadServerModule();
|
|
244
|
+
console.log("[colyseus] Server ready on Vite's HTTP server");
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
{
|
|
250
|
+
name: 'colyseus:hmr',
|
|
251
|
+
hotUpdate({ file, modules }) {
|
|
252
|
+
if (this.environment?.name === 'colyseus' && modules.length > 0) {
|
|
253
|
+
loadServerModule().then(() => {
|
|
254
|
+
if (!options.quiet) {
|
|
255
|
+
console.log(`[colyseus] Server code reloaded (${file})`);
|
|
256
|
+
}
|
|
257
|
+
}).catch((e) => {
|
|
258
|
+
console.error('[colyseus] Failed to reload server module:', e);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Import (or re-import) the user's server entry and configure the
|
|
267
|
+
* matchMaker, transport, rooms, and middleware.
|
|
268
|
+
*
|
|
269
|
+
* On initial load: sets up matchMaker, creates transport, registers rooms.
|
|
270
|
+
* On HMR reload: re-imports user code, swaps rooms/router, hot-reloads
|
|
271
|
+
* running rooms (cache → dispose → restore).
|
|
272
|
+
*/
|
|
273
|
+
async function loadServerModule() {
|
|
274
|
+
const env = viteServer.environments.colyseus;
|
|
275
|
+
if (!env) {
|
|
276
|
+
console.error('[colyseus] Environment not found');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
// Clear the runner's evaluated module cache so re-import picks up
|
|
282
|
+
// fresh user code. External packages (@colyseus/*) are cached by
|
|
283
|
+
// Node's module system — they keep their singleton state.
|
|
284
|
+
if (isStarted && env.runner.evaluatedModules) {
|
|
285
|
+
env.runner.evaluatedModules.clear();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ── Step 1: Set up matchMaker + transport (initial load only) ──
|
|
289
|
+
if (!isStarted) {
|
|
290
|
+
setDevMode(true);
|
|
291
|
+
await matchMaker.setup();
|
|
292
|
+
|
|
293
|
+
const wsModule = await (options.loadWsTransport
|
|
294
|
+
? options.loadWsTransport()
|
|
295
|
+
: dynamicImport<typeof import('@colyseus/ws-transport')>('@colyseus/ws-transport'));
|
|
296
|
+
|
|
297
|
+
const transport = new wsModule.WebSocketTransport({ noServer: true });
|
|
298
|
+
|
|
299
|
+
if (typeof (transport as any).attachToServer !== 'function') {
|
|
300
|
+
throw new Error('[colyseus] Vite dev mode requires a transport with attachToServer().');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
(transport as any).attachToServer(viteServer.httpServer, {
|
|
304
|
+
filter(req: any) {
|
|
305
|
+
return /^\/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+\/?$/.test(
|
|
306
|
+
new URL(req.url || '', 'http://localhost').pathname,
|
|
307
|
+
);
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
setTransport(transport);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ── Step 2: Import user module ──
|
|
314
|
+
// In dev mode, defineServer() returns a config object (no Server
|
|
315
|
+
// instance, no matchMaker.setup() call) because isDevMode is true.
|
|
316
|
+
const mod = await env.runner.import(options.serverEntry);
|
|
317
|
+
|
|
318
|
+
const config = getServerExport(mod);
|
|
319
|
+
const rooms: RoomDefinitions | undefined = getRoomsExport(mod)
|
|
320
|
+
|| config?.['~rooms'];
|
|
321
|
+
|
|
322
|
+
// ── Step 3: Build application middleware (router + express) ──
|
|
323
|
+
const router = config?.router;
|
|
324
|
+
|
|
325
|
+
// Set up express once — persistent across HMR reloads.
|
|
326
|
+
if (!expressApp && config?.options?.express) {
|
|
327
|
+
try {
|
|
328
|
+
const express = (await dynamicImport<any>('express')).default;
|
|
329
|
+
expressApp = express();
|
|
330
|
+
config.options.express(expressApp);
|
|
331
|
+
} catch (e) {
|
|
332
|
+
console.warn('[colyseus] Express not available. Install express to use the express option.');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Build combined handler: router (hot-swappable) + express (persistent).
|
|
337
|
+
if (router || expressApp) {
|
|
338
|
+
const routerHandler = router ? toNodeHandler(router.handler) : null;
|
|
339
|
+
currentAppHandler = (req: any, res: any, next: any) => {
|
|
340
|
+
if (router?.findRoute(req.method, req.url?.split('?')[0]) !== undefined) {
|
|
341
|
+
routerHandler!(req, res);
|
|
342
|
+
} else if (expressApp) {
|
|
343
|
+
expressApp(req, res, next);
|
|
344
|
+
} else {
|
|
345
|
+
next();
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
} else {
|
|
349
|
+
currentAppHandler = null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ── Step 4: Register room definitions ──
|
|
353
|
+
// Must happen BEFORE hotReload() because reloadFromCache() needs
|
|
354
|
+
// the new handlers to recreate room instances.
|
|
355
|
+
unregisterRoomDefinitions(currentRoomNames);
|
|
356
|
+
if (rooms) {
|
|
357
|
+
currentRoomNames = registerRoomDefinitions(rooms);
|
|
358
|
+
} else {
|
|
359
|
+
currentRoomNames = [];
|
|
360
|
+
console.warn(
|
|
361
|
+
'[colyseus] Server entry should export `server = defineServer(...)` or `rooms`.',
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ── Step 5: Accept connections or hot-reload rooms ──
|
|
366
|
+
if (!isStarted) {
|
|
367
|
+
await matchMaker.accept();
|
|
368
|
+
isStarted = true;
|
|
369
|
+
} else {
|
|
370
|
+
await matchMaker.hotReload();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!options.quiet) {
|
|
374
|
+
for (const roomName of currentRoomNames) {
|
|
375
|
+
console.log(`[colyseus] Room defined: "${roomName}"`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
} catch (e) {
|
|
380
|
+
console.error('[colyseus] Failed to load server module:', e);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|