@rpgjs/vite 5.0.0-beta.1 → 5.0.0-beta.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/CHANGELOG.md +29 -0
- package/LICENSE +19 -0
- package/dist/compatibility-v4/flag-transform.d.ts +6 -0
- package/dist/compatibility-v4/index.d.ts +22 -0
- package/dist/compatibility-v4/load-config-file.d.ts +2 -0
- package/dist/compatibility-v4/require-transform.d.ts +2 -0
- package/dist/compatibility-v4/utils.d.ts +55 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7169 -8121
- package/dist/index.js.map +1 -1
- package/dist/rpgjs-plugin.d.ts +1 -0
- package/package.json +19 -11
- package/src/compatibility-v4/flag-transform.ts +61 -0
- package/src/compatibility-v4/index.ts +713 -0
- package/src/compatibility-v4/load-config-file.ts +38 -0
- package/src/compatibility-v4/require-transform.ts +67 -0
- package/src/compatibility-v4/utils.ts +170 -0
- package/src/index.ts +2 -1
- package/tests/compatibility-v4.spec.ts +105 -0
- package/tests/fixtures/v4-game/rpg.toml +11 -0
- package/tests/fixtures/v4-game/src/modules/main/characters/assets/hero.svg +2 -0
- package/tests/fixtures/v4-game/src/modules/main/characters/hero.ts +2 -0
- package/tests/fixtures/v4-game/src/modules/main/client.ts +4 -0
- package/tests/fixtures/v4-game/src/modules/main/database/potion.ts +6 -0
- package/tests/fixtures/v4-game/src/modules/main/events/npc.ts +4 -0
- package/tests/fixtures/v4-game/src/modules/main/gui/menu.vue +2 -0
- package/tests/fixtures/v4-game/src/modules/main/maps/map.tmx +2 -0
- package/tests/fixtures/v4-game/src/modules/main/maps/map.ts +7 -0
- package/tests/fixtures/v4-game/src/modules/main/player.ts +4 -0
- package/tests/fixtures/v4-game/src/modules/main/scene-map.ts +4 -0
- package/tests/fixtures/v4-game/src/modules/main/server.ts +4 -0
- package/tests/fixtures/v4-game/src/modules/main/sounds/theme.ogg +2 -0
- package/tests/fixtures/v4-game/src/modules/main/sounds/theme.ts +5 -0
- package/tests/fixtures/v4-game/src/modules/main/sprite.ts +4 -0
- package/tests/fixtures/v4-game/src/modules/main/worlds/maps/world-map.tmx +6 -0
- package/tests/fixtures/v4-game/src/modules/main/worlds/world.world +13 -0
- package/tests/remove-imports-plugin.spec.ts +56 -0
- package/vite.config.ts +7 -1
- package/dist/index-Bc9qRSkO.js +0 -5496
- package/dist/index-Bc9qRSkO.js.map +0 -1
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import type { Plugin, ViteDevServer } from "vite";
|
|
4
|
+
import sizeOf from "image-size";
|
|
5
|
+
import { createRpgServerTransport, logNetworkSimulationStatus } from "@rpgjs/server/node";
|
|
6
|
+
import type { RpgWebSocketServer } from "@rpgjs/server/node";
|
|
7
|
+
import { flagTransform } from "./flag-transform";
|
|
8
|
+
import vitePluginRequire from "./require-transform";
|
|
9
|
+
import { loadConfigFileSync } from "./load-config-file";
|
|
10
|
+
import {
|
|
11
|
+
ClientBuildConfigOptions,
|
|
12
|
+
Config,
|
|
13
|
+
dedent,
|
|
14
|
+
formatVariableName,
|
|
15
|
+
getAllFiles,
|
|
16
|
+
ImportObject,
|
|
17
|
+
importPathForFile,
|
|
18
|
+
importString,
|
|
19
|
+
resolveModuleImport,
|
|
20
|
+
searchFolderAndTransformToImportString,
|
|
21
|
+
toPosix,
|
|
22
|
+
transformPathIfModule,
|
|
23
|
+
warn,
|
|
24
|
+
} from "./utils";
|
|
25
|
+
|
|
26
|
+
const MODULE_NAME = "virtual:rpgjs-v4-modules";
|
|
27
|
+
const CLIENT_CONFIG = "virtual:rpgjs-v4-client-config";
|
|
28
|
+
const SERVER_ENTRY = "virtual:rpgjs-v4-server-entry";
|
|
29
|
+
const CLIENT_ENTRY = "virtual:rpgjs-v4-client-entry";
|
|
30
|
+
const STANDALONE_ENTRY = "virtual:rpgjs-v4-standalone-entry";
|
|
31
|
+
const LEGACY_MOBILE_GUI = "virtual:rpgjs-v4-legacy-mobile-gui";
|
|
32
|
+
const LEGACY_DEFAULT_GUI = "virtual:rpgjs-v4-legacy-default-gui";
|
|
33
|
+
const LEGACY_GAMEPAD = "virtual:rpgjs-v4-legacy-gamepad";
|
|
34
|
+
const LEGACY_MODULES: Record<string, string> = {
|
|
35
|
+
"@rpgjs/mobile-gui": LEGACY_MOBILE_GUI,
|
|
36
|
+
"@rpgjs/default-gui": LEGACY_DEFAULT_GUI,
|
|
37
|
+
"@rpgjs/gamepad": LEGACY_GAMEPAD,
|
|
38
|
+
};
|
|
39
|
+
const TILED_EXTENSIONS = [".tmx", ".tsx", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"];
|
|
40
|
+
|
|
41
|
+
type ImportImageObject = ImportObject & { propImagesString: string };
|
|
42
|
+
type TiledAssetRoot = {
|
|
43
|
+
root: string;
|
|
44
|
+
moduleRoot: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
async function importWebSocketServer(): Promise<any> {
|
|
48
|
+
if (typeof process === "undefined" || !process.versions?.node) return null;
|
|
49
|
+
try {
|
|
50
|
+
const { createRequire } = await import("module");
|
|
51
|
+
const require = createRequire(import.meta.url);
|
|
52
|
+
const ws = require("ws");
|
|
53
|
+
return ws.WebSocketServer || ws.default?.WebSocketServer || ws;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function verifyDefaultExport(importObject: ImportObject) {
|
|
60
|
+
if (!importObject.variablesString) return "[]";
|
|
61
|
+
return dedent`
|
|
62
|
+
[${importObject.variablesString}].map((value) => {
|
|
63
|
+
if (!value) throw new Error('Missing default export in ${importObject.relativePath}')
|
|
64
|
+
return value
|
|
65
|
+
})
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeDatabase(variableList: string) {
|
|
70
|
+
if (!variableList) return "{}";
|
|
71
|
+
return dedent`
|
|
72
|
+
Object.assign({}, ...[${variableList}].map((value) => {
|
|
73
|
+
if (!value) return {}
|
|
74
|
+
if (typeof value === 'function') {
|
|
75
|
+
return { [value.id || value.name]: value }
|
|
76
|
+
}
|
|
77
|
+
return value
|
|
78
|
+
}))
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function stripQuery(url: string) {
|
|
83
|
+
return url.split("?", 1)[0].split("#", 1)[0];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizePublicBasePath(basePath = "map") {
|
|
87
|
+
const trimmed = basePath.trim().replace(/^\/+|\/+$/g, "");
|
|
88
|
+
return trimmed || "map";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getMimeType(file: string) {
|
|
92
|
+
switch (path.extname(file).toLowerCase()) {
|
|
93
|
+
case ".tmx":
|
|
94
|
+
case ".tsx":
|
|
95
|
+
case ".world":
|
|
96
|
+
return "application/xml";
|
|
97
|
+
case ".png":
|
|
98
|
+
return "image/png";
|
|
99
|
+
case ".jpg":
|
|
100
|
+
case ".jpeg":
|
|
101
|
+
return "image/jpeg";
|
|
102
|
+
case ".gif":
|
|
103
|
+
return "image/gif";
|
|
104
|
+
case ".webp":
|
|
105
|
+
return "image/webp";
|
|
106
|
+
case ".svg":
|
|
107
|
+
return "image/svg+xml";
|
|
108
|
+
default:
|
|
109
|
+
return "application/octet-stream";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isTiledAsset(file: string) {
|
|
114
|
+
return TILED_EXTENSIONS.includes(path.extname(file).toLowerCase());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function moduleRootPath(modulePath: string, projectRoot = process.cwd()) {
|
|
118
|
+
return path.resolve(projectRoot, transformPathIfModule(modulePath));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function hasTiledAssets(directory: string) {
|
|
122
|
+
return fs.existsSync(directory) && getAllFiles(directory).some(isTiledAsset);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function getTiledAssetRoots(modulePath: string, projectRoot = process.cwd()): TiledAssetRoot[] {
|
|
126
|
+
const moduleRoot = moduleRootPath(modulePath, projectRoot);
|
|
127
|
+
const roots: TiledAssetRoot[] = [];
|
|
128
|
+
const mapsRoot = path.join(moduleRoot, "maps");
|
|
129
|
+
const worldsRoot = path.join(moduleRoot, "worlds");
|
|
130
|
+
|
|
131
|
+
if (hasTiledAssets(mapsRoot)) {
|
|
132
|
+
roots.push({ root: mapsRoot, moduleRoot });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (fs.existsSync(worldsRoot)) {
|
|
136
|
+
for (const dirent of fs.readdirSync(worldsRoot, { withFileTypes: true })) {
|
|
137
|
+
if (!dirent.isDirectory()) continue;
|
|
138
|
+
const root = path.join(worldsRoot, dirent.name);
|
|
139
|
+
if (hasTiledAssets(root)) {
|
|
140
|
+
roots.push({ root, moduleRoot });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return roots;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getAllTiledAssetRoots(modules: string[], projectRoot = process.cwd()) {
|
|
149
|
+
return modules
|
|
150
|
+
.filter((module) => module.startsWith("."))
|
|
151
|
+
.flatMap((module) => getTiledAssetRoots(module, projectRoot));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function mapIdFromTmx(file: string, assetRoot: string) {
|
|
155
|
+
return toPosix(path.relative(assetRoot, file)).replace(/\.tmx$/i, "");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function publicTiledFilePath(file: string, assetRoot: string, basePath = "map") {
|
|
159
|
+
return `/${normalizePublicBasePath(basePath)}/${toPosix(path.relative(assetRoot, file))}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function hasSiblingMapScript(file: string) {
|
|
163
|
+
return fs.existsSync(file.replace(/\.tmx$/i, ".ts"));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function createTiledMapEntries(modulePath: string, options: ClientBuildConfigOptions, projectRoot = process.cwd()) {
|
|
167
|
+
return getTiledAssetRoots(modulePath, projectRoot)
|
|
168
|
+
.flatMap(({ root }) => getAllFiles(root)
|
|
169
|
+
.filter((file) => file.toLowerCase().endsWith(".tmx"))
|
|
170
|
+
.filter((file) => !hasSiblingMapScript(file))
|
|
171
|
+
.map((file) => {
|
|
172
|
+
const id = mapIdFromTmx(file, root);
|
|
173
|
+
const publicPath = publicTiledFilePath(file, root, options.tiledMapBasePath);
|
|
174
|
+
return `{ id: '${id}', file: '${publicPath}' }`;
|
|
175
|
+
}))
|
|
176
|
+
.join(",");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function mapIdFromWorldFileName(fileName: string) {
|
|
180
|
+
const withoutExtension = toPosix(fileName).replace(/\.tmx$/i, "");
|
|
181
|
+
const parts = withoutExtension.split("/").filter(Boolean);
|
|
182
|
+
return parts.at(-1) ?? withoutExtension;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function loadWorldFile(file: string) {
|
|
186
|
+
const world = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
187
|
+
const maps = Array.isArray(world.maps)
|
|
188
|
+
? world.maps.map((map: any) => ({
|
|
189
|
+
...map,
|
|
190
|
+
id: map.id ?? (map.fileName ? mapIdFromWorldFileName(map.fileName) : undefined),
|
|
191
|
+
worldX: map.worldX ?? map.x ?? 0,
|
|
192
|
+
worldY: map.worldY ?? map.y ?? 0,
|
|
193
|
+
width: map.width ?? map.widthPx ?? 0,
|
|
194
|
+
height: map.height ?? map.heightPx ?? 0,
|
|
195
|
+
}))
|
|
196
|
+
: [];
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
...world,
|
|
200
|
+
id: world.id ?? path.basename(file, ".world"),
|
|
201
|
+
maps,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function createWorldMapEntries(modulePath: string, projectRoot = process.cwd()) {
|
|
206
|
+
const worldsRoot = path.join(moduleRootPath(modulePath, projectRoot), "worlds");
|
|
207
|
+
if (!fs.existsSync(worldsRoot)) return "";
|
|
208
|
+
|
|
209
|
+
return getAllFiles(worldsRoot)
|
|
210
|
+
.filter((file) => file.endsWith(".world"))
|
|
211
|
+
.map((file) => JSON.stringify(loadWorldFile(file)))
|
|
212
|
+
.join(",");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function copyTiledAssets(assetRoots: TiledAssetRoot[], outputDir: string, basePath = "map") {
|
|
216
|
+
const publicBasePath = normalizePublicBasePath(basePath);
|
|
217
|
+
const copied = new Set<string>();
|
|
218
|
+
|
|
219
|
+
for (const { root } of assetRoots) {
|
|
220
|
+
for (const file of getAllFiles(root).filter(isTiledAsset)) {
|
|
221
|
+
const relativePath = toPosix(path.relative(root, file));
|
|
222
|
+
const target = path.join(outputDir, publicBasePath, relativePath);
|
|
223
|
+
if (copied.has(target)) {
|
|
224
|
+
warn(`Tiled asset collision while copying ${relativePath}`);
|
|
225
|
+
}
|
|
226
|
+
copied.add(target);
|
|
227
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
228
|
+
fs.copyFileSync(file, target);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function serveTiledAssets(server: ViteDevServer, assetRoots: TiledAssetRoot[], basePath = "map") {
|
|
234
|
+
const publicBasePath = `/${normalizePublicBasePath(basePath)}`;
|
|
235
|
+
server.middlewares.use((req, res, next) => {
|
|
236
|
+
if (!req.url?.startsWith(publicBasePath)) return next();
|
|
237
|
+
|
|
238
|
+
const requestPath = decodeURIComponent(stripQuery(req.url).slice(publicBasePath.length).replace(/^\/+/, ""));
|
|
239
|
+
for (const { root } of assetRoots) {
|
|
240
|
+
const file = path.resolve(root, requestPath);
|
|
241
|
+
if (!file.startsWith(root + path.sep) && file !== root) continue;
|
|
242
|
+
if (!fs.existsSync(file) || !fs.statSync(file).isFile() || !isTiledAsset(file)) continue;
|
|
243
|
+
|
|
244
|
+
res.setHeader("Content-Type", getMimeType(file));
|
|
245
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
246
|
+
res.end(fs.readFileSync(file));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
res.statusCode = 404;
|
|
251
|
+
res.end("Not Found");
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function loadServerModuleFiles(modulePath: string, options: ClientBuildConfigOptions & { modulesCreated?: string[] }, config: Config, projectRoot = process.cwd()) {
|
|
256
|
+
const modulesCreated = options.modulesCreated ?? [];
|
|
257
|
+
if (!modulesCreated.includes(modulePath)) modulesCreated.push(modulePath);
|
|
258
|
+
|
|
259
|
+
const importPlayer = importString(modulePath, "player", "player", projectRoot);
|
|
260
|
+
const importEngine = importString(modulePath, "server", "server", projectRoot);
|
|
261
|
+
const mapStandaloneFilesString = searchFolderAndTransformToImportString("maps", modulePath, ".ts", undefined, undefined, projectRoot);
|
|
262
|
+
const mapFilesString = createTiledMapEntries(modulePath, options, projectRoot);
|
|
263
|
+
const worldFilesString = createWorldMapEntries(modulePath, projectRoot);
|
|
264
|
+
const eventsFilesString = searchFolderAndTransformToImportString("events", modulePath, ".ts", undefined, undefined, projectRoot);
|
|
265
|
+
const databaseFilesString = searchFolderAndTransformToImportString("database", modulePath, ".ts", undefined, undefined, projectRoot);
|
|
266
|
+
const hasMaps = !!mapFilesString && !!mapStandaloneFilesString.variablesString;
|
|
267
|
+
const hitbox = config.start?.hitbox;
|
|
268
|
+
|
|
269
|
+
const startHook = modulesCreated.length === 1 && (config.start?.graphic || hitbox || config.startMap)
|
|
270
|
+
? dedent`
|
|
271
|
+
const __rpgjsV4Player = player || {}
|
|
272
|
+
const __rpgjsV4LastConnected = __rpgjsV4Player.onConnected
|
|
273
|
+
__rpgjsV4Player.onConnected = async (player) => {
|
|
274
|
+
if (__rpgjsV4LastConnected) await __rpgjsV4LastConnected(player)
|
|
275
|
+
${config.start?.graphic ? `player.setGraphic('${config.start.graphic}')` : ""}
|
|
276
|
+
${hitbox ? `player.setHitbox(${hitbox[0]}, ${hitbox[1]})` : ""}
|
|
277
|
+
${config.startMap ? `await player.changeMap('${config.startMap}')` : ""}
|
|
278
|
+
}
|
|
279
|
+
`
|
|
280
|
+
: "";
|
|
281
|
+
|
|
282
|
+
return dedent`
|
|
283
|
+
${importPlayer || "const player = {}"}
|
|
284
|
+
${importEngine}
|
|
285
|
+
${mapStandaloneFilesString.importString}
|
|
286
|
+
${eventsFilesString.importString}
|
|
287
|
+
${databaseFilesString.importString}
|
|
288
|
+
|
|
289
|
+
${startHook}
|
|
290
|
+
|
|
291
|
+
export default {
|
|
292
|
+
player: ${startHook ? "__rpgjsV4Player" : "player"},
|
|
293
|
+
${importEngine ? "engine: server," : ""}
|
|
294
|
+
events: ${verifyDefaultExport(eventsFilesString)},
|
|
295
|
+
database: ${normalizeDatabase(databaseFilesString.variablesString)},
|
|
296
|
+
maps: [${mapFilesString}${hasMaps ? "," : ""}${mapStandaloneFilesString.variablesString}],
|
|
297
|
+
worldMaps: [${worldFilesString}]
|
|
298
|
+
}
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function loadServerFiles(modulePath: string, options: ClientBuildConfigOptions & { modulesCreated?: string[] }, config: Config, projectRoot = process.cwd()) {
|
|
303
|
+
const moduleCode = loadServerModuleFiles(modulePath, options, config, projectRoot);
|
|
304
|
+
|
|
305
|
+
return dedent`
|
|
306
|
+
import { createServer, provideServerModules } from '@rpgjs/server'
|
|
307
|
+
import { provideTiledMap } from '@rpgjs/tiledmap/server'
|
|
308
|
+
${moduleCode.replace("export default", "const module =")}
|
|
309
|
+
|
|
310
|
+
export default createServer({
|
|
311
|
+
providers: [
|
|
312
|
+
provideServerModules([module]),
|
|
313
|
+
provideTiledMap()
|
|
314
|
+
]
|
|
315
|
+
})
|
|
316
|
+
`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function createServerEntryLoad() {
|
|
320
|
+
return dedent`
|
|
321
|
+
import { createServer, provideServerModules } from '@rpgjs/server'
|
|
322
|
+
import { provideTiledMap } from '@rpgjs/tiledmap/server'
|
|
323
|
+
import modules from '${MODULE_NAME}'
|
|
324
|
+
|
|
325
|
+
export default createServer({
|
|
326
|
+
providers: [
|
|
327
|
+
provideServerModules(modules),
|
|
328
|
+
provideTiledMap()
|
|
329
|
+
]
|
|
330
|
+
})
|
|
331
|
+
`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function loadSpriteSheet(directoryName: string, modulePath: string, options: ClientBuildConfigOptions, projectRoot = process.cwd(), warning = false): ImportImageObject {
|
|
335
|
+
const importSprites = searchFolderAndTransformToImportString(directoryName, modulePath, ".ts", undefined, undefined, projectRoot);
|
|
336
|
+
let propImagesString = "";
|
|
337
|
+
let variablesString = importSprites.variablesString;
|
|
338
|
+
|
|
339
|
+
if (importSprites.importString) {
|
|
340
|
+
const images = getAllFiles(importSprites.folder).filter((file) => {
|
|
341
|
+
return [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".svg"].some((ext) => file.toLowerCase().endsWith(ext));
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
if (!images.length) {
|
|
345
|
+
warn(`No spritesheet image found in ${directoryName}`);
|
|
346
|
+
} else {
|
|
347
|
+
const imageImports = images
|
|
348
|
+
.map((file) => {
|
|
349
|
+
const filename = path.basename(file);
|
|
350
|
+
const basename = filename.replace(path.extname(filename), "");
|
|
351
|
+
const importPath = `${importPathForFile(file, projectRoot)}?url`;
|
|
352
|
+
const variableName = formatVariableName(importPath);
|
|
353
|
+
return { basename, importPath, variableName };
|
|
354
|
+
});
|
|
355
|
+
const objectString = imageImports
|
|
356
|
+
.map(({ basename, variableName }) => `"${basename}": ${variableName}`)
|
|
357
|
+
.join(",\n");
|
|
358
|
+
const imageImportString = imageImports
|
|
359
|
+
.map(({ importPath, variableName }) => `import ${variableName} from '${importPath}'`)
|
|
360
|
+
.join("\n");
|
|
361
|
+
const generatedSpritesheetsVariable = `__rpgjsV4Spritesheets_${formatVariableName(directoryName)}`;
|
|
362
|
+
variablesString = `...${generatedSpritesheetsVariable}`;
|
|
363
|
+
|
|
364
|
+
const dimensions = sizeOf(fs.readFileSync(images.at(-1)!)) as { width?: number; height?: number };
|
|
365
|
+
propImagesString = dedent`
|
|
366
|
+
${imageImportString}
|
|
367
|
+
const ${generatedSpritesheetsVariable} = [${importSprites.variablesString}].flatMap((spritesheet) => {
|
|
368
|
+
return Object.entries({ ${objectString} }).map(([id, image]) => ({
|
|
369
|
+
...spritesheet,
|
|
370
|
+
...(spritesheet.prototype ?? {}),
|
|
371
|
+
id,
|
|
372
|
+
image,
|
|
373
|
+
}))
|
|
374
|
+
})
|
|
375
|
+
;[${importSprites.variablesString}].forEach((spritesheet) => {
|
|
376
|
+
spritesheet.images = { ${objectString} }
|
|
377
|
+
spritesheet.prototype ||= {}
|
|
378
|
+
spritesheet.prototype.width = ${dimensions.width ?? 0}
|
|
379
|
+
spritesheet.prototype.height = ${dimensions.height ?? 0}
|
|
380
|
+
})
|
|
381
|
+
${generatedSpritesheetsVariable}.forEach((spritesheet) => {
|
|
382
|
+
spritesheet.width = ${dimensions.width ?? 0}
|
|
383
|
+
spritesheet.height = ${dimensions.height ?? 0}
|
|
384
|
+
})
|
|
385
|
+
`;
|
|
386
|
+
}
|
|
387
|
+
} else if (warning) {
|
|
388
|
+
warn(`No spritesheet folder found in ${directoryName}`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
...importSprites,
|
|
393
|
+
variablesString,
|
|
394
|
+
propImagesString,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function loadClientFiles(modulePath: string, options: ClientBuildConfigOptions, config: Config, projectRoot = process.cwd()) {
|
|
399
|
+
const importSpriteString = importString(modulePath, "sprite", "sprite", projectRoot);
|
|
400
|
+
const importSceneMapString = importString(modulePath, "scene-map", "sceneMap", projectRoot);
|
|
401
|
+
const importEngine = importString(modulePath, "client", "engine", projectRoot);
|
|
402
|
+
const guiFilesString = searchFolderAndTransformToImportString("gui", modulePath, [".vue", ".tsx", ".jsx", ".ce"], undefined, undefined, projectRoot);
|
|
403
|
+
const soundStandaloneFilesString = searchFolderAndTransformToImportString("sounds", modulePath, ".ts", undefined, undefined, projectRoot);
|
|
404
|
+
const soundFilesString = searchFolderAndTransformToImportString(
|
|
405
|
+
"sounds",
|
|
406
|
+
modulePath,
|
|
407
|
+
[".mp3", ".ogg"],
|
|
408
|
+
undefined,
|
|
409
|
+
{
|
|
410
|
+
customFilter: (file) => !fs.existsSync(file.replace(/\.(mp3|ogg)$/, ".ts")),
|
|
411
|
+
},
|
|
412
|
+
projectRoot,
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
const spritesheets: ImportImageObject[] = [];
|
|
416
|
+
for (const directory of config.spritesheetDirectories ?? []) {
|
|
417
|
+
spritesheets.push(loadSpriteSheet(directory, modulePath, options, projectRoot));
|
|
418
|
+
}
|
|
419
|
+
if (!(config.spritesheetDirectories ?? []).includes("characters")) {
|
|
420
|
+
spritesheets.push(loadSpriteSheet("characters", modulePath, options, projectRoot));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const spritesheetRoot = path.resolve(projectRoot, transformPathIfModule(modulePath), "spritesheets");
|
|
424
|
+
if (fs.existsSync(spritesheetRoot)) {
|
|
425
|
+
for (const dirent of fs.readdirSync(spritesheetRoot, { withFileTypes: true })) {
|
|
426
|
+
if (dirent.isDirectory()) {
|
|
427
|
+
spritesheets.push(loadSpriteSheet(path.join("spritesheets", dirent.name), modulePath, options, projectRoot, true));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const activeSpritesheets = spritesheets.filter((spritesheet) => spritesheet.importString);
|
|
433
|
+
const hasSounds = !!soundFilesString.variablesString && !!soundStandaloneFilesString.variablesString;
|
|
434
|
+
|
|
435
|
+
return dedent`
|
|
436
|
+
${importSpriteString || "const sprite = {}"}
|
|
437
|
+
${importSceneMapString}
|
|
438
|
+
${importEngine}
|
|
439
|
+
${activeSpritesheets.map((spritesheet) => spritesheet.importString).join("\n")}
|
|
440
|
+
${guiFilesString.importString}
|
|
441
|
+
${soundFilesString.importString}
|
|
442
|
+
${soundStandaloneFilesString.importString}
|
|
443
|
+
|
|
444
|
+
${activeSpritesheets.map((spritesheet) => spritesheet.propImagesString).join("\n")}
|
|
445
|
+
|
|
446
|
+
export default {
|
|
447
|
+
spritesheets: [${activeSpritesheets.map((spritesheet) => spritesheet.variablesString).join(",")}],
|
|
448
|
+
sprite,
|
|
449
|
+
${importEngine ? "engine," : ""}
|
|
450
|
+
sceneMap: ${importSceneMapString ? "sceneMap" : "{}"},
|
|
451
|
+
gui: [${guiFilesString.variablesString}],
|
|
452
|
+
sounds: [${soundFilesString.variablesString}${hasSounds ? "," : ""}${soundStandaloneFilesString.variablesString}]
|
|
453
|
+
}
|
|
454
|
+
`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function createModuleLoad(id: string, variableName: string, modulePath: string, options: ClientBuildConfigOptions & { modulesCreated?: string[] }, config: Config, projectRoot = process.cwd()) {
|
|
458
|
+
if (modulePath === LEGACY_MOBILE_GUI) {
|
|
459
|
+
return dedent`
|
|
460
|
+
import { withMobile } from '@rpgjs/client'
|
|
461
|
+
export default { client: withMobile(), server: {} }
|
|
462
|
+
`;
|
|
463
|
+
}
|
|
464
|
+
if (modulePath === LEGACY_DEFAULT_GUI || modulePath === LEGACY_GAMEPAD) {
|
|
465
|
+
return "export default { client: {}, server: {} }";
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const clientFile = `virtual:${variableName}-client`;
|
|
469
|
+
const serverFile = `virtual:${variableName}-server`;
|
|
470
|
+
|
|
471
|
+
if (id.startsWith(`${serverFile}?server`)) {
|
|
472
|
+
return loadServerModuleFiles(modulePath, options, config, projectRoot);
|
|
473
|
+
}
|
|
474
|
+
if (id.startsWith(`${clientFile}?client`)) {
|
|
475
|
+
return loadClientFiles(modulePath, options, config, projectRoot);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const modulePathId = path.resolve(projectRoot, transformPathIfModule(modulePath));
|
|
479
|
+
const packageJson = path.join(modulePathId, "package.json");
|
|
480
|
+
const indexFile = path.join(modulePathId, "index.ts");
|
|
481
|
+
|
|
482
|
+
if (fs.existsSync(packageJson)) {
|
|
483
|
+
const { main: entryPoint } = JSON.parse(fs.readFileSync(packageJson, "utf8"));
|
|
484
|
+
if (entryPoint) {
|
|
485
|
+
const entryFile = path.join(modulePathId, entryPoint);
|
|
486
|
+
return dedent`
|
|
487
|
+
import mod from '${modulePath.startsWith(".") ? importPathForFile(entryFile, projectRoot) : resolveModuleImport(toPosix(path.join(modulePath, entryPoint)))}'
|
|
488
|
+
export default mod
|
|
489
|
+
`;
|
|
490
|
+
}
|
|
491
|
+
} else if (fs.existsSync(indexFile)) {
|
|
492
|
+
return dedent`
|
|
493
|
+
import mod from '${importPathForFile(indexFile, projectRoot)}'
|
|
494
|
+
export default mod
|
|
495
|
+
`;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return dedent`
|
|
499
|
+
import client from 'client!${clientFile}'
|
|
500
|
+
import server from 'server!${serverFile}'
|
|
501
|
+
export default { client, server }
|
|
502
|
+
`;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export function createModulesLoad(modules: string[]) {
|
|
506
|
+
const modulesToImport = modules.reduce((acc: Record<string, string>, module) => {
|
|
507
|
+
const resolvedModule = LEGACY_MODULES[module] ?? module;
|
|
508
|
+
acc[formatVariableName(resolvedModule)] = resolvedModule;
|
|
509
|
+
return acc;
|
|
510
|
+
}, {});
|
|
511
|
+
|
|
512
|
+
return dedent`
|
|
513
|
+
${Object.entries(modulesToImport).map(([variableName, module]) => `import ${variableName} from '${resolveModuleImport(module)}'`).join("\n")}
|
|
514
|
+
export default [${Object.keys(modulesToImport).join(",")}]
|
|
515
|
+
`;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export function createClientConfigLoad(config: Config, options: Pick<ClientBuildConfigOptions, "tiledMapBasePath"> = {}) {
|
|
519
|
+
return dedent`
|
|
520
|
+
import { provideClientGlobalConfig, provideClientModules } from '@rpgjs/client'
|
|
521
|
+
import { provideTiledMap } from '@rpgjs/tiledmap/client'
|
|
522
|
+
import modules from '${MODULE_NAME}'
|
|
523
|
+
export default {
|
|
524
|
+
providers: [
|
|
525
|
+
provideTiledMap({ basePath: '${normalizePublicBasePath(options.tiledMapBasePath)}' }),
|
|
526
|
+
provideClientGlobalConfig(${JSON.stringify(config)}),
|
|
527
|
+
provideClientModules(modules)
|
|
528
|
+
]
|
|
529
|
+
}
|
|
530
|
+
`;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function createStandaloneEntryLoad() {
|
|
534
|
+
return dedent`
|
|
535
|
+
import { mergeConfig } from '@signe/di'
|
|
536
|
+
import { provideRpg, startGame } from '@rpgjs/client'
|
|
537
|
+
import server from '${SERVER_ENTRY}'
|
|
538
|
+
import configClient from '${CLIENT_CONFIG}'
|
|
539
|
+
|
|
540
|
+
startGame(mergeConfig(configClient, {
|
|
541
|
+
providers: [provideRpg(server)]
|
|
542
|
+
}))
|
|
543
|
+
`;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function createClientEntryLoad() {
|
|
547
|
+
return dedent`
|
|
548
|
+
import { mergeConfig } from '@signe/di'
|
|
549
|
+
import { provideMmorpg, startGame } from '@rpgjs/client'
|
|
550
|
+
import configClient from '${CLIENT_CONFIG}'
|
|
551
|
+
|
|
552
|
+
startGame(mergeConfig(configClient, {
|
|
553
|
+
providers: [provideMmorpg({})]
|
|
554
|
+
}))
|
|
555
|
+
`;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function normalizeModules(config: Config) {
|
|
559
|
+
return config.modules?.length ? config.modules : ["./src/modules/main"];
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function normalizeAliases(aliases: Record<string, string> = {}) {
|
|
563
|
+
return Object.fromEntries(
|
|
564
|
+
Object.entries(aliases).map(([key, value]) => [
|
|
565
|
+
key,
|
|
566
|
+
value.startsWith(".") ? path.resolve(process.cwd(), value) : value,
|
|
567
|
+
]),
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export default function compatibilityV4Plugin(options: Partial<ClientBuildConfigOptions> = {}): Plugin[] {
|
|
572
|
+
let viteMode = "development";
|
|
573
|
+
let config = loadConfigFileSync(viteMode);
|
|
574
|
+
let modules = normalizeModules(config);
|
|
575
|
+
let modulesCreated: string[] = [];
|
|
576
|
+
let viteRoot = process.cwd();
|
|
577
|
+
let viteOutputDir = path.resolve(viteRoot, config.compilerOptions?.build?.outputDir ?? "dist");
|
|
578
|
+
let resolvedOptions: ClientBuildConfigOptions = {
|
|
579
|
+
type: options.type ?? config.type ?? (process.env.RPG_TYPE as "rpg" | "mmorpg") ?? "rpg",
|
|
580
|
+
tiledMapBasePath: normalizePublicBasePath(options.tiledMapBasePath),
|
|
581
|
+
serveMode: true,
|
|
582
|
+
side: options.side ?? "client",
|
|
583
|
+
...options,
|
|
584
|
+
config,
|
|
585
|
+
};
|
|
586
|
+
let wsServer: RpgWebSocketServer | null = null;
|
|
587
|
+
const flagOptions = {
|
|
588
|
+
side: resolvedOptions.side,
|
|
589
|
+
mode: viteMode,
|
|
590
|
+
type: resolvedOptions.type,
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
const virtualPlugin: Plugin = {
|
|
594
|
+
name: "rpgjs-v4-compatibility",
|
|
595
|
+
enforce: "pre",
|
|
596
|
+
config() {
|
|
597
|
+
return {
|
|
598
|
+
resolve: {
|
|
599
|
+
alias: {
|
|
600
|
+
"@": path.resolve(process.cwd(), "src"),
|
|
601
|
+
...normalizeAliases(config.compilerOptions?.alias),
|
|
602
|
+
},
|
|
603
|
+
extensions: [".ts", ".js", ".jsx", ".json", ".vue", ".css", ".scss", ".sass", ".html", ".tmx", ".tsx", ".toml", ".ce"],
|
|
604
|
+
},
|
|
605
|
+
assetsInclude: ["**/*.tmx", "**/*.world", "{!(gui)/**/*}.tsx"],
|
|
606
|
+
};
|
|
607
|
+
},
|
|
608
|
+
configResolved(viteConfig) {
|
|
609
|
+
viteMode = viteConfig.mode || "development";
|
|
610
|
+
viteRoot = viteConfig.root;
|
|
611
|
+
viteOutputDir = path.resolve(viteRoot, viteConfig.build.outDir || "dist");
|
|
612
|
+
config = loadConfigFileSync(viteMode, viteConfig.root);
|
|
613
|
+
modules = normalizeModules(config);
|
|
614
|
+
modulesCreated = [];
|
|
615
|
+
resolvedOptions = {
|
|
616
|
+
...resolvedOptions,
|
|
617
|
+
type: options.type ?? config.type ?? (process.env.RPG_TYPE as "rpg" | "mmorpg") ?? "rpg",
|
|
618
|
+
serveMode: viteConfig.command === "serve",
|
|
619
|
+
mode: viteMode,
|
|
620
|
+
config,
|
|
621
|
+
};
|
|
622
|
+
flagOptions.side = resolvedOptions.side;
|
|
623
|
+
flagOptions.mode = viteMode;
|
|
624
|
+
flagOptions.type = resolvedOptions.type;
|
|
625
|
+
},
|
|
626
|
+
transformIndexHtml: {
|
|
627
|
+
order: "pre",
|
|
628
|
+
handler(html) {
|
|
629
|
+
const entry = resolvedOptions.type === "mmorpg" ? CLIENT_ENTRY : STANDALONE_ENTRY;
|
|
630
|
+
const script = `<script type="module">\nimport '${entry}'\n</script>`;
|
|
631
|
+
if (html.includes('<script type="module"')) {
|
|
632
|
+
return html.replace(/<script\s+type="module"\s+src="[^"]*"[^>]*><\/script>/gi, script);
|
|
633
|
+
}
|
|
634
|
+
return html.replace(/<\/head>/i, ` ${script}\n </head>`);
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
handleHotUpdate() {
|
|
638
|
+
modulesCreated = [];
|
|
639
|
+
},
|
|
640
|
+
resolveId(source) {
|
|
641
|
+
if ([MODULE_NAME, CLIENT_CONFIG, SERVER_ENTRY, CLIENT_ENTRY, STANDALONE_ENTRY, ...Object.values(LEGACY_MODULES)].includes(source)) {
|
|
642
|
+
return source;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
for (const module of modules) {
|
|
646
|
+
const moduleName = resolveModuleImport(module);
|
|
647
|
+
const variableName = formatVariableName(moduleName);
|
|
648
|
+
if (source === moduleName || source === `virtual:${variableName}-client` || source === `virtual:${variableName}-server`) {
|
|
649
|
+
return source;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return null;
|
|
654
|
+
},
|
|
655
|
+
load(id) {
|
|
656
|
+
if (id === MODULE_NAME) return createModulesLoad(modules);
|
|
657
|
+
if (id === CLIENT_CONFIG) return createClientConfigLoad(config, resolvedOptions);
|
|
658
|
+
if (id === SERVER_ENTRY) return createServerEntryLoad();
|
|
659
|
+
if (id === CLIENT_ENTRY) return createClientEntryLoad();
|
|
660
|
+
if (id === STANDALONE_ENTRY) return createStandaloneEntryLoad();
|
|
661
|
+
if (Object.values(LEGACY_MODULES).includes(id)) {
|
|
662
|
+
return createModuleLoad(id, formatVariableName(id), id, { ...resolvedOptions, modulesCreated }, config);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
for (const module of modules) {
|
|
666
|
+
const moduleName = resolveModuleImport(module);
|
|
667
|
+
const variableName = formatVariableName(moduleName);
|
|
668
|
+
if (id === moduleName || id.startsWith(`virtual:${variableName}-client?client`) || id.startsWith(`virtual:${variableName}-server?server`)) {
|
|
669
|
+
return createModuleLoad(id, variableName, module, { ...resolvedOptions, modulesCreated }, config);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return null;
|
|
674
|
+
},
|
|
675
|
+
async configureServer(server: ViteDevServer) {
|
|
676
|
+
serveTiledAssets(server, getAllTiledAssetRoots(modules, server.config.root), resolvedOptions.tiledMapBasePath);
|
|
677
|
+
|
|
678
|
+
if (resolvedOptions.type !== "mmorpg") return;
|
|
679
|
+
|
|
680
|
+
const { default: serverModule } = await server.ssrLoadModule(SERVER_ENTRY);
|
|
681
|
+
const transport = createRpgServerTransport(serverModule);
|
|
682
|
+
const WebSocketServerClass = await importWebSocketServer();
|
|
683
|
+
if (WebSocketServerClass) {
|
|
684
|
+
wsServer = new WebSocketServerClass({ noServer: true });
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
logNetworkSimulationStatus();
|
|
688
|
+
server.middlewares.use("/parties", async (req, res, next) => {
|
|
689
|
+
await transport.handleNodeRequest(req, res, next, {
|
|
690
|
+
mountedPath: "/parties",
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
if (wsServer) {
|
|
695
|
+
server.httpServer?.on("upgrade", (request, socket, head) => {
|
|
696
|
+
void transport.handleUpgrade(wsServer!, request, socket, head);
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
},
|
|
700
|
+
buildEnd() {
|
|
701
|
+
wsServer?.close();
|
|
702
|
+
},
|
|
703
|
+
generateBundle() {
|
|
704
|
+
copyTiledAssets(getAllTiledAssetRoots(modules, viteRoot), viteOutputDir, resolvedOptions.tiledMapBasePath);
|
|
705
|
+
},
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
return [
|
|
709
|
+
flagTransform(flagOptions),
|
|
710
|
+
vitePluginRequire(),
|
|
711
|
+
virtualPlugin,
|
|
712
|
+
];
|
|
713
|
+
}
|