sapdon 3.5.3 → 3.6.0
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/README.md +35 -3
- package/doc/dev/architecture.md +69 -23
- package/doc/dev/cli.md +179 -43
- package/doc/dev/core.md +2 -1
- package/doc/dev/known-pitfalls.md +869 -0
- package/doc/dev/oc.md +156 -23
- package/doc/dev/ui-architecture.md +83 -0
- package/doc/dev/workflow.md +61 -19
- package/doc/guidebook.md +196 -6
- package/doc/user/api/block.md +342 -0
- package/doc/user/api/runtime.md +332 -0
- package/doc/user/faq.md +96 -14
- package/package.json +1 -1
- package/prod/cli/index.js +1 -1
- package/prod/cli/start.js +1 -1
- package/prod/core/index.d.ts +620 -74
- package/prod/core/index.js +1 -1
- package/prod/oc/index.d.ts +134 -3
- package/prod/oc/index.js +1 -1
- package/prod/templates/js_sapdon/package.json +4 -5
- package/prod/templates/js_sapdon/res/models/blocks/cube.geo.json +35 -0
- package/prod/templates/ts_sapdon/package.json +4 -5
- package/prod/templates/ts_sapdon/res/models/blocks/cube.geo.json +35 -0
package/README.md
CHANGED
|
@@ -213,20 +213,29 @@ RecipeAPI.registerSimpleFurnace('my:smelted', 'my:ore')
|
|
|
213
213
|
| [实体教程](./doc/user/tutorials/entity.md) | 创建实体 → 组件 → AI 行为 |
|
|
214
214
|
| [方块教程](./doc/user/tutorials/block.md) | 基础方块 → 旋转 → 作物 |
|
|
215
215
|
| [配方教程](./doc/user/tutorials/recipe.md) | 有序/无序/熔炉配方 |
|
|
216
|
-
| [
|
|
217
|
-
| [指南书实战经验](./doc/user/tutorials/neo-guidebook-experience.md) | 接入流程与踩坑清单 |
|
|
216
|
+
| [UI 教程](./doc/user/tutorials/sapdon-ui.md) | 自定义 Server Form 页面壳(含完整示例) |
|
|
218
217
|
| [物品 API](./doc/user/api/item.md) | ItemAPI、Item、ItemComponent |
|
|
219
218
|
| [实体 API](./doc/user/api/entity.md) | EntityAPI、EntityComponent、AI |
|
|
220
|
-
| [方块 API](./doc/user/api/block.md) | BlockAPI、BlockComponent |
|
|
219
|
+
| [方块 API](./doc/user/api/block.md) | BlockAPI、BlockComponent、TileBlock、自定义组件两条路线 |
|
|
220
|
+
| [运行期 API](./doc/user/api/runtime.md) | `@sapdon/runtime`:自定义组件注册、分块持久化 |
|
|
221
|
+
| [UI API](./doc/user/api/sapdon-ui.md) | Sapdon UI 页面壳、FormButton / FormButtonGrid |
|
|
221
222
|
| [配方 API](./doc/user/api/recipe.md) | RecipeAPI、配方类 |
|
|
222
223
|
| [生物群系 & 特征 API](./doc/user/api/biome.md) | BiomeAPI、FeatureAPI |
|
|
223
224
|
| [纹理 API](./doc/user/api/texture.md) | 纹理管理器 |
|
|
225
|
+
| [扩展模块 API](./doc/user/api/extra.md) | ClientEntityApperance、BaseVehicle |
|
|
226
|
+
| [手册(Guidebook)](./doc/guidebook.md) | SapdonGuideBook:三层手册、页类型、路由协议、多语言 |
|
|
224
227
|
| [build.config](./doc/user/config/build-config.md) | 构建配置字段 |
|
|
228
|
+
| [mod.info](./doc/user/config/mod-info.md) | 模组元数据 |
|
|
225
229
|
| [常见问题](./doc/user/faq.md) | FAQ |
|
|
226
230
|
| [架构概览](./doc/dev/architecture.md) | 整体架构(源码开发者) |
|
|
227
231
|
| [Core 模块](./doc/dev/core.md) | 三层架构详解(源码开发者) |
|
|
228
232
|
| [CLI 模块](./doc/dev/cli.md) | 构建管道(源码开发者) |
|
|
229
233
|
| [OC 运行时](./doc/dev/oc.md) | ECS 框架(源码开发者) |
|
|
234
|
+
| [开发工作流](./doc/dev/workflow.md) | 框架自身构建、全局 CLI、项目侧同步(源码开发者) |
|
|
235
|
+
| [已知坑清单](./doc/dev/known-pitfalls.md) | 改框架前先扫一遍(源码开发者) |
|
|
236
|
+
| [L-R 编程范式](./doc/dev/lr-paradigm.md) | 存量类 Addon 通用骨架(源码开发者) |
|
|
237
|
+
| [UI 架构](./doc/dev/ui-architecture.md) | JSON UI 的分层与生成(源码开发者) |
|
|
238
|
+
| [UI 经验](./doc/dev/ui-lessons.md) | JSON UI 背景与踩坑(源码开发者) |
|
|
230
239
|
|
|
231
240
|
---
|
|
232
241
|
|
|
@@ -243,6 +252,29 @@ npm run build
|
|
|
243
252
|
- `npm run build -- verbose` — 查看详细日志
|
|
244
253
|
- `npm run build -- keep` — 保留中间 `dist/` 目录
|
|
245
254
|
|
|
255
|
+
### 受限环境:手工 4 步等价流程
|
|
256
|
+
|
|
257
|
+
`npm run build` 内部走 `cp.spawn` + 管道,在受限沙箱里跑不通。它做的其实就是下面 4 步,可以逐条手工执行(等价):
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
# 1) TypeScript 编译:src/ → dist/
|
|
261
|
+
tsc
|
|
262
|
+
# 2) 解析路径别名(★ 不能漏)
|
|
263
|
+
npx tsc-alias
|
|
264
|
+
# 3) 打包:dist/ → prod/(rollup)
|
|
265
|
+
node scripts/buildTask.cjs
|
|
266
|
+
# 4) 拷贝 src/templates → prod/templates,然后删除 dist/
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
> ⚠️ **漏掉第 2 步 `tsc-alias` 的后果**:`dist/` 里会残留 `@sapdon/utils/...` 这类裸别名,
|
|
270
|
+
> rollup 解析不到就当成 external ⇒ **`prod/cli/start.js` 里会留下无法解析的 `@sapdon/utils` 裸包名**,
|
|
271
|
+
> 表现为 `ERR_MODULE_NOT_FOUND: Cannot find package '@sapdon/utils'`。
|
|
272
|
+
>
|
|
273
|
+
> ⚠️ 另外:**"rollup N/N 成功"不等于 `prod/` 是新的** —— 改完要断言 `prod/` 里确实有新导出,别只看 `Failed: 0`。
|
|
274
|
+
|
|
275
|
+
流程细节与常见坑(全局 CLI junction、项目侧 `sapdon lib` 同步、HMR 等)见 [doc/dev/workflow.md](./doc/dev/workflow.md);
|
|
276
|
+
受限环境的完整说明另见 [doc/dev/known-pitfalls.md](./doc/dev/known-pitfalls.md) §5。
|
|
277
|
+
|
|
246
278
|
---
|
|
247
279
|
|
|
248
280
|
## 致谢
|
package/doc/dev/architecture.md
CHANGED
|
@@ -105,7 +105,7 @@ src/core/addon/
|
|
|
105
105
|
|-----|------|---------|
|
|
106
106
|
| `ItemAPI` | `factory/itemFactory.js` | `createItem()`, `createFood()`, `createAttachable()`, armor 系列 |
|
|
107
107
|
| `EntityAPI` | `factory/entityFactory.js` | `createEntity()`, `createDummyEntity()`, `createProjectile()` |
|
|
108
|
-
| `BlockAPI` | `factory/blockFactory.js` |
|
|
108
|
+
| `BlockAPI` | `factory/blockFactory.js` | 共 12 个工厂方法:`createBasicBlock()`, `createBlock()`, `createRotatableBlock()`, `createGeometryBlock()`, **`createTileBlock()`**(带实体方块), `createHeadBlock()`, `createGlassBlock()`, `createFenceBlock()`, `createStairBlock()`, `createTrapdoorBlock()`, `createCropBlock()` |
|
|
109
109
|
| `RecipeAPI` | `factory/recipeFactory.js` | `registerSimpleShaped()`, `registerSimpleShapeless()`, `registerSimpleFurnace()` |
|
|
110
110
|
| `BiomeAPI` | `factory/biomeFactory.js` | `createBiome()` |
|
|
111
111
|
| `FeatureAPI` | `factory/featureFactory.js` | `createOreFeature()`, `createFeatureRules()` |
|
|
@@ -283,29 +283,40 @@ export function submit() {
|
|
|
283
283
|
```
|
|
284
284
|
1. projectCanBuild() → 验证项目目录和 build.config
|
|
285
285
|
2. initResourceDir() → 扫描 res/ 目录,生成 res.hint.ts
|
|
286
|
+
⚠️ 只在 `sapdon build` 命令里调用(start.js:117);`sapdon compile` 不调用
|
|
286
287
|
3. 生成 manifest.json → behavior pack + resource pack 清单
|
|
288
|
+
⚠️ 仅当 dev server 尚未监听、且 manifest 文件不存在时
|
|
287
289
|
4. 复制 pack_icon.png
|
|
288
290
|
5. 复制资源文件 res/ → RP
|
|
289
291
|
6. 启动开发服务器 → startDevServer() + GRegistryServer.startServer()
|
|
290
|
-
|
|
292
|
+
端口取 SAPDON_DEV_SERVER_PORT(默认 49037)
|
|
293
|
+
7. runScript(main.ts) → rollup 编译 → cp.spawn(process.execPath, [file]) 执行
|
|
291
294
|
子进程中用户代码执行并注册数据
|
|
292
295
|
HTTP POST 提交数据到 dev server
|
|
293
296
|
generateAddon() 生成 JSON 文件
|
|
294
|
-
|
|
297
|
+
★ 检查子进程退出码,非 0 直接抛错
|
|
298
|
+
8. bundleScripts() → rollup 打包 scripts/main.ts → scripts/index.js(await)
|
|
295
299
|
9. syncDevFilesServer() → 复制 BP/RP 到 Minecraft 开发包目录
|
|
300
|
+
10. 收尾 → keepServer 为真:保持服务器常开
|
|
301
|
+
为假(默认):process.exit(0) 自动退出
|
|
296
302
|
```
|
|
297
303
|
|
|
304
|
+
**失败必须非 0 退出**:历史行为是构建脚本失败被吞掉、CLI 照样打印「构建完成」并 `exit 0`,于是 `dev/` 里留下的是旧产物。现在 `runOnChild()` 检查退出码、`scriptBundler` 打包失败即抛、CLI 顶层 catch `process.exit(1)`。⇒ **「构建成功」不可信**,成功的判据是**退出码 0** + 日志里出现 `处理数据: <name> <root> <path>`(`src/cli/load.js:210`)。
|
|
305
|
+
|
|
306
|
+
⚠️ **不要用 `cp.fork`**:`fork` 一定会建立 IPC **命名管道**(受限环境下 `spawn EPERM`),而框架传输层走 **HTTP**(`src/core/transport/client.ts` → localhost),**从不使用 IPC**;`spawn(process.execPath, [file], { stdio: 'inherit' })` 是等价且更少依赖的写法(`src/cli/build.js:191-200`)。
|
|
307
|
+
|
|
298
308
|
### 6.3 关键文件职责
|
|
299
309
|
|
|
300
310
|
| 文件 | 作用 |
|
|
301
311
|
|------|------|
|
|
302
312
|
| `src/cli/start.js` | CLI 入口,定义所有 commander 命令 |
|
|
303
313
|
| `src/cli/build.js` | 构建编排器:`scriptBundler`、`buildProject()`、`runScript()` |
|
|
304
|
-
| `src/cli/load.js` | `generateAddon()` — 遍历 dataList 写入 JSON 文件,生成纹理 JSON |
|
|
305
|
-
| `src/cli/init.js` |
|
|
314
|
+
| `src/cli/load.js` | `generateAddon()` — 遍历 dataList 写入 JSON 文件,生成纹理 JSON;陈旧产物清单清理;自定义组件注册索引合并 |
|
|
315
|
+
| `src/cli/init.js` | 项目初始化:脚手架生成、路径辅助(`getBuildDirBp/Rp` 固定大写 `_BP`/`_RP`) |
|
|
316
|
+
| `src/cli/pack.js` | `packProject()` — 把 `dev/<proj>_BP`、`dev/<proj>_RP` 打包为 `.mcaddon` |
|
|
306
317
|
| `src/cli/utils.ts` | `saveFile`、`readFile`、`copyFileSync` 等文件 I/O 工具 |
|
|
307
|
-
| `src/cli/registryServer.ts` | `GRegistryServer` — 服务端注册表,注册
|
|
308
|
-
| `src/cli/dev-server/server.ts` | `DevelopmentServer` — HTTP
|
|
318
|
+
| `src/cli/registryServer.ts` | `GRegistryServer` — 服务端注册表,注册 `submitGregistry` / `remote-logger` handler |
|
|
319
|
+
| `src/cli/dev-server/server.ts` | `DevelopmentServer` — 构建时进程间通信的 HTTP 服务(**不是 IPC**) |
|
|
309
320
|
| `src/cli/dev-server/client.js` | `cliRequest()` / `post()` — CLI 内部使用的 HTTP 客户端(remoteLogger 等) |
|
|
310
321
|
| `src/cli/dev-server/hmr.js` | `hmr()` — 文件监听器,热更新触发重建 |
|
|
311
322
|
| `src/cli/dev-server/syncFiles.js` | `syncDevFilesServer()` — 同步到 Minecraft 目录;`writeLib()` — 复制库文件 |
|
|
@@ -325,10 +336,13 @@ export function submit() {
|
|
|
325
336
|
|
|
326
337
|
### Handler 列表
|
|
327
338
|
|
|
328
|
-
| Handler | 用途 |
|
|
329
|
-
|
|
330
|
-
| `
|
|
331
|
-
| `
|
|
339
|
+
| Handler | 注册位置 | 用途 |
|
|
340
|
+
|---------|---------|------|
|
|
341
|
+
| `submitGregistry` | `registryServer.ts:12` | 接收注册数据、存入 `GRegistryServer.dataList`(由 `GRegistry.submit()` 提交,`core/registry.ts:52`) |
|
|
342
|
+
| `submit` | `build.js:308` | 接收数据并触发 `generateAddon()` 写 JSON(由 `registry.submit()` 提交,`core/registry.ts:87`);`buildMode === 'debug'` 时跳过生成 |
|
|
343
|
+
| `remote-logger` | `registryServer.ts:16` | 游戏内 Script API 通过此 handler 发送日志到 CLI |
|
|
344
|
+
|
|
345
|
+
⚠️ `GRegistry.submit()` 与 `registry.submit()` 是**两个不同入口、POST 到两个不同 handler**:前者只上传数据(供 dev server 侧的注册表使用),后者才触发 `generateAddon()` 落盘。用户项目里通常调用 `registry.submit()`。
|
|
332
346
|
|
|
333
347
|
---
|
|
334
348
|
|
|
@@ -364,13 +378,13 @@ OC (Object-Component) 是一个 ECS 风格的游戏框架,用于 Minecraft Scr
|
|
|
364
378
|
- `@MinecraftMain` — 标记主游戏类,自动绑定玩家/实体生成事件
|
|
365
379
|
- `@PlayerSpawned` / `@EntitySpawned` / `@ActorSpawned` — 实体生成时自动附加组件
|
|
366
380
|
- `@SpawnFilter` — 过滤哪些实体附加组件
|
|
367
|
-
-
|
|
381
|
+
- `RequireComponents(...)` — ⚠️ 它不是装饰器,而是 `src/oc/core.ts:221` 的 **mixin 工厂函数**(返回一个带依赖的组件类);用法见 [OC 运行时文档](oc.md) 第 2.5 节
|
|
368
382
|
|
|
369
383
|
---
|
|
370
384
|
|
|
371
385
|
## 10. 框架自身的构建流程 (`scripts/build.cjs`)
|
|
372
386
|
|
|
373
|
-
Sapdon
|
|
387
|
+
Sapdon 框架自身的构建使用四步管道:
|
|
374
388
|
|
|
375
389
|
```
|
|
376
390
|
tsc (TypeScript 编译)
|
|
@@ -387,8 +401,14 @@ rollup (打包)
|
|
|
387
401
|
│ + 各自的 .d.ts 声明文件
|
|
388
402
|
│ 插件: node-resolve, commonjs, json, terser
|
|
389
403
|
│ 外部化: rollup, typescript, @sapdon/*, @minecraft/*
|
|
404
|
+
▼
|
|
405
|
+
收尾 (scripts/build.cjs)
|
|
406
|
+
│ 拷贝 src/templates → prod/templates
|
|
407
|
+
└─ 删除 dist/(除非 `node scripts/build.cjs keep`)
|
|
390
408
|
```
|
|
391
409
|
|
|
410
|
+
> 这 4 步由 `scripts/build.cjs` 串起来(`npm run build`)。⚠️ 在受限环境里需要**拆开直跑**这 4 步,并注意「漏 `tsc-alias`」与「rollup 全绿 ≠ prod 是新的」两个坑,详见 [开发工作流](workflow.md) 第 2 节。
|
|
411
|
+
|
|
392
412
|
> **开发工作流**:修改 `src/` 后运行 `npm run build` 重建 `prod/`,在项目侧通过 `npm i`(触发 `postinstall` → `sapdon lib`)或直接运行 `sapdon lib` 将新库同步到 `node_modules/@sapdon/`,然后使用 `sapdon compile` / `npm run build`。详见 [开发工作流](workflow.md)。
|
|
393
413
|
|
|
394
414
|
---
|
|
@@ -399,16 +419,35 @@ rollup (打包)
|
|
|
399
419
|
|
|
400
420
|
| 数据类型 | root | dataPath | 输出路径 |
|
|
401
421
|
|---------|------|----------|---------|
|
|
402
|
-
| 物品 | `behavior` | `items/` | `dev/<
|
|
403
|
-
| 实体 (行为) | `behavior` | `entities/` | `dev/<
|
|
404
|
-
| 方块 | `behavior` | `blocks/` | `dev/<
|
|
405
|
-
| 配方 | `behavior` | `recipes/` | `dev/<
|
|
406
|
-
| 生物群系 | `behavior` | `biomes/` | `dev/<
|
|
407
|
-
| 特征 | `behavior` | `features/` | `dev/<
|
|
408
|
-
| 特征规则 | `behavior` | `feature_rules/` | `dev/<
|
|
409
|
-
|
|
|
410
|
-
|
|
|
411
|
-
|
|
|
422
|
+
| 物品 | `behavior` | `items/` | `dev/<proj>_BP/items/<name>.json` |
|
|
423
|
+
| 实体 (行为) | `behavior` | `entities/` | `dev/<proj>_BP/entities/<name>.json` |
|
|
424
|
+
| 方块 | `behavior` | `blocks/` | `dev/<proj>_BP/blocks/<name>.json` |
|
|
425
|
+
| 配方 | `behavior` | `recipes/` | `dev/<proj>_BP/recipes/<name>.json` |
|
|
426
|
+
| 生物群系 | `behavior` | `biomes/` | `dev/<proj>_BP/biomes/<name>.json` |
|
|
427
|
+
| 特征 | `behavior` | `features/` | `dev/<proj>_BP/features/<name>.json` |
|
|
428
|
+
| 特征规则 | `behavior` | `feature_rules/` | `dev/<proj>_BP/feature_rules/<name>.json` |
|
|
429
|
+
| **方块音效表** `blocks.json` | `resource` | (空) | **`dev/<proj>_RP/blocks.json`**(★ 2026-09 起只含 `format_version`,不写方块条目) |
|
|
430
|
+
| 实体 (资源) | `resource` | `entity/` | `dev/<proj>_RP/entity/<name>.json` |
|
|
431
|
+
| 附着物 | `resource` | `attachables/` | `dev/<proj>_RP/attachables/<name>.json` |
|
|
432
|
+
| 渲染控制器 | `resource` | `render_controllers/` | `dev/<proj>_RP/render_controllers/<name>.json` |
|
|
433
|
+
| 物品贴图图集 | — | — | `dev/<proj>_RP/textures/item_texture.json` |
|
|
434
|
+
| 方块贴图图集 | — | — | `dev/<proj>_RP/textures/terrain_texture.json` |
|
|
435
|
+
| 翻书贴图 | — | — | `dev/<proj>_RP/textures/flipbook_textures.json` |
|
|
436
|
+
|
|
437
|
+
说明:
|
|
438
|
+
|
|
439
|
+
- 路径里的 `<proj>` 是**项目名**(目录 basename),`<name>` 是**注册项的数据名**,两者是两个不同的值。
|
|
440
|
+
- `_BP` / `_RP` 必须**大写**(`src/cli/init.js:105-114`)。历史版本曾用小写 `_bp`/`_rp`,Windows 大小写不敏感掩盖了它,Linux/macOS 下会分叉成两个目录(构建写 A、打包读 B → 空包)。
|
|
441
|
+
- **`blocks.json` 属资源包(RP)**,不是行为包。依据:[Bedrock Wiki · Pack Folder Structure](https://wiki.bedrock.dev/documentation/pack-structure) 把 `blocks.json` 列在 **RP** 根目录(BP 侧无此文件);[Bedrock Wiki · Block Sounds](https://wiki.bedrock.dev/blocks/block-sounds) 的示例标题即 `RP/blocks.json`。
|
|
442
|
+
- ★ **2026-09 起 `blocks.json` 里不再写任何方块条目**(只留 `{"format_version": "1.20.20"}`):曾经每个方块写一条
|
|
443
|
+
`{ "ns:name": { "textures": { up/down/... } } }`,引擎随后对**每个自定义方块**报
|
|
444
|
+
`trying to override the Geometry component with blocks.json settings for a custom block`。
|
|
445
|
+
官方定位(Microsoft Learn · blocks.json File Reference):`minecraft:geometry` / `minecraft:material_instances`
|
|
446
|
+
**会覆盖** blocks.json,官方推荐用组件写视觉,`blocks.json` 只当 **sound** 配置系统
|
|
447
|
+
<https://learn.microsoft.com/en-us/minecraft/creator/reference/content/blockreference/examples/blocksjsonfilestructure>。
|
|
448
|
+
自定义方块的贴图由 `minecraft:material_instances` + `terrain_texture.json` 提供(不经 blocks.json)。
|
|
449
|
+
(键格式的历史:曾要求 `ns:name`,对应护栏 `assertBlocksJsonKey()` 已随之删除。)
|
|
450
|
+
⚠️ 只含 `format_version` 的文件引擎会不会抱怨、音效是否正常,**未验证**(本项目无法启动 Minecraft)。
|
|
412
451
|
|
|
413
452
|
最终同步到 Minecraft 目录(`versionType` 决定路径):
|
|
414
453
|
|
|
@@ -416,3 +455,10 @@ rollup (打包)
|
|
|
416
455
|
- **beta**: `%USERPROFILE%\AppData\Local\Packages\Microsoft.MinecraftWindowsBeta_8wekyb3d8bbwe\LocalState\games\com.mojang\development_<behavior|resource>_packs\<name>_<BP|RP>\`
|
|
417
456
|
|
|
418
457
|
可通过环境变量 `MC_PATH` 或 `MC_BETA_PATH` 覆盖。
|
|
458
|
+
|
|
459
|
+
**★ 同步是「按清单 prune」而不是只增不减**(2026-09 修,见 `doc/dev/cli.md`):
|
|
460
|
+
`syncFiles.js` 的 `syncDevFilesServer()` 会把「上次部署过、这次 `dev/` 里已经没有」的文件
|
|
461
|
+
从上面这两个开发包目录里删掉(清单 `dev/.sapdon_synced_<proj>.json`);
|
|
462
|
+
`syncResourceFiles()` 同理清理 `dev/<proj>_RP/` 里「上次从 `res/` 拷过、这次 `res/` 没有」的文件
|
|
463
|
+
(清单 `dev/.sapdon_res_<proj>.json`)。**没有清单时一个文件都不删**,且**从不扫目录删未知文件**
|
|
464
|
+
(玩家自己放进开发包的东西必须留住)。单测:`node tests/sync-manifest.test.mjs`。
|
package/doc/dev/cli.md
CHANGED
|
@@ -11,8 +11,9 @@ src/cli/
|
|
|
11
11
|
├── index.js # @sapdon/cli 包入口,导出 dev server 客户端
|
|
12
12
|
├── start.js # CLI 命令定义 (commander),入口脚本
|
|
13
13
|
├── build.js # 构建编排器:bundler、manifest 生成、注册
|
|
14
|
-
├── load.js # 处理注册数据,生成 JSON 文件
|
|
15
|
-
├──
|
|
14
|
+
├── load.js # 处理注册数据,生成 JSON 文件 + 陈旧产物清理 + 注册索引合并
|
|
15
|
+
├── pack.js # packProject() — 把 dev/<proj>_BP、_RP 打包为 .mcaddon
|
|
16
|
+
├── registryServer.ts # GRegistryServer — 服务端注册表,注册 submitGregistry / remote-logger handler
|
|
16
17
|
├── init.js # 项目初始化、路径辅助
|
|
17
18
|
├── utils.ts # 通用文件 I/O 工具
|
|
18
19
|
├── tools/
|
|
@@ -37,7 +38,7 @@ src/cli/
|
|
|
37
38
|
└── message.ts # 日志消息类型定义
|
|
38
39
|
```
|
|
39
40
|
|
|
40
|
-
共
|
|
41
|
+
共 23 个文件。
|
|
41
42
|
|
|
42
43
|
---
|
|
43
44
|
|
|
@@ -51,12 +52,16 @@ src/cli/
|
|
|
51
52
|
|------|------|
|
|
52
53
|
| `init` | 为已有项目添加 sapdon 配置(修改 package.json scripts,生成 mod.info) |
|
|
53
54
|
| `create <name>` | 从模板脚手架新项目,支持 `js` / `ts` 选择 |
|
|
54
|
-
| `build <name>` | 完整构建 +
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
55
|
+
| `build <name>` | 完整构建 + 启动热更新(构建前会调用 `initResourceDir()`) |
|
|
56
|
+
| `compile` | 构建当前目录(不启动 HMR)。⚠️ **不会**调用 `initResourceDir()`,即不重新生成 `res.hint.ts` |
|
|
57
|
+
| `pack` | 把 `dev/<proj>_BP` + `dev/<proj>_RP` 打包为 `dev/<proj>.mcaddon`(**不构建**,直接打包现有产物) |
|
|
58
|
+
| `lib` | 复制 `prod/core`、`prod/cli`、`prod/oc` 到 `node_modules/@sapdon/{core,cli,runtime}` |
|
|
57
59
|
| `res` | 生成资源提示文件 `res.hint.ts` |
|
|
58
60
|
| `config` | (占位) 读取 build.config |
|
|
59
61
|
|
|
62
|
+
> 命令定义见 `src/cli/start.js`。`compile` 与 `pack` 的差别是容易记错的一处:`compile` = 构建不打包,`pack` = 打包不构建。
|
|
63
|
+
> `initResourceDir()` 只在 `build`(`start.js:117`)与 `res`(`start.js:156`)里调用,`compile`(`start.js:130-140`)没有调用 —— 所以新增 `res/` 资源后只跑 `sapdon compile` 不会刷新 `res.hint.ts`,需要单独跑 `sapdon res`。
|
|
64
|
+
|
|
60
65
|
### `index.js` — 包入口
|
|
61
66
|
|
|
62
67
|
`@sapdon/cli` 的包入口,供其他模块编程式使用:
|
|
@@ -79,34 +84,58 @@ import { devServer, client } from '@sapdon/cli'
|
|
|
79
84
|
```
|
|
80
85
|
1. projectCanBuild()
|
|
81
86
|
→ 验证项目存在 + build.config 存在
|
|
87
|
+
⚠️ 未通过时只打印原因并 return,命令本身不报错(见「退出码语义」)
|
|
88
|
+
|
|
89
|
+
2. 【仅当 dev server 尚未监听时执行】(if (!server.isListening()))
|
|
90
|
+
2a. 生成 manifest.json(仅当文件不存在时:if (pathNotExist(manifestPath)))
|
|
91
|
+
→ BP/manifest.json + RP/manifest.json
|
|
92
|
+
→ 通过 AddonManifest 类生成;BP/RP 的 UUID 由 loadOrCreateUuids() 从 mod.info 读写
|
|
93
|
+
2b. 复制 pack_icon.png → BP + RP
|
|
94
|
+
2c. 复制资源文件夹 (res/) → RP
|
|
95
|
+
2d. startDevServer()
|
|
96
|
+
→ 启动 HTTP 服务器(端口取 SAPDON_DEV_SERVER_PORT,默认 49037)
|
|
97
|
+
2e. GRegistryServer.startServer()
|
|
98
|
+
→ 注册 submitGregistry / remote-logger handler
|
|
99
|
+
2f. server.handle('submit', ...)
|
|
100
|
+
→ 收到数据后调用 generateAddon() 写 JSON
|
|
101
|
+
→ buildMode === 'debug' 时跳过生成(只同步已有 dev/)
|
|
102
|
+
|
|
103
|
+
3. runScript(absoluteModPath)
|
|
104
|
+
→ rollup 编译 buildEntry (main.ts) 到 .tmp/<uuid>.js
|
|
105
|
+
→ cp.spawn(process.execPath, [file], { stdio: 'inherit' }) 执行
|
|
106
|
+
→ ★ 检查退出码:非 0 立即抛错(不再静默走到「构建完成」)
|
|
107
|
+
→ 子进程中用户代码注册数据
|
|
108
|
+
→ HTTP POST 提交到 dev server → generateAddon() 生成 JSON 文件
|
|
109
|
+
→ finally 删除临时文件
|
|
82
110
|
|
|
83
|
-
|
|
84
|
-
→
|
|
85
|
-
→ 通过 AddonManifest 类生成
|
|
111
|
+
4. bundleScripts()
|
|
112
|
+
→ rollup 打包 scripts/main.ts → scripts/index.js(await,失败即抛)
|
|
86
113
|
|
|
87
|
-
|
|
114
|
+
5. syncDevFilesServer()
|
|
115
|
+
→ 复制 BP/RP 到 Minecraft 开发包目录
|
|
88
116
|
|
|
89
|
-
|
|
117
|
+
6. 收尾
|
|
118
|
+
→ buildOptions.keepServer 为真:保持开发服务器常开(配合 HMR)
|
|
119
|
+
→ 为假(默认):打印「构建完成,开发服务器已自动退出」并 process.exit(0)
|
|
120
|
+
```
|
|
90
121
|
|
|
91
|
-
|
|
92
|
-
→ 启动 HTTP 服务器 (端口 49037)
|
|
122
|
+
**为什么 step 3 用 `spawn` 而不是 `fork`**(`src/cli/build.js:191-200`):
|
|
93
123
|
|
|
94
|
-
|
|
95
|
-
→ 注册 submit handler
|
|
124
|
+
`cp.fork` 一定会建立 IPC **命名管道**,在受限环境下会以 `EPERM`(`spawn EPERM`)失败;而框架的传输层走 **HTTP**(`src/core/transport/client.ts` → `localhost:49037`),**从不使用 IPC**。所以 `cp.spawn(process.execPath, [file], { stdio: 'inherit' })` 对「跑一个 Node 脚本」是等价且更少依赖的写法。
|
|
96
125
|
|
|
97
|
-
|
|
98
|
-
→ rollup 编译 buildEntry (main.ts)
|
|
99
|
-
→ fork 子进程执行
|
|
100
|
-
→ 子进程中用户代码注册数据
|
|
101
|
-
→ HTTP POST 提交到 dev server
|
|
102
|
-
→ generateAddon() 生成 JSON 文件
|
|
126
|
+
### 退出码语义(失败必须非 0)
|
|
103
127
|
|
|
104
|
-
|
|
105
|
-
→ rollup 打包 scripts/main.ts → scripts/index.js
|
|
128
|
+
历史行为是**失败被吞掉**、CLI 照样打印「构建完成」并 `exit 0`,于是 `dev/` 里留下的是**上一次的旧产物**(本仓库真实踩过的坑)。现在:
|
|
106
129
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
130
|
+
| 环节 | 行为 | 源码 |
|
|
131
|
+
|------|------|------|
|
|
132
|
+
| 构建脚本子进程非 0 退出 | 抛错 | `src/cli/build.js:191-200` `runOnChild()` |
|
|
133
|
+
| `scriptBundler` 打包失败 | 抛错(不再只 `console.error`) | `src/cli/build.js:101-104`、`152-155` |
|
|
134
|
+
| CLI 顶层 catch | `process.exit(1)` | `src/cli/start.js:118-126`(build)、`133-139`(compile) |
|
|
135
|
+
|
|
136
|
+
⇒ **「构建成功」这句话不可信**。成功的判据是**退出码 0** + 日志里出现 `处理数据: <name> <root> <path>` 行(`src/cli/load.js:210`)。
|
|
137
|
+
|
|
138
|
+
⚠️ 注意 `projectCanBuild()` 失败(项目不存在 / 没有 `build.config`)时只是**打印原因并 return**,命令本身**不报错**、退出码仍为 0 —— 这是另一条需要看日志才能发现的路径。
|
|
110
139
|
|
|
111
140
|
**导出的关键组件:**
|
|
112
141
|
|
|
@@ -137,22 +166,105 @@ scriptBundler.any(source, target)
|
|
|
137
166
|
```
|
|
138
167
|
generateAddon(modPath, buildPath, projectName)
|
|
139
168
|
|
|
169
|
+
previousFiles = readManifest(buildPath, projectName) # 上次的产物清单
|
|
170
|
+
cleanLegacyBpBlocksJson(buildPath, projectName) # 清理历史误放在 BP 的 blocks.json
|
|
171
|
+
generatedFiles = [] # 本次写出的产物(相对 buildPath)
|
|
172
|
+
|
|
140
173
|
for each { name, root, path: dataPath, data } in dataList:
|
|
174
|
+
├── 日志:console.log('处理数据:', name, root, dataPath) ← 这是「产物真的生成了」的唯一判据
|
|
141
175
|
├── case "item_texture" → 暂存,稍后生成 item_texture.json
|
|
142
176
|
├── case "terrain_texture" → 暂存,稍后生成 terrain_texture.json
|
|
143
177
|
├── case "flipbook_textures" → 暂存,稍后生成 flipbook_textures.json
|
|
178
|
+
├── data._scriptSource 存在 → 写 scripts/custom_components/<name>.js(已存在则跳过)
|
|
179
|
+
│ 并登记进 customComponentInfos
|
|
144
180
|
└── default:
|
|
145
|
-
root == "behavior"
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
→
|
|
181
|
+
root == "behavior" → writeFileSync(<buildPath>/<projectName>_BP/<dataPath>/<name>.json)
|
|
182
|
+
root == "resource" → writeFileSync(<buildPath>/<projectName>_RP/<dataPath>/<name>.json)
|
|
183
|
+
(每个写出的文件都 record() 进 generatedFiles)
|
|
184
|
+
|
|
185
|
+
customComponentInfos 非空 → writeCustomComponentIndex(<projectPath>/scripts/custom_components/index.js)
|
|
186
|
+
|
|
187
|
+
generateTextureFiles(resDir, ...)
|
|
188
|
+
→ generateItemTextureJson() → <proj>_RP/textures/item_texture.json
|
|
189
|
+
→ generateBlockTextureJson() → <proj>_RP/textures/terrain_texture.json
|
|
153
190
|
→ saveFile(flipbook_textures.json)
|
|
191
|
+
|
|
192
|
+
cleanStaleGenerated(buildPath, previousFiles.files, generatedFiles) # 删「上次有、这次没有」的
|
|
193
|
+
writeManifest(buildPath, projectName, generatedFiles) # 写回本次清单
|
|
154
194
|
```
|
|
155
195
|
|
|
196
|
+
⚠️ 注意 path 用的是**项目名** `${projectName}_BP` / `${projectName}_RP`(`load.js:183-184`),而 `<name>.json` 用的是注册项的**数据名**,两者是两个不同的值。
|
|
197
|
+
|
|
198
|
+
### 陈旧产物清理(缺口 9)
|
|
199
|
+
|
|
200
|
+
清单文件:`dev/.sapdon_generated_<proj>.json`(`load.js:11-12`),记录**本次构建写出的产物**(相对 `dev/` 的路径)。
|
|
201
|
+
|
|
202
|
+
- 下次构建只删「**上次清单里有、这次清单里没有**」的路径 → 改名 / 删条目留下的旧 JSON 会被自动清掉。
|
|
203
|
+
- ★ **禁止**把它改成「扫目录删未知文件」:那会把用户从 `res/` 拷进来、以及手写的文件一起误删。
|
|
204
|
+
- ⚠️ **局限**(两个会留下旧文件的场合,必须手工删):
|
|
205
|
+
1. **重命名项目后**:新项目名对应的清单(`.sapdon_generated_<新名>.json`)管不到旧名字的 `dev/<旧名>_*` 目录 → 旧目录整个残留,需**手工删除**。
|
|
206
|
+
2. **从未成功构建过的项目**没有清单 → 其陈旧文件也清不掉(清单不存在时 `readManifest` 返回空列表,`load.js:19`)。
|
|
207
|
+
|
|
208
|
+
### ★ 同步到游戏开发包 / `res/` → `dev/` 的「按清单 prune」(2026-09 新增,缺口 P1)
|
|
209
|
+
|
|
210
|
+
三条**互不重叠**的清单,各管一段路,**全都只删"自己上次记过的文件"**,一律不扫目录:
|
|
211
|
+
|
|
212
|
+
| 清单 | 位置 | 管什么 | 谁写的 |
|
|
213
|
+
|---|---|---|---|
|
|
214
|
+
| 生成物 | `dev/.sapdon_generated_<proj>.json` | 框架生成的 JSON 在 **`dev/`** 里的陈旧清理 | `load.js` |
|
|
215
|
+
| 部署物 | `dev/.sapdon_synced_<proj>.json` | **游戏开发包目录**(`development_behavior_packs` / `development_resource_packs`)里「上次部署过、这次 `dev/` 里已经没有」的文件 | `syncFiles.js` 的 `syncDevFilesServer()` |
|
|
216
|
+
| 资源 | `dev/.sapdon_res_<proj>.json` | **`dev/<proj>_RP/`** 里「上次从项目 `res/` 拷过、这次 `res/` 里已经没有」的文件 | `syncFiles.js` 的 `syncResourceFiles()` |
|
|
217
|
+
|
|
218
|
+
**症状(修之前)**:从项目里删掉的方块/物品/配方**永远留在玩家游戏里**,并持续报
|
|
219
|
+
`… not present in the Schema`;`res/` 删掉的资源同样残留 —— 因为同步/拷贝用的都是
|
|
220
|
+
`fs.cpSync(..., {recursive:true, force:true})` / `copyFolder`,**只合并、从不删除**。
|
|
221
|
+
|
|
222
|
+
**要点**
|
|
223
|
+
- 部署清单记的是「**上次实际部署过的文件全集**」,不是直接复用生成物清单:后者不含
|
|
224
|
+
`manifest.json` / `pack_icon.png` / 打包好的 `scripts/index.js` / `res/` 拷进来的资源,
|
|
225
|
+
而且它在同步**之前**就被本次构建覆写了(同步时已经读不到"上次")。
|
|
226
|
+
- **没有清单时(首跑 / 从未成功构建过的老项目)一个文件都不删**,只记录。
|
|
227
|
+
- 空包目录(`dev/<proj>_BP` 里一个文件都没有,正常总该有 `manifest.json`)视为**构建中断**,
|
|
228
|
+
跳过 prune 并 warn —— 免得把游戏里一份完好可用的包删空。
|
|
229
|
+
- HMR 路径(`hmr.js:37`)调的是同一个 `syncDevFilesServer()`,所以热更新同样会 prune。
|
|
230
|
+
- 单测:`node tests/sync-manifest.test.mjs`(临时目录 + `MC_PATH` 重定向,不需要真机)。
|
|
231
|
+
|
|
232
|
+
### `blocks.json` 的位置与内容(★ 2026-09 起**不再写方块条目**)
|
|
233
|
+
|
|
234
|
+
- **位置**:`blocks.json` 是**资源包(RP)**文件 → 框架写在 `dev/<proj>_RP/blocks.json`(`GRegistry.register("blocks","resource","",blocks_json)`)。
|
|
235
|
+
依据:[Bedrock Wiki · Pack Folder Structure](https://wiki.bedrock.dev/documentation/pack-structure) 的目录树把 `blocks.json` 列在 **RP** 根目录下(BP 侧没有 `blocks.json` 这个概念);[Bedrock Wiki · Block Sounds](https://wiki.bedrock.dev/blocks/block-sounds) 的示例标题即 `RP/blocks.json`。历史上框架曾把它误写在 BP,那里会被 Bedrock **完全忽略**;首次带清单构建会自动清掉 BP 侧那个**确切路径**的历史残留(`cleanLegacyBpBlocksJson()`,`load.js:34-41`)。
|
|
236
|
+
- ★ **内容:只有 `{"format_version": "1.20.20"}`,没有任何方块条目**(`blockFactory.js:34-59` 有完整依据)。
|
|
237
|
+
- 原因:曾经每个方块写一条 `{ "ns:name": { "textures": { up/down/... } } }`;键修成完整标识符后引擎真的匹配上了这些方块,
|
|
238
|
+
于是**每个自定义方块**报一条 `trying to override the Geometry component with blocks.json settings for a custom block`。
|
|
239
|
+
- 官方依据(Microsoft Learn · blocks.json File Reference):`minecraft:geometry` / `minecraft:material_instances`
|
|
240
|
+
**会覆盖** blocks.json 里的配置,官方推荐用组件写视觉,`blocks.json` 只当 **sound** 配置系统。
|
|
241
|
+
<https://learn.microsoft.com/en-us/minecraft/creator/reference/content/blockreference/examples/blocksjsonfilestructure>
|
|
242
|
+
- 自定义方块的贴图由 `minecraft:material_instances`(BP 方块 JSON)+ `terrain_texture.json` 提供,**不经过** blocks.json。
|
|
243
|
+
- **键格式的历史**(保留记录,供查旧产物;现在没有触发场景,对应的 `assertBlocksJsonKey()` 护栏**已删除**):
|
|
244
|
+
曾经的键必须是**完整标识符** `ns:name`(如 `"mob_chest:chest"`),不是 `ns_name`。
|
|
245
|
+
依据:[Bedrock Wiki · Block Sounds](https://wiki.bedrock.dev/blocks/block-sounds) 的示例键为 `"wiki:chestnut_log"`;
|
|
246
|
+
本仓库历史产物 `git show e1199cc:examples/mob_chest/dev/mob_chest_RP/blocks.json` 用的也是 `"mob_chest:chest"` / `"sapdon:falling_block"`。
|
|
247
|
+
`ns_name` 形态是把「**文件名安全名**」复用成 JSON 键的副产品 —— 文件名仍必须用 `_`
|
|
248
|
+
(`:` 在 Windows 文件名里非法),但它不再兼任任何 JSON 键。
|
|
249
|
+
- ⚠️ **未验证**:只含 `format_version` 的 `blocks.json` 引擎会不会抱怨;以及音效/贴图在游戏内是否正常
|
|
250
|
+
(音效本来就没配过 —— 框架从未写过 `sound` 字段,故预期与改动前一致)。
|
|
251
|
+
真机验证方式:重进世界看那批 `trying to override the Geometry component` 警告是否消失 + 挖掘/放置音效正常。
|
|
252
|
+
|
|
253
|
+
### 自定义组件注册索引合并(缺口 10/11)
|
|
254
|
+
|
|
255
|
+
`scripts/custom_components/index.js` 用**标记块**维护(`load.js:166-167`):
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
// >>> sapdon:custom-component-registry (auto-generated, 请勿手改本块) >>>
|
|
259
|
+
... 自动生成的 import + system.beforeEvents.startup 注册 ...
|
|
260
|
+
// <<< sapdon:custom-component-registry <<<
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
- **标记块之外的内容一律保留**(用户手写/模板占位都不会被覆盖);文件里没有生成段时**追加**而不是覆盖。
|
|
264
|
+
- **幂等**:连跑两次文件内容不变(`load.js:158-161`)。
|
|
265
|
+
- 生成块里用 `import { system as __sapdon_system }` 而不是裸 `system` —— 追加时文件里可能已有 `import { system }`,同名重复声明会让整个脚本包 rollup 报 `Identifier "system" has already been declared`(`load.js:80-82`, `SYSTEM_ALIAS`)。
|
|
266
|
+
- ⚠️ 这里**不能**改成「文件已存在就跳过」:索引在 ts 模板里是**预置占位文件**且被 `scripts/index.ts` import,「存在即跳过」会让注册索引**永远不更新**(`load.js:262-265`)。
|
|
267
|
+
|
|
156
268
|
---
|
|
157
269
|
|
|
158
270
|
## 4. 初始化模块
|
|
@@ -167,11 +279,17 @@ generateTextureFiles()
|
|
|
167
279
|
| `globalObject` | 全局可变对象,存储运行时状态 (projectPath) |
|
|
168
280
|
| `getProjectPath()` | 返回当前项目路径 |
|
|
169
281
|
| `getProjectName()` | 返回项目名 (目录 basename) |
|
|
170
|
-
| `getBuildDirBp()` | 返回 BP
|
|
171
|
-
| `getBuildDirRp()` | 返回 RP
|
|
282
|
+
| `getBuildDirBp()` | 返回 BP 构建目录路径(`<projectPath>/<buildDir>/<name>_BP`) |
|
|
283
|
+
| `getBuildDirRp()` | 返回 RP 构建目录路径(`<projectPath>/<buildDir>/<name>_RP`) |
|
|
172
284
|
|
|
173
285
|
模板映射:`{ js: 'js_sapdon', ts: 'ts_sapdon' }`,对应 `src/templates/` 下的目录。
|
|
174
286
|
|
|
287
|
+
### 构建子目录名统一大写 `_BP` / `_RP`
|
|
288
|
+
|
|
289
|
+
`getBuildDirBp()` / `getBuildDirRp()` 固定拼出**大写**的 `_BP` / `_RP`(`src/cli/init.js:105-114`)。
|
|
290
|
+
|
|
291
|
+
⚠️ 历史上这里是**小写** `_bp` / `_rp`,而 `build.js`、`load.js`、`syncFiles.js`、`pack.js` 一律用大写 —— Windows 文件系统大小写不敏感才侥幸能跑;**Linux/macOS 下会分叉成两个目录**(构建写进 `X_bp`、打包读 `X_BP` → **空包**)。全框架现在只保留大写这一种形态,不要改回小写。
|
|
292
|
+
|
|
175
293
|
---
|
|
176
294
|
|
|
177
295
|
## 5. 开发服务器
|
|
@@ -185,11 +303,13 @@ generateTextureFiles()
|
|
|
185
303
|
┌─────────────────────────┐ ┌──────────────────────────┐
|
|
186
304
|
│ │ │ │
|
|
187
305
|
│ GRegistry │ │ DevelopmentServer │
|
|
188
|
-
│ .register() │ POST │ (端口
|
|
189
|
-
│ .submit() │─────→│
|
|
190
|
-
│ │ │
|
|
191
|
-
│ core/transport/client.ts│ │
|
|
192
|
-
│ transportPost() │ │
|
|
306
|
+
│ .register() │ POST │ (端口 SAPDON_DEV_SERVER_│
|
|
307
|
+
│ .submit() │─────→│ PORT,默认 49037) │
|
|
308
|
+
│ │ │ │
|
|
309
|
+
│ core/transport/client.ts│ │ cliServerHandlers: │
|
|
310
|
+
│ transportPost() │ │ submitGregistry │
|
|
311
|
+
│ │ │ submit │
|
|
312
|
+
│ │ │ remote-logger │
|
|
193
313
|
└─────────────────────────┘ └──────────────────────────┘
|
|
194
314
|
```
|
|
195
315
|
|
|
@@ -223,10 +343,25 @@ HTTP POST /<url>
|
|
|
223
343
|
|
|
224
344
|
### `config.js` — 配置
|
|
225
345
|
|
|
346
|
+
端口从环境变量 **`SAPDON_DEV_SERVER_PORT`** 读取,默认 `49037`(`src/cli/dev-server/config.js:11-20`):
|
|
347
|
+
|
|
226
348
|
```javascript
|
|
227
|
-
|
|
349
|
+
const DEFAULT_PORT = 49037
|
|
350
|
+
// 非整数 / ≤0 / 未设置 → 回落默认端口
|
|
351
|
+
export const devServerConfig = { port: resolvePort() }
|
|
228
352
|
```
|
|
229
353
|
|
|
354
|
+
| 事项 | 说明 |
|
|
355
|
+
|------|------|
|
|
356
|
+
| 默认端口 | `49037` |
|
|
357
|
+
| 覆盖方式 | 环境变量 `SAPDON_DEV_SERVER_PORT` |
|
|
358
|
+
| ★ 一致性要求 | 服务端(`dev-server/config.js`)与客户端(`src/core/transport/client.ts`)**必须解析同一个变量** |
|
|
359
|
+
| 端口被占用 | 打印「端口已被其他 sapdon 进程占用」并 **`process.exit(1)`**(`dev-server/server.ts:56-64`,判据是 `EADDRINUSE`) |
|
|
360
|
+
|
|
361
|
+
⚠️ **静默失联**:如果只有一侧读这个变量,就会出现「**客户端 POST 到 A 端口、服务端监听 B 端口**」——两边都不报错,表现为**构建产物莫名不更新**(注册数据发到了没人听的端口)。改端口相关代码时两处必须同步改。
|
|
362
|
+
|
|
363
|
+
用途:多个 sapdon 构建并行(多 agent / 多项目同时构建)时靠它避开 `EADDRINUSE`,各设各的端口即可。
|
|
364
|
+
|
|
230
365
|
---
|
|
231
366
|
|
|
232
367
|
## 6. 热更新 (HMR)
|
|
@@ -303,6 +438,7 @@ interface BuildConfig {
|
|
|
303
438
|
formatVersion: number // 当前是 2
|
|
304
439
|
buildOptions: {
|
|
305
440
|
useHMR: boolean // 热更新
|
|
441
|
+
keepServer?: boolean // 构建后是否保持开发服务器常开(默认 false → 自动退出)
|
|
306
442
|
buildMode: 'dev' | 'prod' | 'debug' // 构建模式
|
|
307
443
|
buildEntry: string // main.ts 入口
|
|
308
444
|
scriptEntry: string // scripts/main.ts
|
|
@@ -447,7 +583,7 @@ load.js (JSON 文件生成)
|
|
|
447
583
|
├── tools/textureSet.js → 纹理图集
|
|
448
584
|
└── registryServer.js → GRegistryServer
|
|
449
585
|
|
|
450
|
-
dev-server/ (IPC
|
|
586
|
+
dev-server/ (HTTP 传输层 —— 全框架不使用 IPC)
|
|
451
587
|
├── server.ts ←→ client.js (HTTP 通信)
|
|
452
588
|
├── hmr.js → build.js + syncFiles.js
|
|
453
589
|
└── syncFiles.js → meta/versionType.js + meta/package.js
|
package/doc/dev/core.md
CHANGED
|
@@ -685,7 +685,8 @@ UIElement (基类: name, type, template, control, layout, properties)
|
|
|
685
685
|
| `UISystem` | `systems/system.js` | 核心 UI 文件系统,管理 elements + animations |
|
|
686
686
|
| `SapdonServerUI` | `systems/sapdon/sapdonServerUI.ts` | 页面壳路由系统,生成 `server_form.json` |
|
|
687
687
|
| `ChestUISystem` | `systems/chest.js` | 容器 UI 系统 |
|
|
688
|
-
| `ContainerUISystem` | `systems/containerUISystem.
|
|
688
|
+
| `ContainerUISystem` | `systems/containerUISystem.ts` | 自定义容器 UI(绝对像素版面 + 槽位声明) |
|
|
689
|
+
| `containerLayout`(纯函数) | `systems/containerLayout.ts` | 槽号 ↔ `grid_position`、像素 `pos` ↔ 格位 `offset`、槽位声明校验;零 import,可离线单测 |
|
|
689
690
|
| `SapdonGuideBook` | `systems/sapdon/sapdonGuideBook.ts` | 数据驱动手册框架类,详见 [guidebook.md](../guidebook.md) |
|
|
690
691
|
| `HudUISystem` | `systems/hud/hud.ts` | HUD 系统 |
|
|
691
692
|
| `HudStatePanel` | `systems/hud/hudElement.ts` | HUD 状态面板 |
|