From c05c96d5f11c0ff482fcb0c1fe950e7c6d499a03 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Mon, 19 Oct 2026 20:50:34 +0800 Subject: [PATCH] docs(09-21): plan build refactor for new engine --- .../09-minimum-browser-runnable/09-21-PLAN.md | 430 ++++++++++++++++++ 1 file changed, 430 insertions(+) create mode 100644 .planning/phases/09-minimum-browser-runnable/09-21-PLAN.md diff --git a/.planning/phases/09-minimum-browser-runnable/09-21-PLAN.md b/.planning/phases/09-minimum-browser-runnable/09-21-PLAN.md new file mode 100644 index 0000000..6421976 --- /dev/null +++ b/.planning/phases/09-minimum-browser-runnable/09-21-PLAN.md @@ -0,0 +1,430 @@ +--- +phase: 09-minimum-browser-runnable +plan: 21 +type: execute +wave: 18 +depends_on: [] +files_modified: + - script/vite-plugin-data-isolation.ts + - script/vite-plugin-data-isolation.test.ts + - script/build-resource.ts + - script/build-resource.test.ts + - script/build-game.ts + - script/build-packages.ts + - script/build-lib.ts + - package.json + - index.html + - src/bootstrap.ts + - .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md +autonomous: false +requirements: [] +estimate: + tokens: 60000 + raw_tokens: 30000 + tasks: 3 + confidence: low +must_haves: + truths: + - "**本计划的第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md 硬要求,D-03 / D-93)**:Task 0 必须先汇报「本计划 = 任务 11 · 构建重构(适配新引擎)」的**要做什么 / 为什么 / 大致怎么做 / 影响范围**,**必须列明:D-93 脚本重构与删除清单、D-94 数据端/渲染端分离插件的扫描口径与报错终止行为(取代 `checkClientData` 体积启发式)、D-95 数据端库模式 ESM 产物 `dist/data/data.js`、D-96 `src/content` → `dist/content` 的 jsonc 压缩(**必须保留 `// --- SYSTEM PREFIX END --- //` 标记行**)/ fontmin / 不分块 / 不 zip、D-97 的 dist 产物排除清单与 `LICENSE` 保留、`index.html` 去旧样式表 link、D-98 `package.json` script 调整、D-99 保留 `vue-tsc --noEmit`、D-100 移除二次压缩并微调 `manualChunks`、**全部拟新增命名(OI-1 / OI-2)**、写操作边界、验证门禁方式、已知阻断(用户 WIP 导致全量构建可能失败)与 `open_items`(OI-1..OI-10)**,然后必须**停下等待**用户回复;**回复为「可以执行」即视为通过(本计划不因触发方式或 `mode: yolo` / `workflow.auto_advance` 自动放行)**;用户若对任一 `open_items` 给出指定,按指定执行。" + - "**D-93 / D-98 脚本重构与删除:** 重构 `script/build-game.ts` 与 `script/build-resource.ts`;**删除** `script/build-packages.ts` 与 `script/build-lib.ts`;从 `package.json` 的 `scripts` 中删除 `build:packages` / `build:lib`;`script/dev.ts` **不改**;`build:game` 保留(按 OI-6 调整其前置链,仍保留 `vue-tsc --noEmit`)。" + - "**D-94 数据端独立包不得引用渲染端(取代 `checkClientData`):** 新增 Vite 插件在**数据端构建**时扫描模块图;命中渲染端模块(`@user/client-*`、`@user/entry-client`、`@motajs/render*`、`@motajs/client*`、`@motajs/system`、`@motajs/audio`、`@motajs/legacy-*`、`vue`、`vue/*`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue`,以及对应的 `packages-user/client-*` / `packages/render*` / `packages/system` / `packages/audio` / `packages/legacy-*` / `node_modules/vue|@vue|ant-design-vue|@ant-design` 解析路径)即 `this.error(...)` **报错并终止**构建;数据端自身依赖(`@user/data-*`、`@motajs/common`、`@motajs/loader`、`@motajs/types`、`@motajs/animate`、`anon-tokyo` 等)**不得**误判。**`checkClientData`、`getFileName`、`getFileSize`、`ClientDataLevel` 一并删除**。" + - "**D-95 数据端产物:** 数据端构建改为 **Vite 库模式 + ESM**(`formats: ['es']`,不使用 iife),入口 `src/data.ts`,输出 `dist/data/data.js`;不生成 `data.iife.js` / `data.process.js`。" + - "**D-96 content 处理:** 构建后 `src/content` → `dist/content`(路径去掉 `src` 前缀);`*.jsonc` 以 `JSON.parse + JSON.stringify` 压缩(去注释、**后缀仍为 `.jsonc`**);**压缩后必须保留且仅保留一行 `// --- SYSTEM PREFIX END --- //` 标记**,其后为压缩后的 JSON(理由:`PrefixedJSONCProcessor` 以该标记 `split` 且要求恰好 2 段,见 `packages-user/data-state/src/loader/jsoncProcessor.ts:14-18`;丢失标记会在运行期触发 error 66 并解析为 `{}`);字体用 fontmin(glyph 子集)压缩;**不分块**(不生成 `resource..h5data`)、**不 zip**。`build-resource` 改为从 `src/content` 读取素材。" + - "**D-97 旧模板内容与 dist 产物:** 不复制 `public/**`(`copyPublicDir: false`,`public` 整体删除由用户执行,本计划不删);不产出 `editor.html`、`_server`、`_docs`、`libs`、`logo.png`、`styles.css`、`main.js`、`script/template/启动服务.exe`;不打包 `source-code.zip`;不生成 `loadList.json`;不生成 `dist.zip`;**保留 `LICENSE`(LGPL v3)并复制到 `dist/LICENSE`**;`index.html` 去掉旧 ``,样式改由入口经 JS 打包引入(落点见 OI-5)。" + - "**D-99 / D-100:** 构建仍前置 `vue-tsc --noEmit`(保留于 `build:game`);移除 `main.js` 二次压缩(babel)步骤与相关依赖引用;`manualChunks` 按实际依赖微调——`audio` chunk(`codec-parser` / `opus-decoder` / `ogg-opus-decoder` / `@wasm-audio-decoders/ogg-vorbis`)保留,`antdv` chunk 移除(全仓源码无 `ant-design-vue` / `@ant-design/icons-vue` 导入);`@vitejs/plugin-legacy` 维持现状(目标浏览器列表不变)。" + - "**写入边界(D-56 / AGENTS.md):** 只允许写 `files_modified` 所列路径;**不回退**用户既有改动(`index.html`、`packages-user/client-modules/src/**`、`src/**` 等 WIP);除 OI-5 获批后的 `src/bootstrap.ts` **一行样式 import** 外,**不改 `src/**`**;不改数据端/渲染端源码、`public/**`、`vite.config.ts`、lockfile、`.planning/ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 既有 PLAN 与 SUMMARY。" + - "**命名须先获批(AGENTS.md / D-04):** 新增文件 `script/vite-plugin-data-isolation.ts`、导出 `dataIsolationPlugin`、测试文件 `script/vite-plugin-data-isolation.test.ts`(OI-1);`script/build-resource.ts` 新导出 `buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` 与测试文件 `script/build-resource.test.ts`(OI-2)。未获批前不得落码(测试文件按 AGENTS.md 免审批,但仍列入报备)。" + - "**验证可运行性(Nyquist):** Task 1 以**真实 Vite `build()`** 驱动插件单测(渲染端 specifier / 渲染端解析路径分别被拒;纯数据端通过并输出 ESM);Task 2 以 `minifyJSONC` / `buildContent` 单测 + 构建脚本结构断言 + `git status` 范围门禁兜底;`pnpm exec tsx script/build-game.ts` **尽力实跑**,若因用户 WIP(`src/bootstrap.ts` 仍指向已移动的 UI 文件等)失败,必须记录完整错误与归因,**不得**为通过门禁而修改 `src/**` 或包源码。" + artifacts: + - path: script/vite-plugin-data-isolation.ts + provides: "新增 Vite 插件 `dataIsolationPlugin`:`enforce: 'pre'` + `resolveId` 拦渲染端 specifier,`moduleParsed` 按解析后绝对路径拦渲染端包(含传递依赖),命中即 `this.error(...)` 终止数据端构建(命名待批准,OI-1)" + - path: script/vite-plugin-data-isolation.test.ts + provides: "运行期单测:真实 `vite.build()`(`configFile:false`、临时目录 fixture、`write:false`)验证「渲染端 specifier 被拒」「渲染端解析路径被拒」「纯数据端通过且产物为 ESM」「数据端路径不误判」;每个 `it` 前一行注释(AGENTS.md)" + - path: script/build-resource.ts + provides: "改为 content 处理模块:`minifyJSONC`(保留 `// --- SYSTEM PREFIX END --- //` 标记 + `JSON.parse/stringify` 去注释)、`buildContent`(递归 `src/content` → `dist/content`:jsonc 压缩、fontmin glyph 子集压缩字体、其它素材原样拷贝;无字体二进制时跳过并告警)与结果/选项接口(命名待批准,OI-2)" + - path: script/build-resource.test.ts + provides: "运行期单测:`minifyJSONC` 保留标记且压缩、去注释、非法 JSON 抛错、无标记纯 JSON 处理;`buildContent` 在临时目录完成 jsonc 压缩 + PNG 原样拷贝 + 无字体时跳过;每个 `it` 前一行注释" + - path: script/build-game.ts + provides: "重写编排:渲染端构建到 `dist`(`copyPublicDir:false`、`manualChunks` 微调、移除 babel/zip/资源地图/资源分块/旧模板拷贝)→ 收集字体字符 → 数据端 ESM 库构建到 `dist/data`(注入 `dataIsolationPlugin`)→ `buildContent` 处理 `dist/content` → 复制 `LICENSE` → 输出体积信息;删除 `checkClientData` 等旧逻辑" + - path: script/build-packages.ts + provides: "**删除**(D-93 / D-98)" + - path: script/build-lib.ts + provides: "**删除**(D-93 / D-98)" + - path: package.json + provides: "删除 `build:packages` / `build:lib`;`build:game` 保留 `vue-tsc --noEmit`(按 OI-6 处理 `declare.ts` 前置)" + - path: index.html + provides: "移除旧样式表 link(保留其余 head/body 内容与 `src/bootstrap.ts` module script)" + - path: src/bootstrap.ts + provides: "**仅当 OI-5 获批**:新增一行 `import './styles.less';`,使 D-97「样式走 JS 打包」成立;未获批则不改" + - path: .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md + provides: "本计划执行摘要:范围、插件扫描口径与错误行为、content 压缩口径(含标记保留这一兼容性约束)、dist 产物清单与排除项、删除清单、`open_items` 最终处置、门禁结果与全量构建阻断归因" + key_links: + - "`dataIsolationPlugin`(`script/vite-plugin-data-isolation.ts`)↔ `buildData` 的 `plugins` 数组(`script/build-game.ts`)—— D-94 的唯一执行点;取代 `checkClientData` 体积启发式" + - "`buildData` 的 `build.lib.fileName: 'data'` + `formats: ['es']` ↔ `dist/data/data.js` —— D-95 产物契约" + - "`minifyJSONC` 的标记保留 ↔ `PrefixedJSONCProcessor.process`(`packages-user/data-state/src/loader/jsoncProcessor.ts:14-18`,`split(prefixSplit)` 要求恰好 2 段)—— 丢失标记会导致运行期 error 66 / `{}`" + - "`dist/content/**` ↔ `MotaDataLoader.resolveURL`(`packages-user/data-state/src/loader/loader.ts:74-82`,生产态为 `${BASE_URL}${url}`)与 `src/constant.ts` 的 `CORE_URL='content/core.jsonc'` / `CLIENT_CORE_URL='content/client.jsonc'` —— 目录落点契约" + - "`buildClient` 的 `copyPublicDir: false` ↔ D-97 —— 阻止旧 `public/**` 泄入 `dist`" + - "`package.json` 的 `build:game` ↔ `script/build-game.ts` / `vue-tsc --noEmit` —— D-98 / D-99" + - "`index.html` 去 link ↔ `src/bootstrap.ts` 的 `import './styles.less'`(OI-5)—— D-97 样式链路" + assumptions: + - "**content 全部为「前缀注释 + 严格 JSON」:** 实测 `src/content/**/*.jsonc` 共 20 个文件均含 `// --- SYSTEM PREFIX END --- //`(含 `core.jsonc` / `client.jsonc` / `defaults/*` / `maps/*.jsonc` / `maps/.list.jsonc` / `texture/.list.jsonc` / `animation/.list.jsonc` / `audio/.list.jsonc` / `font/.list.jsonc`);`PrefixedJSONCProcessor` 要求标记恰好出现一次,故压缩必须保留该行。" + - "**生产态 content URL 以 `BASE_URL` 为根:** `MotaDataLoader.resolveURL` 在 `import.meta.env.DEV === false` 时返回 `${BASE_URL}${url}`;核心配置传 `content/core.jsonc` / `content/client.jsonc`,额外配置传 `content/${url}`,故 `dist/content/**` 是唯一正确落点。" + - "**字体目录当前无字体二进制:** `src/content/font/` 仅有 `.list.jsonc`(内容 `[\"testFont.ttf\"]`),`public/project/fonts/` 不存在;fontmin 无源可压,计划默认**跳过并告警**(OI-4),不得因缺字体阻断构建。" + - "**`src/content/item.ts` 是源码而非运行时素材:** `core.jsonc` 的 `itemEffectDir` 指向 `item.js`,但 `src/content` 中只有 `item.ts` 且当前无消费者(用户 WIP);本计划按 D-96 原样复制 content 目录,不改该数据/不改加载器(OI-8)。" + - "**全量构建当前可能被用户 WIP 阻断:** `src/bootstrap.ts` 仍从 `../packages-user/client-modules/src/ui/load` 导入,而该文件在工作树中已被移动到 `src/client/ui/`(`git status` 显示删除 + 新增目录);因此 `tsx script/build-game.ts` 的渲染端构建可能失败。这是**用户 WIP**,本计划**不得**为让其通过而修改 `src/**`;门禁以插件/内容单测 + 结构断言为主,全量构建结果如实记录(OI-9)。" + - "**`ant-design-vue` 已不再被源码引用:** 对 `packages/`、`packages-user/`、`src/` 的源码检索(排除 `node_modules`)未发现 `ant-design-vue` / `@ant-design/icons-vue` 导入;`audio` 系依赖仍由 `@motajs/audio` 使用(`codec-parser` / `ogg-opus-decoder`)。" + - "**`vite.config.ts` 提供 `base: './'` 与 `@motajs/*` / `@user/*` 别名:** 数据端构建复用 `loadConfigFromFile` + `mergeConfig`,因此插件可在 `enforce: 'pre'` 阶段先拦截未解析 specifier,再由 `moduleParsed` 按解析后路径兜底。" + - "**构建顺序安全:** 渲染端构建先执行并 `emptyOutDir` 清空 `dist`;数据端构建 `emptyOutDir:false` 写入 `dist/data`;content 最后写入 `dist/content`;`LICENSE` 最后复制。" + - "**行号可能漂移:** 本计划引用行号为规划日实测;门禁以「文件存在 + 结构/行为断言」为准,不硬编码行号。" + prohibitions: + - "不得在用户回复通过 Task 0 关卡之前执行本计划的任何任务、或派发任何执行子代理。" + - "**未经批准的命名一律不得使用:** `script/vite-plugin-data-isolation.ts` / `dataIsolationPlugin` / `script/vite-plugin-data-isolation.test.ts`(OI-1)与 `buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` / `script/build-resource.test.ts`(OI-2)必须先经 Task 0 报备并获批准;未获批前不得落码。" + - "**content 压缩必须保留标记行:** 不得只输出 `JSON.stringify` 结果而丢掉 `// --- SYSTEM PREFIX END --- //`;不得改后缀(仍为 `.jsonc`)。此为本计划对 D-96 的**兼容性补充**(OI-3),不是范围扩张。" + - "**不得删除 `public/**`:** D-97 的「整体删除」由用户执行;本计划只通过 `copyPublicDir: false` 阻止其进入 `dist`。" + - "**写入边界(D-56):** 只允许写 `files_modified`;`src/bootstrap.ts` **仅允许**在 OI-5 获批后新增一行样式 import;不得改 `vite.config.ts`、`packages/**`、`packages-user/**`、`public/**`、`script/dev.ts`、`script/declare.ts`、`script/types.ts`、`script/utils.ts`、lockfile;不得回退用户在工作树中的任何既有改动。" + - "**`.planning/**` 边界:** 不得改 `ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 任何既有 PLAN 或 SUMMARY;只允许新增 `09-21-SUMMARY.md`(与 09-19 / 09-20 的既有做法一致)。" + - "**不新增依赖:** 不得改 `package.json` 的 `dependencies` / `devDependencies`、不得改 lockfile;`vite-plugin-dts` 虽因 `build-lib.ts` 删除而失去使用点,也**不删除**(OI-10)。" + - "**不得改既有注释 / jsDoc(AGENTS.md):** 重写 `build-game.ts` / `build-resource.ts` 时保留/更新其内部注释属重写范围,但不得改动其它文件的既有注释;新增注释须遵循 dev.md。" + - "**不得以全仓门禁代替:** 不设 `pnpm check:type` / `pnpm build` / 全量 `pnpm test:ci` 作为通过条件(用户 WIP 存在历史类型错误)。" + - "**不得为通过门禁而修改范围外文件:** 全量构建若因 `src/**` / 包源码的用户 WIP 失败,只能记录归因,不得修 `src/**` 或包源码。" + open_items: + - "**OI-1(插件文件与导出命名):** 提议新增 `script/vite-plugin-data-isolation.ts`,导出 `dataIsolationPlugin(): Plugin`;测试文件 `script/vite-plugin-data-isolation.test.ts`。备选命名:`script/build-plugin-data-isolation.ts` / `forbidRenderDependencies` / `renderIsolationPlugin`。**待用户报备批准**(D-04;测试文件按 AGENTS.md 免审批,但仍一并报备)。" + - "**OI-2(content 模块命名):** 提议 `script/build-resource.ts` 导出 `buildContent(options: IContentBuildOptions): Promise`、`minifyJSONC(text: string): string`,接口 `IContentBuildOptions`(`sourceDir` / `outputDir` / `chars`)与 `IContentBuildStats`(`files` / `jsonc` / `fonts`);测试文件 `script/build-resource.test.ts`。备选:`processContent` / `compressJSONC`。**待批准**(dev.md 要求对象入参声明 interface)。" + - "**OI-3(jsonc 标记保留与 `Type:` 注释丢失):** 提议压缩输出 = `// --- SYSTEM PREFIX END --- //` 一行 + 压缩 JSON;`// Type: ...` 等标记前注释**全部丢失**(含 `texture/.list.jsonc` 的类型指针)。D-96 说「去注释」,但加载器要求保留标记,故标记必须保留;`Type:` 指针丢失是否可接受**请用户确认**。" + - "**OI-4(字体来源):** `src/content/font/` 仅 `.list.jsonc`(引用 `testFont.ttf`),无字体二进制;提议「无字体文件时跳过 fontmin 并告警」。若用户要求压缩特定字体,请指定字体文件落点。" + - "**OI-5(样式引入落点):** D-97 要求样式走 JS 打包,但当前 `src/styles.less` 无任何导入点。提议在 `src/bootstrap.ts` 顶部新增一行 `import './styles.less';`(该文件是入口、已有顶层执行语句,故不受 dev.md「模块无副作用」约束)。因 `src/**` 属用户 WIP 边界,**须批准**。备选:不改 `src/**`,接受 `#game-draw` 居中样式缺失(记入 SUMMARY)。" + - "**OI-6(`build:game` 前置链):** 现 `build:game` = `tsx script/declare.ts && vue-tsc --noEmit && tsx script/build-game.ts`,而 `script/declare.ts` 读取 `public/project/*`(D-97 将删除 `public/**`)。提议改为 `vue-tsc --noEmit && tsx script/build-game.ts`(保留 D-99 的类型检查),**不删除** `script/declare.ts` 文件。**待确认**。" + - "**OI-7(`manualChunks` / legacy 插件):** 提议移除 `antdv` chunk、保留 `audio` chunk、`@vitejs/plugin-legacy` 维持现状(含目标浏览器列表与 `renderModernChunks:false`)。若用户希望移除 legacy 或调整目标,请在 Task 0 指明。" + - "**OI-8(`itemEffectDir` 缺文件):** `core.jsonc` 引用 `item.js`,`src/content` 仅有 `item.ts` 且无消费者。本计划原样复制 content,不生成 `item.js`、不改数据;**请确认该缺口由用户后续处理**。" + - "**OI-9(全量构建的已知阻断):** 用户 WIP 使 `src/bootstrap.ts` 指向已移动的 UI 文件,`tsx script/build-game.ts` 可能失败。提议:主门禁 = 插件单测 + content 单测 + 结构断言;全量构建**尽力实跑**,失败则记录完整错误与归因,不修改 `src/**`。**待确认**。" + - "**OI-10(遗留依赖与文件):** `script/types.ts` 的 `RequiredData` / `RequiredIconsData` 及 `vite-plugin-dts`、`@babel/*`、`archiver`、`compressing` 等依赖在重构后可能失去使用点,但**不删除**(避免 lockfile/依赖面变更)。**请确认**保留。" +--- + + +Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎)**。 + +**要解决的问题:** 现有 `script/build-game.ts` / `script/build-resource.ts` 仍面向**旧引擎 + 旧模板**:构建产物依赖 `public/project`(旧 `data.js` / `icons.js` / `floors/` / `materials/` 等)、数据端产出 iife、按体积启发式 `checkClientData` 猜测「数据端是否引用了渲染端」、`main.js` 二次 babel 压缩、资源按 2MB 分块并 zip 成 `resource..h5data`、拷贝 `editor.html` / `_server` / `_docs` / `libs` / `logo.png` / `styles.css` / `main.js` / `启动服务.exe`、打包 `source-code.zip`、生成 `loadList.json`、再整体压成 `dist.zip`。新引擎的数据落在 `src/content`(jsonc + 贴图 + 字体目录),加载器按 `content/**` 相对路径读取,渲染端/数据端已分家。继续沿用旧脚本既无法跑通新数据,也无法保证「数据端独立包不得引用渲染端」。 + +**目标(D-93..D-100):** +1. **D-94** 用 Vite 插件扫描数据端模块图,命中渲染端模块即报错终止,**取代** `checkClientData`。 +2. **D-95** 数据端改为库模式 ESM,入口 `src/data.ts`,产物 `dist/data/data.js`。 +3. **D-96** `src/content` → `dist/content`:jsonc 压缩(**保留系统标记行**)、fontmin 压缩字体、**不分块 / 不 zip**;`build-resource` 改为从 `src/content` 读取。 +4. **D-97** dist 不再携带旧模板内容;保留 `LICENSE`;`index.html` 去掉旧样式表 link,样式走 JS 打包。 +5. **D-93 / D-98** 删除 `build-packages.ts` / `build-lib.ts` 及对应 npm script;`build:game` 调整并保留 `vue-tsc --noEmit`(D-99)。 +6. **D-100** 移除 `main.js` 二次压缩;`manualChunks` 按实际依赖微调(`antdv` 已无源码引用 → 移除;`audio` 保留);legacy 插件维持现状。 + +**关键兼容性约束(本计划对 D-96 的实现补充):** 所有 `src/content/**/*.jsonc` 都是「前缀注释 + 严格 JSON」,运行期由 `PrefixedJSONCProcessor` 以 `// --- SYSTEM PREFIX END --- //` **split** 并要求恰好 2 段(`packages-user/data-state/src/loader/jsoncProcessor.ts:14-18`)。因此「`JSON.parse + JSON.stringify` 去注释」**必须保留该标记行**;否则运行期触发 error 66、配置解析为 `{}`。`// Type: ...` 等标记前注释会按 D-96 丢失(OI-3 待确认)。 + +**大致解决方案(本计划 = 09-21):** +- **Task 0(`checkpoint:decision@blocking-human`)= 预执行汇报关卡**:汇报要做什么 / 为什么 / 大致怎么做 / 影响范围(含插件扫描口径、content 压缩口径与标记保留理由、dist 产物清单、删除清单、`manualChunks` 调整、命名报备、验证方式、已知阻断),停下等待「可以执行」。 +- **Task 1(`tracer`)= 分离插件端到端最小切片**:新增 Vite 插件并在**真实 `vite.build()`** 的临时 fixture 上验证——渲染端 specifier 被拒、渲染端解析路径(含传递依赖)被拒、纯数据端通过且产物为 ESM、数据端路径不误判。这是本增量唯一的全新机制,先用运行期测试把「扫描 + 终止」跑通。 +- **Task 2 = 构建编排重写与收口**:重写 `build-resource.ts`(content 压缩/拷贝/字体)与 `build-game.ts`(渲染端 → 数据端 ESM(注入插件)→ content → LICENSE);删除旧脚本;调整 `package.json` 与 `index.html`(OI-5 获批则加样式 import);补 `build-resource.test.ts`;尽力实跑全量构建并做产物门禁 / 范围门禁;写 `09-21-SUMMARY.md`。 + +**为何 `depends_on: []`:** 本计划只改构建脚本与其直接产物契约,不依赖 09-19 / 09-20 的源码改动;它依赖的是**已存在**的数据端加载器、`src/content` 数据与 `src/data.ts` 入口(均已就位)。因此可与既有计划解耦,独占 wave 18。 + +**API coverage:** `detected:false`。**`No external API integration: 本计划为本地构建流程重构,不涉及任何外部 API。`** + +**修改方式声明(AGENTS.md):** 本计划执行含**文件写入与删除**(新增插件与测试、重写两个构建脚本、删除两个旧脚本、改 `package.json` / `index.html`、条件性改 `src/bootstrap.ts`、写 SUMMARY),将由**派发子代理(`gsd-executor`)执行**,非内联执行;除 `files_modified` 所列路径外无任何写入。 + + +@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md +@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/09-minimum-browser-runnable/09-CONTEXT.md +@.planning/phases/09-minimum-browser-runnable/09-20-PLAN.md +@AGENTS.md +@dev.md + + + + + + Task 0: 预执行汇报关卡 —— 汇报「任务 11 · 构建重构(适配新引擎)」的要做什么 / 为什么 / 大致怎么做 / 影响范围,等待用户回复「可以执行」 + `script/build-game.ts`(703 行)与 `script/build-resource.ts`(225 行)仍为旧引擎实现;`script/build-packages.ts` 与 `script/build-lib.ts` 存在;`package.json` scripts 含 `build:packages` / `build:lib` / `build:game`(`build:game` 含 `tsx script/declare.ts` 与 `vue-tsc --noEmit`);`index.html` 含 ``;`src/content/**` 共 20 个 `.jsonc`(全部含 `// --- SYSTEM PREFIX END --- //`)与 `texture/*.png`;`src/content/font/` 无字体二进制;`src/data.ts` 存在且导出 `createCoreState`;`script/vite-plugin-data-isolation.ts` / `script/vite-plugin-data-isolation.test.ts` / `script/build-resource.test.ts` **尚不存在**;`src/bootstrap.ts` 仍指向已移动的 `packages-user/client-modules/src/ui/load`(用户 WIP)。 + (只读汇报,不修改任何文件) + + - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(**D-93 / D-94 / D-95 / D-96 / D-97 / D-98 / D-99 / D-100**;D-03 / D-04 / D-56) + - script/build-game.ts(全文:`buildClient:40` / `buildData:91` / `checkClientData:212` / `getAllChars:254` / `generateResourceJSON:310` / `buildGame:329`,含 babel、archiver、compressing、资源分块、旧模板拷贝、`source-code.zip`、`loadList.json`、`启动服务.exe`、`dist.zip`) + - script/build-resource.ts(全文:`splitResource:123` 从 `public/project/{animates,fonts,images,sounds,tilesets,autotiles,materials}` 读取并按 2MB 分块 zip 成 `resource..h5data`) + - script/build-packages.ts / script/build-lib.ts(待删除);script/types.ts / script/utils.ts(`formatSize`) + - package.json(只读:scripts、依赖面);index.html(只读:`` 与 `/src/bootstrap.ts` module script) + - src/data.ts / src/data/entry.ts / src/client/entry.ts / src/constant.ts(`CORE_URL='content/core.jsonc'`、`CLIENT_CORE_URL='content/client.jsonc'`) + - packages-user/data-state/src/loader/loader.ts:70-142(`resolveURL` 生产态 = `${BASE_URL}${url}`)与 packages-user/data-state/src/loader/jsoncProcessor.ts:4-28(标记 split 必须恰好 2 段) + - src/content/**(`client.jsonc` / `core.jsonc` / `defaults/*` / `maps/*` / `texture/.list.jsonc` / `font/.list.jsonc` / `item.ts`)与 src/styles.less + - vite.config.ts(`base: './'`、`@motajs/*` / `@user/*` / `@dp/*` 别名);`git status`(用户 WIP 面) + - AGENTS.md(执行前须获批、命名须先反馈、修改方式须预先声明、禁改既有注释);dev.md(命名规则、对象类型须声明 interface、模块无副作用、非空判断约定、jsDoc 规范) + + + **停下并向用户汇报,等待用户回复「可以执行」后才继续;汇报本身只读。** + + ① **要解决的问题:** 旧构建脚本面向旧引擎与旧模板(依赖 `public/project`、数据端 iife、体积启发式、`main.js` 二次压缩、资源分块 zip、旧模板拷贝、源码 zip、`loadList.json`、`dist.zip`),无法适配新引擎的 `src/content` 数据与双端分离。 + + ② **范围(D-93):** 重构 `script/build-game.ts` 与 `script/build-resource.ts`;删除 `script/build-packages.ts` 与 `script/build-lib.ts`;`script/dev.ts` 不改。 + + ③ **D-94 分离插件:** 新增 Vite 插件,在数据端构建时扫描**解析后的模块图**:`enforce: 'pre'` 的 `resolveId` 先拦渲染端 specifier(`@user/client-*`、`@user/entry-client`、`@motajs/render*`、`@motajs/client*`、`@motajs/system`、`@motajs/audio`、`@motajs/legacy-*`、`vue`、`vue/*`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue`),`moduleParsed` 再按绝对路径兜底(`packages-user/client-*`、`packages/render*`、`packages/system`、`packages/audio`、`packages/legacy-*`、`node_modules/{vue,@vue,ant-design-vue,@ant-design}`),命中即 `this.error(...)` **终止构建**;数据端自身依赖不误判。删除 `checkClientData` / `getFileName` / `getFileSize` / `ClientDataLevel`。 + + ④ **D-95 数据端:** 库模式 + `formats: ['es']`,入口 `src/data.ts`,产物 `dist/data/data.js`;`build.lib.fileName: 'data'`(package.json `type: module` → es 扩展名 `.js`);`emptyOutDir:false`;`copyPublicDir:false`;注入分离插件。 + + ⑤ **D-96 content:** `src/content` → `dist/content`;`.jsonc` = **保留一行系统标记 + `JSON.stringify(JSON.parse(body))`**(去注释、后缀仍 `.jsonc`;标记必须保留,否则 `PrefixedJSONCProcessor` 运行期 error 66);字体用 fontmin glyph 子集压缩;**不分块、不 zip**。`build-resource` 改为从 `src/content` 读取素材。 + + ⑥ **D-97 产物:** 不复制 `public/**`(`copyPublicDir:false`;整体删除由用户执行);dist 不携带旧模板内容(`editor.html` / `_server` / `_docs` / `libs` / `logo.png` / `styles.css` / `main.js` / `启动服务.exe`);不打包源码 zip;不生成资源地图 JSON;不生成 `dist.zip`;**复制 `LICENSE` 到 `dist/LICENSE`**;`index.html` 去掉旧样式表 link。 + + ⑦ **D-98 / D-99 / D-100:** 删除 `build:packages` / `build:lib`;`build:game` 调整(OI-6:移除读取已删 `public/project` 的 `declare.ts` 前置;**保留 `vue-tsc --noEmit`**);移除 babel 二次压缩;`manualChunks` 移除 `antdv`(源码已无引用)、保留 `audio`;legacy 插件维持现状。 + + ⑧ **拟新增命名(须报备批准):** `script/vite-plugin-data-isolation.ts` + `dataIsolationPlugin` + 测试文件(OI-1);`buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` + `script/build-resource.test.ts`(OI-2)。 + + ⑨ **待确认项:** 标记保留与 `Type:` 注释丢失(OI-3);无字体二进制的处置(OI-4);`src/styles.less` 的引入落点 `src/bootstrap.ts`(OI-5);`build:game` 前置链(OI-6);`manualChunks` / legacy(OI-7);`itemEffectDir` 缺 `item.js`(OI-8);全量构建可能被用户 WIP 阻断(OI-9);遗留文件与依赖保留(OI-10)。 + + ⑩ **产出文件:** 新增 `script/vite-plugin-data-isolation.ts`、`script/vite-plugin-data-isolation.test.ts`、`script/build-resource.test.ts`;重写 `script/build-resource.ts` / `script/build-game.ts`;删除 `script/build-packages.ts` / `script/build-lib.ts`;改 `package.json` / `index.html`(OI-5 获批则加 `src/bootstrap.ts` 一行 import);新增 `09-21-SUMMARY.md`。 + + ⑪ **不在本增量:** 不删 `public/**`;不改 `src/**`(除 OI-5 一行);不改数据端/渲染端源码、`vite.config.ts`、`script/dev.ts`、`script/declare.ts`、`script/types.ts`、lockfile;不新增依赖;不改 `ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 既有 PLAN 与 SUMMARY。 + + ⑫ **已知阻断(须一并告知并记入 SUMMARY):** 用户 WIP 使 `src/bootstrap.ts` 指向已移动的 UI 文件,全量构建可能失败;本计划不为此修改 `src/**`,门禁以插件单测 + content 单测 + 结构断言为主,全量构建如实记录归因。 + + ⑬ **验证方式:** Task 1 = `pnpm exec vitest run script/vite-plugin-data-isolation.test.ts`(真实 `vite.build()`:拒渲染端 specifier / 拒渲染端路径 / 通过纯数据端并输出 ESM / 不误判数据端路径)+ 结构断言;Task 2 = `pnpm exec vitest run script/build-resource.test.ts` + `script/build-game.ts` / `package.json` / `index.html` 结构断言 + `git status` 范围门禁 + `pnpm exec tsx script/build-game.ts` **尽力实跑**与产物门禁(成功时);**不设全仓 `check:type` / `build` / 全量 `test:ci` 门禁**。 + + ⑭ **修改方式声明(AGENTS.md):** 本计划执行含文件写入与删除,由**派发子代理执行**(`gsd-executor`),非内联执行。 + + ⑮ **关卡性质:** **无论触发方式或 `mode: yolo` / `workflow.auto_advance`,执行者 / 子代理必须停下等待用户回复;回复「可以执行」即视为通过。** 若用户就 OI-1..OI-10 给出指定,按指定执行。 + + 是否按本计划(09-21)执行「任务 11 · 构建重构」——新增数据端/渲染端分离 Vite 插件并注入数据端库模式 ESM 构建(`dist/data/data.js`)、重写 `build-resource` 把 `src/content` 压缩拷贝到 `dist/content`(jsonc 保留系统标记、fontmin、不分块不 zip)、重写 `build-game` 去掉旧模板/babel/资源分块/源码 zip/资源地图/dist.zip 并保留 `LICENSE`、删除 `build-packages.ts` / `build-lib.ts` 与对应 npm script、`index.html` 去旧样式表 link(样式经入口 JS 引入)、保留 `vue-tsc --noEmit`、微调 `manualChunks`;不删 `public/**`、不改 `src/**`(除 OI-5 一行)、不动用户其它并发 WIP。 + + AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。执行期含写操作与**删除两个脚本**。`src/content` 全部 20 个 jsonc 均为「前缀注释 + 严格 JSON」,加载器 `PrefixedJSONCProcessor` 依赖 `// --- SYSTEM PREFIX END --- //` 的 split(必须恰好 2 段),故压缩保留标记是运行期兼容硬约束(与 D-96「去注释」并不冲突:去掉的是标记**之外**的注释)。`src/content/font` 无字体二进制、`src/content/item.ts` 与 `core.jsonc` 的 `itemEffectDir` 不匹配、`src/bootstrap.ts` 仍指向已移动的 UI 文件——均属用户 WIP 或既有缺口,本计划只记录不改。工作树含用户并发改动(`index.html`、`packages-user/client-modules/src/**`、`src/**` 等),提交时须逐条核对暂存面、只暂存本计划 `files_modified`。行号为规划日实测、可能漂移,门禁以结构/行为断言为准。 + + + + + + 回复「可以执行」即可(默认采用 OI-1..OI-10 的提议口径)。如需调整任一 `open_items`,请在回复中一并指明。 + + node -e "const fs=require('fs');const bad=[];const must=['script/build-game.ts','script/build-resource.ts','script/build-packages.ts','script/build-lib.ts','package.json','index.html','src/data.ts','src/content/core.jsonc','src/content/client.jsonc','src/content/texture/.list.jsonc','src/content/font/.list.jsonc','packages-user/data-state/src/loader/jsoncProcessor.ts','packages-user/data-state/src/loader/loader.ts'];for(const p of must){if(!fs.existsSync(p))bad.push('missing ref: '+p)}const pkg=JSON.parse(fs.readFileSync('package.json','utf8'));for(const s of ['build:packages','build:lib','build:game']){if(!(s in pkg.scripts))bad.push('package.json missing script '+s)}const bg=fs.readFileSync('script/build-game.ts','utf8');for(const t of ['checkClientData','splitResource','source-code.zip','loadList.json']){if(bg.indexOf(t)<0)bad.push('build-game.ts no longer contains expected legacy token: '+t)}const html=fs.readFileSync('index.html','utf8');if(html.indexOf('styles.css')<0)bad.push('index.html no longer has the legacy stylesheet link');const jp=fs.readFileSync('packages-user/data-state/src/loader/jsoncProcessor.ts','utf8');if(jp.indexOf('SYSTEM PREFIX END')<0&&jp.indexOf('prefixSplit')<0)bad.push('jsoncProcessor.ts: expected prefix split logic');for(const p of ['script/vite-plugin-data-isolation.ts','script/vite-plugin-data-isolation.test.ts','script/build-resource.test.ts']){if(fs.existsSync(p))bad.push('post-state already exists: '+p)}if(bad.length>0){console.error('PRESTATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK pre-execution state: legacy build scripts + scripts entries + legacy index.html + content loader present; new plugin/tests absent')" + non-zero exit, 或 stderr 含 "PRESTATE FAILED"(旧构建脚本 / `package.json` scripts / `index.html` 旧 link / 加载器标记逻辑任一缺失,或新插件与测试文件已存在) + 用户在对话中明确回复「可以执行」(若就 OI-1..OI-10 给出非默认指定,须在汇报与后续任务中据其指定执行) + + + - 用户回复中明确包含「可以执行」 + - 汇报内容涵盖:要解决的问题、范围(两个脚本重构 + 两个脚本删除 + npm script 调整)、D-94 插件扫描口径(specifier + 解析路径 + 报错终止 + 不误判数据端)与取代 `checkClientData`、D-95 库模式 ESM 与 `dist/data/data.js`、D-96 content 压缩(**保留系统标记**、fontmin、不分块不 zip)、D-97 产物清单与 `LICENSE` 保留、`index.html` 去 link、D-99 保留 `vue-tsc`、D-100 去 babel/微调 `manualChunks`、**全部拟新增命名(OI-1 / OI-2)**、写操作边界(含不删 `public/**`、`src/**` 仅 OI-5 一行)、验证方式、已知阻断(用户 WIP)、OI-1..OI-10 + - 汇报中已原样告知:**删除 `build-packages.ts` / `build-lib.ts` 与 `build:packages` / `build:lib` 属不可逆的仓库内删除(可经 git 恢复)**、**content 压缩保留标记是对 D-96 的兼容性补充**、**全量构建可能因用户 WIP 失败且不得修 `src/**`** + - 汇报中已原样告知**关卡性质**(必须停下等待;不因触发方式或 `yolo` / `auto_advance` 自动放行;回复「可以执行」即通过) + + 用户明确回复「可以执行」;在此之前未执行任何任务、未修改或删除任何文件、未派发任何执行子代理 + + + + Task 1: 数据端/渲染端分离 Vite 插件端到端最小切片 —— `dataIsolationPlugin` + 真实 `vite.build()` 运行期单测 + Task 0 已闭环(用户回复「可以执行」;OI-1 已获批或按用户指定命名)。`vite` 与 `vitest` 可用;`script/vite-plugin-data-isolation.ts` 与 `script/vite-plugin-data-isolation.test.ts` 尚不存在;`packages-user/*` 与 `packages/*` 的渲染端/数据端包名如前置读取所列。 + script/vite-plugin-data-isolation.ts, script/vite-plugin-data-isolation.test.ts + + - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(**D-94**;D-04 / D-56) + - vite.config.ts(别名:`@motajs/*` → `packages/*/src`、`@user/*` → `packages-user/*/src`、`@dp/*`、`@/*`;`base: './'`) + - packages-user/client-modules/package.json / packages-user/client-base/package.json / packages-user/client-system/package.json(渲染端包面) + - packages-user/data-state/package.json / packages-user/data-common/package.json / packages-user/data-base/package.json / packages-user/data-system/package.json(数据端包面,用于避免误判) + - script/check-data-circular.test.ts(既有 `script/*.test.ts` 的 vitest 风格:显式 `import { describe, expect, it } from 'vitest'`;每个 `it` 前一行注释) + - vitest 配置位于 vite.config.ts 的 `test` 段(`testTimeout: 30000`) + - AGENTS.md(测试文件规则:每个 `it` 前一行注释;命名须先获批);dev.md(命名规范、jsDoc、模块无副作用、非空判断约定) + + + (1) **新建 `script/vite-plugin-data-isolation.ts`(命名以 OI-1 获批结果为准):** + - 自 `vite` 导入**类型** `Plugin`(`import { Plugin } from 'vite'`,按 dev.md 不使用 `import type`)。 + - 模块内声明两组模块级只读常量(不导出,避免额外命名面):**specifier 禁令**——`@user/client-` 前缀、`@user/entry-client`、`@motajs/render` / `@motajs/render-vue` / `@motajs/client` / `@motajs/client-base` / `@motajs/system` / `@motajs/audio` / `@motajs/legacy-*`、`vue` 与 `vue/*`、`@vue/*`、`ant-design-vue` 与子路径、`@ant-design/icons-vue` 与子路径;**解析路径禁令**——`[/\\]packages-user[\\/](client-modules|client-base|client-system|entry-client)[\\/]`、`[/\\]packages[\\/](render|render-vue|client|client-base|system|audio|legacy-[^\\/]+)[\\/]`、`[/\\]node_modules[\\/](vue|@vue|ant-design-vue|@ant-design)[\\/]`。 + - 导出 `dataIsolationPlugin(): Plugin`,返回对象包含:`name`(例如 `'mota-data-isolation'`)、`enforce: 'pre'`、`apply: 'build'`、`resolveId(source, importer)` 与 `moduleParsed(id)`。 + - `resolveId`:对 `source` 先做 specifier 禁令匹配;命中即调用 `this.error(...)`,错误消息必须包含**命中的模块标识**、**导入者**(`importer ?? ''`)与「data-side build must not depend on render-side modules」的明确说明,使构建立即终止且错误可定位。未命中返回 `null`(交回默认解析)。 + - `moduleParsed(id)`:对**解析后的绝对路径**做路径禁令匹配;命中即 `this.error(...)`(消息含该绝对路径与其 importers(可由 `this.getModuleInfo(id)?.importers` 提供,若为空则省略))。这一路覆盖经别名解析、传递依赖与 `node_modules` 的真实落点。 + - **不得**把数据端包(`@user/data-*`、`@motajs/common`、`@motajs/loader`、`@motajs/types`、`@motajs/animate`、`anon-tokyo`)列入禁令;**不得**对字符串做宽泛的 `client` 子串匹配(避免 `client.jsonc` 之类的误判)。 + - 按 dev.md 为导出函数与其参数写 jsDoc;模块顶层零副作用(只声明常量与函数)。 + + (2) **新建 `script/vite-plugin-data-isolation.test.ts`:** + - 使用 `node:fs/promises` 的 `mkdtemp`(`os.tmpdir()`)、`mkdir`、`writeFile`、`rm` 与 `node:path`,每个用例独立临时目录,`afterEach` 清理。 + - 调用**真实** `vite.build()`:`configFile:false`、`root: `、`logLevel:'silent'`、`build: { write:false, minify:false, lib: { entry: , formats: ['es'], fileName: 'data' }, copyPublicDir:false }`、`plugins:[dataIsolationPlugin()]`。 + - 用例(每个 `it` 前一行注释说明测试内容): + 1. **渲染端 specifier 被拒**:entry 导入 `vue`;断言 `build()` rejects,错误消息含 `vue` 与插件错误标识;同时在临时根写入同名 `vue` 无关文件以确保拒绝来自插件而非解析失败(或用 `resolve.alias` 把 `vue` 指到本地 fixture,仍应先被 `resolveId` 拦截)。 + 2. **渲染端解析路径被拒(传递依赖)**:临时目录内构造 `/packages-user/client-modules/fake.ts`,entry 相对导入它;断言 rejects 且消息含该路径片段。 + 3. **纯数据端通过且产物为 ESM**:entry 导入 `/packages-user/data-state/fake.ts`(导出常量)并 re-export;断言 `build()` resolve,输出含 chunk 且其 `code` 含 `export` 语句(ESM 产物),不抛错。 + 4. **数据端路径不误判**:entry 导入 `@motajs` 风格名不可解析时改用相对路径 `/packages-user/data-common/fake.ts`;断言通过(证明禁令只覆盖渲染端路径)。 + - 断言尽量稳定(不依赖 chunk 文件名/顺序),只依赖 rejects/resolve 与错误消息关键字。 + + + - entry `import 'vue'` → `build()` rejects;错误消息含 `vue` 与 `data-side build must not depend on render-side`。 + - entry 相对导入 `/packages-user/client-modules/fake.ts` → rejects;错误消息含该路径(证明 `moduleParsed` 路径兜底生效,且覆盖传递依赖)。 + - entry 相对导入 `/packages-user/data-state/fake.ts` → resolve;输出 chunk `code` 含 `export`(ESM)。 + - entry 相对导入 `/packages-user/data-common/fake.ts` → resolve(不误判数据端)。 + + 仅新增一个插件文件与一个测试文件;`Remove-Item` 两文件即可整体回退,未接线到构建脚本、未改任何既有文件。 + + pnpm exec vitest run script/vite-plugin-data-isolation.test.ts + non-zero exit, 或任一断言失败(渲染端 specifier 未被拒 / 渲染端解析路径未被拒 / 纯数据端构建失败 / 数据端路径被误判 / 错误消息缺少模块标识或导入者) + node -e "const fs=require('fs');const bad=[];const p='script/vite-plugin-data-isolation.ts';if(!fs.existsSync(p))bad.push('plugin file missing');else{const t=fs.readFileSync(p,'utf8');if(!/export\s+function\s+dataIsolationPlugin\s*\(/.test(t))bad.push('dataIsolationPlugin not exported');if(!/enforce\s*:\s*'pre'/.test(t))bad.push('plugin is not enforce:pre');if(t.indexOf('moduleParsed')<0)bad.push('plugin lacks moduleParsed resolved-path check');if(t.indexOf('this.error(')<0)bad.push('plugin does not abort via this.error');for(const tok of ['@user/client-','@motajs/render','vue','ant-design-vue']){if(t.indexOf(tok)<0)bad.push('plugin forbidden set missing token: '+tok)}for(const tok of ['@user/data-','@motajs/common']){if(t.indexOf(tok)>=0)bad.push('plugin must not forbid data-side token: '+tok)}}const tp='script/vite-plugin-data-isolation.test.ts';if(!fs.existsSync(tp))bad.push('plugin test file missing');else{const t=fs.readFileSync(tp,'utf8');if(t.indexOf('vite')<0)bad.push('test does not use real vite build');if(t.indexOf('dataIsolationPlugin')<0)bad.push('test does not import the plugin')}if(bad.length>0){console.error('PLUGIN GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK plugin structure: enforce pre + specifier + resolved-path checks + abort + no data-side false positives')" + non-zero exit, 或 stderr 含 "PLUGIN GATE FAILED"(插件文件缺失 / 未导出 `dataIsolationPlugin` / 非 `enforce: 'pre'` / 缺 `moduleParsed` 路径检查 / 未 `this.error` 终止 / 禁令缺少关键 token / 误把数据端包列入禁令) + + + - `script/vite-plugin-data-isolation.ts` 存在并按 OI-1 命名导出 `dataIsolationPlugin`;`enforce: 'pre'` + `resolveId` specifier 检查 + `moduleParsed` 解析路径检查 + `this.error(...)` 终止 + - 禁令覆盖 D-94 所列渲染端面(`@user/client-*`、`@user/entry-client`、`@motajs/render*` / `client*` / `system` / `audio` / `legacy-*`、`vue`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue` 及其解析路径);未把 `@user/data-*` / `@motajs/common` 等数据端依赖列入 + - 运行期单测用**真实 `vite.build()`** 证明四类行为(拒 specifier / 拒解析路径(传递依赖)/ 纯数据端通过且 ESM / 数据端不误判),且每个 `it` 前有一行注释 + - 未改任何既有文件;未接线到 `build-game.ts`(留给 Task 2) + + 分离插件与运行期单测就位:真实 Vite 构建在渲染端依赖上被阻断、在纯数据端构建通过并产出 ESM;结构断言全绿;零既有文件改动 + + + + Task 2: 构建编排重写与收口 —— `build-resource`(content 压缩/拷贝/字体)+ `build-game`(渲染端 → 数据端 ESM → content → LICENSE)+ 删除旧脚本 + npm script / index.html + 产物与范围门禁 + SUMMARY + Task 1 已完成(`script/vite-plugin-data-isolation.ts` 导出 `dataIsolationPlugin` 并通过单测);Task 0 已闭环(OI-2..OI-10 已获批或按用户指定)。`src/content/**` 的 20 个 jsonc 均含系统标记;`src/content/font/` 无字体二进制;`public/**` 仍存在(本计划不删,仅阻止其进入 dist)。 + script/build-resource.ts, script/build-resource.test.ts, script/build-game.ts, script/build-packages.ts, script/build-lib.ts, package.json, index.html, src/bootstrap.ts, .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md + + - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(**D-93 / D-95 / D-96 / D-97 / D-98 / D-99 / D-100**;D-56) + - script/build-game.ts(旧编排:`logProgress` 步骤表、`buildClient`、`buildData`、`checkClientData`、`getAllChars`、`generateResourceJSON`、`buildGame` 的 toCopy / archiver / compressing / 服务器 exe / `dist.zip`) + - script/build-resource.ts(旧 `splitResource` / `CompressedUsage` / `LoadDataType` / `SplittedResource`) + - script/utils.ts(`formatSize`);script/build-packages.ts / script/build-lib.ts(待删除内容,确认无其它引用) + - package.json(scripts 面);index.html(样式 link 行);src/styles.less(仅 7 行 `#game-draw` 居中样式) + - src/content/**(含 `texture/.list.jsonc` / `maps/.list.jsonc` / `defaults/*.jsonc` / `font/.list.jsonc` / `item.ts`) + - packages-user/data-state/src/loader/jsoncProcessor.ts(标记 split);packages-user/data-state/src/loader/loader.ts:70-142(生产态路径);src/constant.ts(`content/core.jsonc` / `content/client.jsonc`) + - packages/audio/package.json 与 packages/audio/src/{source,decoder}.ts(`audio` chunk 依据);全仓源码中 `ant-design-vue` 无导入的检索结论 + - AGENTS.md(禁改既有注释、修改方式须预先声明);dev.md(命名、interface、jsDoc、非空判断) + + + (0) **前置确认:** 仅按 Task 0 获批口径执行;若 OI-2 命名被调整,以用户指定为准并在 SUMMARY 记录。 + + (1) **重写 `script/build-resource.ts`(命名以 OI-2 为准):** 删除 `splitResource` / `CompressedUsage` / `LoadDataType` / `SplittedResource` / JSZip 相关实现,改为 content 处理模块: + - 声明并导出 `IContentBuildOptions`:`readonly sourceDir: string`(`src/content`)、`readonly outputDir: string`(`dist/content`)、`readonly chars: string`(fontmin glyph 子集文本)。 + - 声明并导出 `IContentBuildStats`:`readonly files: number`(写入文件数)、`readonly jsonc: number`(压缩的 jsonc 数)、`readonly fonts: number`(fontmin 处理的字体数)。 + - 导出 `minifyJSONC(text: string): string`:以系统标记 `'// --- SYSTEM PREFIX END --- //'` 定位;若标记出现则要求 `split` 结果恰好 2 段(否则抛出带文件语义的错误),对标记之后的正文 `JSON.stringify(JSON.parse(body))`;输出 = 标记行 + `'\n'` + 压缩正文;若无标记则对整段 `JSON.stringify(JSON.parse(text))`(非法 JSON 抛出并带上上下文)。**严禁**丢掉标记行;**不得**改调用方后缀。 + - 导出 `buildContent(options: IContentBuildOptions): Promise`:递归遍历 `sourceDir`(`readdir` with `withFileTypes`)并在 `outputDir` 内镜像目录结构: + - `*.jsonc` → `minifyJSONC` 后写入同名路径; + - 字体扩展(`.ttf` / `.otf` / `.woff` / `.woff2`)→ 用 `Fontmin` 的 `glyph({ text: options.chars })` 插件压缩到 `outputDir` 对应子目录(保持文件名); + - 其它文件(PNG 等)→ `fs-extra.copy` 原样拷贝; + - `sourceDir/font` 下若**无**字体二进制:跳过 fontmin 并 `process.stdout.write` 告警(含目录),不抛错(OI-4)。 + - 按 dev.md 写 jsDoc;模块无顶层副作用;非空判断用 `isNil`(字面量)/ `!`(对象)。 + + (2) **重写 `script/build-game.ts`:** + - **保留**:`DEBUG_BUILD` / `DEBUG_REPLAY`、`ansi`、`logProgress`(外部行为)、`buildClient` / `buildData` 名称、`distDir`、`getAllChars`(改为从新产物收集字符)、错误处理与 `process.exit(1)` 风格、`formatSize` 报告。 + - **删除**:`tempDir` / `_temp` 相关、`RESOUCE_SIZE`、`checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize`、`generateResourceJSON` / `CompressedLoadList*`、`@babel/core` 与 `transformAsync`、`archiver` / `createWriteStream` / `compressing`、`RequiredData` / `RequiredIconsData` 及对 `public/project/{data,icons}.js` 的读取、`toCopy` 旧模板清单、源码压缩包、资源地图 JSON、资源分块拷贝、服务器 exe 拷贝、`dist.zip` 压缩与相关体积告警分支。 + - **`buildClient(outDir)`**:复用 `loadConfigFromFile` + `mergeConfig`;注入 `legacy(...)`(目标浏览器列表与 `renderModernChunks:false` 维持现状,OI-7);`build.outDir = outDir`(即 `distDir`)、`copyPublicDir: false`、`minify: !DEBUG_BUILD`、`emptyOutDir: true`;`rollupOptions.external` 维持 `['@wasm-audio-decoders/opus-ml']`;`output` 维持 `format:'es'` / `entryFileNames` / `chunkFileNames` / `assetFileNames`;`manualChunks` 仅保留 `audio`(移除 `antdv`)。 + - **`buildData(outDir, entry)`**:`build.outDir = resolve(distDir, 'data')`、`copyPublicDir:false`、`emptyOutDir:false`、`minify: !DEBUG_REPLAY`、`lib: { entry, fileName: 'data', formats: ['es'] }`(移除 iife 与 `name`)、`plugins: [dataIsolationPlugin()]`。构建成功后断言/记录产物为 `dist/data/data.js`。 + - **`getAllChars(distDir)`**:递归读取渲染端产物中的 `.js`(排除 `dist/data`)与 `src/content/**/*.jsonc` 的字符集合,返回字符串;供 fontmin 使用。 + - **`buildGame()` 编排(新进度表:① 构建前准备 ② 构建渲染端 ③ 构建数据端 ④ 处理 content ⑤ 收尾):** + 1. `ensureDir(distDir)` + `emptyDir(distDir)`; + 2. `await buildClient(distDir)`; + 3. `const chars = await getAllChars(distDir)`; + 4. `await buildData(resolve(distDir, 'data'), resolve(process.cwd(), 'src/data.ts'))`; + 5. `const stats = await buildContent({ sourceDir: resolve(process.cwd(), 'src/content'), outputDir: resolve(distDir, 'content'), chars })`; + 6. `copy(resolve(process.cwd(), 'LICENSE'), resolve(distDir, 'LICENSE'))`; + 7. 输出构建信息(dist 体积、`dist/data/data.js` 体积、content 统计),**不再**生成任何压缩包。 + - 保留模块底部执行块(`buildGame()`),但按 Task 1 的测试模式无需导出。 + + (3) **删除旧脚本:** 删除 `script/build-packages.ts` 与 `script/build-lib.ts`(用 `Remove-Item` / `fs.rm`);确认全仓无其它引用(`Select-String` 检索 `build-packages` / `build-lib`,仅允许 `package.json` 旧 script 行在删除前命中)。 + + (4) **`package.json`:** 删除 `build:packages` 与 `build:lib` 两个 script;按 OI-6 将 `build:game` 调整为 `vue-tsc --noEmit && tsx script/build-game.ts`(**保留 `vue-tsc --noEmit`**,移除 `tsx script/declare.ts` 前置;不删除 `script/declare.ts` 文件)。**不改** `dependencies` / `devDependencies`。 + + (5) **`index.html`:** 仅删除旧样式表 `` 行;保留 DOCTYPE / head 其它 meta / `
` / `` / `