mirror of
https://github.com/motajs/template.git
synced 2026-10-22 07:10:17 +08:00
docs(09-21): revision 7 - camelCase plugin files, ModuleInfo hook, function-scoped gates
This commit is contained in:
parent
b8afe95e1b
commit
69e44b823b
@ -5,8 +5,8 @@ type: execute
|
||||
wave: 18
|
||||
depends_on: [09-19, 09-20]
|
||||
files_modified:
|
||||
- script/vite-plugin-data-isolation.ts
|
||||
- script/vite-plugin-data-isolation.test.ts
|
||||
- script/vitePluginDataIsolation.ts
|
||||
- script/vitePluginDataIsolation.test.ts
|
||||
- script/build-resource.ts
|
||||
- script/build-game.ts
|
||||
- script/build-packages.ts
|
||||
@ -20,30 +20,34 @@ estimate:
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "**本计划第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md / D-03)**:先汇报「任务 11 · 构建重构(适配新引擎)」的要做什么 / 为什么 / 大致怎么做 / 影响范围,列明最小范围(只动 4 个 script 路径 + 删除 `build-packages.ts` + 可选插件测试)、确认的目标构建流程(准备 → 渲染端 → 数据端 → 隔离检测 → content → LICENSE)、**不改构建顺序**、**不做特殊失败处理(无 BLOCKED、无 baseline 范围门禁、无对外部文件/状态的检查)**、以及待批准命名(OI-1 / OI-2),然后停下等待「可以执行」。"
|
||||
- "**本计划第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md / D-03)**:先汇报「任务 11 · 构建重构(适配新引擎)」的要做什么 / 为什么 / 大致怎么做 / 影响范围,列明最小范围(新增 `script/vitePluginDataIsolation.ts` 及其**必需**测试 `script/vitePluginDataIsolation.test.ts`、重写 2 个 script、删除 `build-packages.ts`)、确认的目标构建流程(准备 → 渲染端 → 数据端 → 隔离检测 → content → LICENSE)、**不改构建顺序**、**不做特殊失败处理(无 BLOCKED、无 baseline 范围门禁、无对外部文件/状态的检查)**、三项已知边界(jsonc 去净注释与 `PrefixedJSONCProcessor` error 66 的冲突、`index.html` 仍引用 `/styles.css` 且本计划不产出该文件、本计划不端到端运行全量 `build:game`)、以及待批准命名(OI-1 / OI-2),然后停下等待「可以执行」。"
|
||||
- "**新文件名必须小驼峰(dev.md「代码文件名:小驼峰」):** 插件文件固定为 `script/vitePluginDataIsolation.ts`,测试文件固定为 `script/vitePluginDataIsolation.test.ts`;**不得**创建 `script/vite-plugin-data-isolation.ts` / `script/vite-plugin-data-isolation.test.ts`(连字符命名只用于 Markdown 文档)。测试是**必需产物**,不是可选项。"
|
||||
- "**`moduleParsed` 签名(W2):** 使用 Rollup 的 `ModuleParsedHook` 形态 `moduleParsed(info: ModuleInfo)`(**不是** `moduleParsed(id: string)`),按 `info.id` 做解析路径匹配;错误消息中的导入者通过 `this.getModuleInfo(info.id)` 读取 `importers`。"
|
||||
- "**结构门禁按函数切片(W3):** `build-game.ts` 的门禁先按函数切出 `buildClient` / `buildData` / `buildGame` 片段,再在各自片段内断言——`buildData` 片段必须含 `copyPublicDir: false`、`emptyOutDir: false`、`dataIsolationPlugin`、`formats: ['es']`、`entryFileNames: 'data.js'`、`chunkFileNames: 'data/`、`assetFileNames: 'data/`;`buildClient` 片段单独断言 `copyPublicDir: false` 且**不含** `dataIsolationPlugin`;配置放错位置必须导致门禁失败。"
|
||||
- "**渲染端(D-97 适配):** 以 `index.html` 为入口(Vite 默认,不显式设置 `rollupOptions.input`,`index.html` 本身不改),按 `vite.config.ts` 常规配置输出到 `dist`(常规 `index.html` + `assets/` / `chunks/`);`copyPublicDir: false`、`emptyOutDir: false`;不再拷贝旧模板产物(`editor.html` / `_server` / `_docs` / `libs` / `logo.png` / `styles.css` / `main.js` / `启动服务.exe`),不再做 `main.js` 的 babel 二次压缩。"
|
||||
- "**数据端(D-94 / D-95):** 入口 `src/data.ts`,Vite 库模式 **ESM**(`formats: ['es']`);**入口产物固定为 `dist/data.js`(dist 根目录)**,数据端附属产物(chunk / asset)落在**数据端自己的子文件夹 `dist/data/`**,**不得与渲染端的 `assets/`、`chunks/` 混用**;`emptyOutDir: false`(不得清掉已产出的渲染端产物);注入 `dataIsolationPlugin()`。"
|
||||
- "**隔离检测(D-94):** 新增 Vite 插件扫描数据端模块图(specifier + 解析后路径双路);命中渲染端模块即 `this.error(...)` 终止;错误消息含 `[mota-data-isolation]`、命中模块与 `matched: <token>`;**取代**现有按体积的 `checkClientData` 启发式(`checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize` 一并删除)。"
|
||||
- "**content(D-96):** `src/content` → `dist/content`;`*.jsonc` 压成**纯 JSON、去掉所有注释(含 `// --- SYSTEM PREFIX END --- //`)**,后缀仍 `.jsonc`;字体用 fontmin 压缩(无字体二进制时跳过并告警);**不分块、不 zip**;`build-resource` 从 `src/content` 读取素材。"
|
||||
- "**最终 `dist` 根(D-97):** `index.html` + Vite 常规渲染产物 + `data.js` + `content/` + `LICENSE`;数据端附属在 `dist/data/`;**不产出** `source-code.zip` / `loadList.json` / `dist.zip` / 资源分块 `resource.<hash>.h5data`。"
|
||||
- "**写入边界:** 只写 `files_modified`;**不改** `script/build-lib.ts`、`package.json`、`index.html`、`dev.md`、`src/**`、`public/**`、`vite.config.ts`、lockfile、`packages*/**`、`script/dev.ts`;不回退用户既有 WIP。"
|
||||
- "**命名须先获批(D-04):** `script/vite-plugin-data-isolation.ts` / `dataIsolationPlugin` / `name: 'mota-data-isolation'` / `script/vite-plugin-data-isolation.test.ts`(OI-1);`buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats`(OI-2)。未获批前不得落码。"
|
||||
- "**验证可运行(Nyquist):** 插件单测以**真实 `vite.build()`** 证明「拒渲染端 import / 通过纯数据端」;两个脚本做结构断言(无 `checkClientData`、数据端 `formats: ['es']` 且入口 `data.js` 在根、附属产物在 `dist/data/`、jsonc 去注释、无 zip / 分块、无旧模板拷贝、数据子文件夹与渲染端分离);命令一律 `cmd /c \"pnpm exec ...\"`(PowerShell 5.1 下裸 `pnpm` 会因 `pnpm.ps1` 的 `PSSecurityException` 失败)。"
|
||||
- "**content(D-96):** `src/content` → `dist/content`;`*.jsonc` 压成**纯 JSON、去掉所有注释(含 `// --- SYSTEM PREFIX END --- //`)**,后缀仍 `.jsonc`;字体用 fontmin 压缩(无字体二进制时跳过并告警);**不分块、不 zip**;`build-resource` 从 `src/content` 读取素材。**已知边界(INFO-1):** 去净注释会移除运行期 `PrefixedJSONCProcessor` 依赖的标记行(其 `split` 结果不是 2 段时报 error 66);`packages-user/**` 的 loader 适配经用户确认**不在本计划范围**,只作为已知后续项记入 Task 0 汇报与 SUMMARY,**不得**因此保留注释或改 loader。"
|
||||
- "**最终 `dist` 根(D-97):** `index.html` + Vite 常规渲染产物 + `data.js` + `content/` + `LICENSE`;数据端附属在 `dist/data/`;**不产出** `source-code.zip` / `loadList.json` / `dist.zip` / 资源分块 `resource.<hash>.h5data`。**已知边界(INFO-2):** `index.html` 仍含 `<link href='/styles.css'>` 且本计划不产出 `styles.css`;该文件与样式接线由用户处理,事实须记入 Task 0 汇报与 SUMMARY。"
|
||||
- "**写入边界:** 只写 `files_modified`;**不改** `script/build-lib.ts`、`package.json`、`index.html`、`dev.md`、`src/**`、`public/**`、`packages-user/**`、`vite.config.ts`、lockfile、`packages*/**`、`script/dev.ts`;不回退用户既有 WIP。"
|
||||
- "**命名须先获批(D-04):** `script/vitePluginDataIsolation.ts` / `dataIsolationPlugin` / `name: 'mota-data-isolation'` / `script/vitePluginDataIsolation.test.ts`(OI-1);`buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats`(OI-2)。未获批前不得落码。"
|
||||
- "**验证可运行(Nyquist)且范围诚实(INFO-4):** 插件单测(**必需**)以**真实 `vite.build()`** 证明「拒渲染端 import / 通过纯数据端」;两个脚本做**按函数切片**的结构断言(无 `checkClientData`、数据端 `formats: ['es']` 且入口 `data.js` 在根、附属产物在 `dist/data/`、jsonc 去注释、无 zip / 分块、无旧模板拷贝、数据子文件夹与渲染端分离);SUMMARY 明确记录**未端到端运行全量 `build:game`**(只做插件单测 + 结构断言);命令一律 `cmd /c \"pnpm exec ...\"`(PowerShell 5.1 下裸 `pnpm` 会因 `pnpm.ps1` 的 `PSSecurityException` 失败)。"
|
||||
artifacts:
|
||||
- path: script/vite-plugin-data-isolation.ts
|
||||
provides: "新增插件 `dataIsolationPlugin`:`enforce: 'pre'` + `apply: 'build'`;`resolveId` 拦渲染端 specifier,`moduleParsed` 按解析后绝对路径兜底;命中即 `this.error(...)`,消息含 `[mota-data-isolation]` / 命中模块 / `matched: <token>`(命名待批准,OI-1)"
|
||||
- path: script/vite-plugin-data-isolation.test.ts
|
||||
provides: "运行期单测:真实 `vite.build()`(`configFile:false`、临时 fixture、`write:false`、lib ESM)验证拒渲染端 specifier(`vue`)、拒渲染端解析路径(`src/client` / `packages-user/client-*`)、通过纯数据端并产出 ESM;每个 `it` 前一行注释(AGENTS.md)"
|
||||
- path: script/vitePluginDataIsolation.ts
|
||||
provides: "新增插件(文件名小驼峰,dev.md):`dataIsolationPlugin(): Plugin`,`enforce: 'pre'` + `apply: 'build'`;`resolveId` 拦渲染端 specifier,`moduleParsed(info: ModuleInfo)` 按 `info.id` 兜底,错误消息经 `this.getModuleInfo(info.id)` 读取 `importers`;命中即 `this.error(...)`,消息含 `[mota-data-isolation]` / 命中模块 / `matched: <token>`(命名待批准,OI-1)"
|
||||
- path: script/vitePluginDataIsolation.test.ts
|
||||
provides: "**必需**运行期单测:真实 `vite.build()`(`configFile:false`、临时 fixture、`write:false`、lib ESM)验证拒渲染端 specifier(`vue`)、拒渲染端解析路径(`src/client` / `packages-user/client-*`,走 `moduleParsed(info.id)`)、通过纯数据端并产出 ESM;每个 `it` 前一行注释(AGENTS.md)"
|
||||
- path: script/build-resource.ts
|
||||
provides: "重写为 content 处理模块:`minifyJSONC`(按行去掉 `//` 注释后 `JSON.parse` + `JSON.stringify`,输出纯 JSON)、`buildContent`(递归 `src/content` → `dist/content`:jsonc 压缩、fontmin glyph 子集、其它素材原样拷贝;无字体时跳过告警)与 `IContentBuildOptions`(`chars: string`)/ `IContentBuildStats`"
|
||||
- path: script/build-game.ts
|
||||
provides: "重写编排:清空/创建 `dist` 与 `_temp` 一次 → 渲染端(`index.html` 入口 → `dist`)→ 数据端(库模式 ESM,入口 `dist/data.js`、附属 `dist/data/`)→ `getAllChars` → `buildContent` → 复制 `LICENSE`;删除旧 pipeline(babel / zip / 分块 / 旧模板拷贝 / 资源地图 / `checkClientData`)"
|
||||
provides: "重写编排:清空/创建 `dist` 与 `_temp` 一次 → 渲染端(`index.html` 入口 → `dist`)→ 数据端(库模式 ESM,入口 `dist/data.js`、附属 `dist/data/`,注入 `./vitePluginDataIsolation` 的 `dataIsolationPlugin`)→ `getAllChars` → `buildContent` → 复制 `LICENSE`;删除旧 pipeline(babel / zip / 分块 / 旧模板拷贝 / 资源地图 / `checkClientData`)"
|
||||
- path: script/build-packages.ts
|
||||
provides: "**删除**(D-93 / D-98;`build-lib.ts` 不动)"
|
||||
- path: .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md
|
||||
provides: "执行摘要:范围、插件扫描口径与错误行为、数据端产物落点(`dist/data.js` + `dist/data/`)、content 压缩口径(纯 JSON、无注释)、dist 产物与排除清单、删除清单、命名获批情形、门禁结果、`open_items` 处置"
|
||||
provides: "执行摘要:范围、插件扫描口径与错误行为(含 `moduleParsed(info: ModuleInfo)`)、数据端产物落点(`dist/data.js` + `dist/data/`)、content 压缩口径(纯 JSON、无注释)、dist 产物与排除清单、删除清单、命名获批情形、门禁结果、`open_items` 处置、**已知后续项**(INFO-1 `PrefixedJSONCProcessor` error 66 冲突与 loader 适配不在范围;INFO-2 `index.html` 仍引用 `/styles.css` 且不产出)与**验证边界**(INFO-4 未端到端运行全量 `build:game`)"
|
||||
key_links:
|
||||
- "`dataIsolationPlugin`(`script/vite-plugin-data-isolation.ts`)↔ `buildData` 的 `plugins` 数组(`script/build-game.ts`)—— D-94 的唯一执行点,取代 `checkClientData` 体积启发式"
|
||||
- "`dataIsolationPlugin`(`script/vitePluginDataIsolation.ts`)↔ `buildData` 的 `plugins` 数组(`script/build-game.ts`,`import { dataIsolationPlugin } from './vitePluginDataIsolation'`)—— D-94 的唯一执行点,取代 `checkClientData` 体积启发式"
|
||||
- "`moduleParsed(info: ModuleInfo)` 的 `info.id` ↔ `this.getModuleInfo(info.id)` 的 `importers` —— 错误消息可追溯到引入渲染端模块的上游"
|
||||
- "`buildData` 的 `build.lib.fileName: 'data'` + `rollupOptions.output.entryFileNames: 'data.js'` ↔ `dist/data.js`(dist 根)—— D-95 入口产物契约"
|
||||
- "`buildData` 的 `chunkFileNames` / `assetFileNames` 前缀 `data/` ↔ `dist/data/` —— 数据端附属产物与渲染端 `assets/`、`chunks/` 分离"
|
||||
- "`buildClient` 不设 `rollupOptions.input` ↔ Vite 默认以 `index.html` 为入口 —— 渲染端输出常规 `dist/index.html` + `assets/` / `chunks/`"
|
||||
@ -52,27 +56,32 @@ must_haves:
|
||||
- "`dist/content/**` ↔ `MotaDataLoader.resolveURL` 的 `content/**` 相对路径与 `src/data/constant.ts` 的 `CORE_URL='content/core.jsonc'` —— 目录落点契约"
|
||||
assumptions:
|
||||
- "**构建顺序维持现状:** 准备(清空/创建 `dist`、`_temp`)→ 渲染端 → 数据端;与现有 `build-game.ts` 的 client → data 顺序一致。数据端构建必须 `emptyOutDir: false`,否则会清掉渲染端产物。"
|
||||
- "**`src/content` 共 23 个 `.jsonc`:** 均为「前缀注释(`// Type: ...` / `// --- SYSTEM PREFIX END --- //`)+ 严格 JSON」;注释只以整行 `//` 形式出现,故按行过滤可安全转成纯 JSON。"
|
||||
- "**jsonc 输出为纯 JSON(无注释、无标记行):** 按本条执行;运行期 `PrefixedJSONCProcessor` 依赖标记行的适配不在本计划范围(不改 `src/**` / `packages-user/**`)。"
|
||||
- "**`src/content` 共 23 个 `.jsonc`:** 均为「前缀注释(`// Type: ...` / `// --- SYSTEM PREFIX END --- //`)+ 严格 JSON」;注释只以整行 `//` 形式出现,故按行过滤可安全转成纯 JSON。`src/content/item.ts` 与 `texture/*.png` 不参与 jsonc 分支,走原样拷贝。"
|
||||
- "**jsonc 输出为纯 JSON(无注释、无标记行):** 按 D-96 执行。**INFO-1:** 这会与运行期 `PrefixedJSONCProcessor`(`packages-user/data-state/src/loader/jsoncProcessor.ts`,按 `prefixSplit` 切分、非 2 段时报 error 66)冲突;loader 适配经用户确认**不在本计划范围**,记录为已知后续项,不改 `src/**` / `packages-user/**`。"
|
||||
- "**`index.html` 现状:** 仍含 `<link id='mota-css' href='/styles.css'>`(D-97 要求去掉,但该文件不在 `files_modified`)。**INFO-2:** 本计划不产出 `styles.css`,构建后该 link 会 404;由用户处理,本计划不改 `index.html`。"
|
||||
- "**字体:** `src/content/font/` 仅有 `.list.jsonc`(引用 `testFont.ttf`),无字体二进制;fontmin 无源可压时跳过并告警,不阻断构建。"
|
||||
- "**渲染端可能构建失败:** `src/client/initialize.ts:14`(`'../constant'`)与 `src/client/plugin/shop/shop.tsx:9`(`'@/constant'`)仍指向已删除的 `src/constant.ts`,属用户 `src` WIP。按本条**不做特殊失败处理**:构建失败即失败,不由本计划的脚本层做 BLOCKED 分支;本计划的验证不实跑全量构建。"
|
||||
- "**渲染端可能构建失败:** `src/client/initialize.ts:14`(`'../constant'`)与 `src/client/plugin/shop/shop.tsx:9`(`'@/constant'`)仍指向已删除的 `src/constant.ts`,属用户 `src` WIP。按本条**不做特殊失败处理**:构建失败即失败,不由本计划的脚本层做 BLOCKED 分支;**INFO-4:** 本计划因此**不端到端运行全量 `build:game`**,验证只做插件单测 + 两个脚本的结构断言。"
|
||||
- "**`build-lib.ts` 保持不动:** 仅删除 `build-packages.ts`;`package.json` 不改,故 `build:packages` script 会指向已删除文件(按本条接受)。"
|
||||
- "**`manualChunks` 维持现状:** 当前 `antdv` + `audio` 两段不在本次适配范围,不改。"
|
||||
- "**行号可能漂移:** 门禁以「文件存在 + 结构/行为断言」为准,不硬编码行号。"
|
||||
- "**`moduleParsed` 可用:** Vite 的 `Plugin` 继承 Rollup 的 `PluginHooks`,`moduleParsed` 的 `this` 为 `PluginContext`,`this.getModuleInfo(info.id)` 可取到已解析模块;`ModuleInfo` 为正例导入的 `rollup` 类型(与 `build-game.ts` 现有 `import { OutputAsset, ... } from 'rollup'` 写法一致,dev.md 禁 `import type`)。"
|
||||
- "**行号可能漂移:** 门禁以「文件存在 + 结构/行为断言」为准,不硬编码行号;`build-game.ts` 的门禁按**函数名切片**而非固定行号。"
|
||||
prohibitions:
|
||||
- "不得在用户回复通过 Task 0 关卡之前执行任何任务或派发执行子代理。"
|
||||
- "**未经批准的命名一律不得使用:** `script/vite-plugin-data-isolation.ts` / `dataIsolationPlugin` / `mota-data-isolation` / `script/vite-plugin-data-isolation.test.ts`(OI-1)与 `buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats`(OI-2)。"
|
||||
- "**未经批准的命名一律不得使用:** `script/vitePluginDataIsolation.ts` / `dataIsolationPlugin` / `mota-data-isolation` / `script/vitePluginDataIsolation.test.ts`(OI-1)与 `buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats`(OI-2)。"
|
||||
- "**新文件不得用连字符命名:** 不得创建 `script/vite-plugin-data-isolation.ts` 或 `script/vite-plugin-data-isolation.test.ts`(dev.md 要求代码文件名为小驼峰)。"
|
||||
- "**`moduleParsed` 不得改回字符串签名:** 必须为 `moduleParsed(info: ModuleInfo)` 并用 `info.id` 匹配;不得写成 `moduleParsed(id: string)`。"
|
||||
- "**构建顺序不得倒置:** 必须先渲染端、后数据端;`buildData` 必须 `emptyOutDir: false`(不得清掉已产出的渲染端产物)。"
|
||||
- "**数据端附属产物不得混入渲染端目录:** 不得让 chunk / asset 落到 `assets/` 或 `chunks/`;入口必须是 `dist/data.js`(根),不得改成 `dist/data/data.js`。"
|
||||
- "**jsonc 必须去净注释:** 不得保留 `// --- SYSTEM PREFIX END --- //` 或 `// Type: ...`;不得改后缀(仍 `.jsonc`)。"
|
||||
- "**jsonc 必须去净注释:** 不得保留 `// --- SYSTEM PREFIX END --- //` 或 `// Type: ...`;不得改后缀(仍 `.jsonc`);**不得**为迁就 `PrefixedJSONCProcessor` 而保留注释或改 loader(INFO-1 只记录、不实施)。"
|
||||
- "**不得保留旧 pipeline:** 不得把 `checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize` / `generateResourceJSON` / `splitResource` / `transformAsync` / `archiver` / `compressing` / `source-code.zip` / `loadList.json` / `dist.zip` / 旧模板文件名写进源码(含注释与字符串)。"
|
||||
- "**不得改范围外文件:** 不改 `script/build-lib.ts`、`package.json`、`index.html`、`dev.md`、`src/**`、`public/**`、`vite.config.ts`、lockfile、`packages*/**`、`script/dev.ts`;不改既有注释 / jsDoc(重写两个脚本自身的注释属重写范围)。"
|
||||
- "**不得改范围外文件:** 不改 `script/build-lib.ts`、`package.json`、`index.html`、`dev.md`、`src/**`、`public/**`、`packages-user/**`、`vite.config.ts`、lockfile、`packages*/**`、`script/dev.ts`;不改既有注释 / jsDoc(重写两个脚本自身的注释属重写范围)。"
|
||||
- "**不得新增依赖、不改 lockfile。**"
|
||||
- "**不得引入特殊失败处理:** 不写 BLOCKED 分支、不写 baseline / 范围门禁、不检查外部文件或仓库状态;构建失败即以非零退出并打印错误。"
|
||||
- "**不得伪造门禁:** 不得手写 `dist` 产物或用手工文件冒充构建结果。"
|
||||
- "**不得伪造门禁:** 不得手写 `dist` 产物或用手工文件冒充构建结果;不得用「全量 `build:game` 已通过」之类的表述掩盖未端到端运行的事实(INFO-4)。"
|
||||
open_items:
|
||||
- "**OI-1(插件命名):** 提议新增 `script/vite-plugin-data-isolation.ts`,导出 `dataIsolationPlugin(): Plugin`,插件 `name: 'mota-data-isolation'`;测试文件 `script/vite-plugin-data-isolation.test.ts`。备选:`script/build-plugin-data-isolation.ts` / `forbidRenderDependencies`。**待批准**(D-04;测试文件按 AGENTS.md 免审批,仍一并报备)。"
|
||||
- "**OI-1(插件命名):** 提议新增 `script/vitePluginDataIsolation.ts`(**小驼峰,dev.md 强制**),导出 `dataIsolationPlugin(): Plugin`,插件 `name: 'mota-data-isolation'`;**必需**测试文件 `script/vitePluginDataIsolation.test.ts`。备选:`script/buildPluginDataIsolation.ts` / `forbidRenderDependencies`。**待批准**(D-04;测试文件按 AGENTS.md 免审批,仍一并报备)。"
|
||||
- "**OI-2(content 模块命名):** 提议 `script/build-resource.ts` 导出 `buildContent(options: IContentBuildOptions): Promise<IContentBuildStats>` 与 `minifyJSONC(text: string): string`;接口 `IContentBuildOptions`(`sourceDir` / `outputDir` / **`chars: string`**)、`IContentBuildStats`(`files` / `jsonc` / `fonts`)。备选:`processContent` / `compressJSONC`。**待批准**。"
|
||||
- "**已知后续项(不在本计划):** ① INFO-1 —— `dist/content/**/*.jsonc` 去净注释后,运行期 `PrefixedJSONCProcessor` 会因缺少 `prefixSplit` 标记而报 error 66,需另行适配 loader(经用户确认本计划不做);② INFO-2 —— `index.html` 的 `/styles.css` link 与样式接线由用户处理。"
|
||||
---
|
||||
<objective>
|
||||
Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎)**,最小范围,只动构建脚本。
|
||||
@ -89,7 +98,7 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
7. 不产出旧模板内容与 `source-code.zip` / `loadList.json` / `dist.zip`;移除 `main.js` babel 步骤。
|
||||
8. 最终 `dist` 根:`index.html` + Vite 常规渲染产物 + `data.js` + `content/` + `LICENSE`;数据端附属在 `dist/data/`。
|
||||
|
||||
**约束:** 不改构建顺序;只做上述适配;不做特殊失败处理(无 BLOCKED、无 baseline/范围门禁、无对外部文件或状态的检查);构建失败就失败。
|
||||
**约束:** 不改构建顺序;只做上述适配;不做特殊失败处理(无 BLOCKED、无 baseline/范围门禁、无对外部文件或状态的检查);构建失败就失败。**新代码文件名一律小驼峰**(dev.md),插件与测试文件同理。
|
||||
|
||||
**修改方式声明(AGENTS.md):** 本计划执行含文件写入与删除,由**派发子代理(`gsd-executor`)执行**,非内联执行;除 `files_modified` 外无任何写入。
|
||||
</objective>
|
||||
@ -116,10 +125,9 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:decision" gate="blocking-human">
|
||||
<name>Task 0: 预执行汇报关卡 —— 汇报最小范围、目标构建流程与待批准命名,等待「可以执行」</name>
|
||||
<precondition>`script/build-game.ts` / `script/build-resource.ts` 仍为旧引擎实现(含 `checkClientData`、babel、zip、资源分块、旧模板拷贝);`script/build-packages.ts` 与 `script/build-lib.ts` 均存在;`index.html` 含 `/styles.css` link 与 `/src/bootstrap.ts` module script;`vite.config.ts` 提供 `base: './'` 与 `@motajs/*` / `@user/*` / `@dp/*` / `@/` 别名;`src/content/**` 共 23 个 `.jsonc`、`src/content/font/` 无字体二进制;`script/vite-plugin-data-isolation.ts` 与测试文件尚不存在。</precondition>
|
||||
<name>Task 0: 预执行汇报关卡 —— 汇报最小范围、目标构建流程、已知边界与待批准命名,等待「可以执行」</name>
|
||||
<precondition>`script/build-game.ts` / `script/build-resource.ts` 仍为旧引擎实现(含 `checkClientData`、babel、zip、资源分块、旧模板拷贝);`script/build-packages.ts` 与 `script/build-lib.ts` 均存在;`index.html` 含 `/styles.css` link 与 `/src/bootstrap.ts` module script;`vite.config.ts` 提供 `base: './'` 与 `@motajs/*` / `@user/*` / `@dp/*` / `@/` 别名;`src/content/**` 共 23 个 `.jsonc`、`src/content/font/` 无字体二进制;`packages-user/data-state/src/loader/jsoncProcessor.ts` 仍按 `prefixSplit` 切分且报 error 66;`script/vitePluginDataIsolation.ts` 与 `script/vitePluginDataIsolation.test.ts` 尚不存在。</precondition>
|
||||
<files>(只读汇报,不修改任何文件)</files>
|
||||
<read_first>
|
||||
- .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)
|
||||
@ -127,38 +135,41 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
- script/build-resource.ts(`splitResource` 分块 zip 逻辑);script/build-packages.ts(待删除)
|
||||
- index.html(`<link href='/styles.css'>` 与 `/src/bootstrap.ts`);vite.config.ts(别名 / `base`)
|
||||
- src/data.ts;src/content/core.jsonc 与 src/content/font/.list.jsonc(注释 + 严格 JSON 形态)
|
||||
- AGENTS.md(执行前须获批、命名须先反馈、修改方式须预先声明)
|
||||
- packages-user/data-state/src/loader/jsoncProcessor.ts(`PrefixedJSONCProcessor`:`prefixSplit` 切分、非 2 段报 error 66)
|
||||
- AGENTS.md(执行前须获批、命名须先反馈、修改方式须预先声明);dev.md(代码文件名小驼峰)
|
||||
</read_first>
|
||||
<action>
|
||||
**停下并向用户汇报,等待回复「可以执行」后才继续;汇报本身只读。**
|
||||
|
||||
① **要解决的问题:** 旧构建脚本面向旧引擎(iife 数据端、体积启发式、babel 二次压缩、资源分块 zip、旧模板拷贝、源码 zip、`loadList.json`、`dist.zip`),无法适配新引擎的 `src/content` 与双端分离。
|
||||
|
||||
② **范围(最小):** 新增 `script/vite-plugin-data-isolation.ts`(+ 可选 `script/vite-plugin-data-isolation.test.ts`);重写 `script/build-game.ts` 与 `script/build-resource.ts`;删除 `script/build-packages.ts`;写 `09-21-SUMMARY.md`。**`build-lib.ts` / `package.json` / `index.html` / `dev.md` / `src/**` / `public/**` / `vite.config.ts` / lockfile / `packages*/**` / `script/dev.ts` 全部不动。**
|
||||
② **范围(最小):** 新增 `script/vitePluginDataIsolation.ts`(**小驼峰文件名**)及其**必需**测试 `script/vitePluginDataIsolation.test.ts`;重写 `script/build-game.ts` 与 `script/build-resource.ts`;删除 `script/build-packages.ts`;写 `09-21-SUMMARY.md`。**`build-lib.ts` / `package.json` / `index.html` / `dev.md` / `src/**` / `public/**` / `packages-user/**` / `vite.config.ts` / lockfile / `packages*/**` / `script/dev.ts` 全部不动。**
|
||||
|
||||
③ **目标构建流程(原样告知,不得改序):** 准备(清空/创建 `dist`、`_temp`)→ 渲染端(`index.html` 入口,常规输出到 `dist`)→ 数据端(`src/data.ts`,库模式 ESM,入口 `dist/data.js`,附属产物 `dist/data/`)→ 隔离检测(插件 `this.error`)→ content(`src/content` → `dist/content`,jsonc 纯 JSON 去净注释、后缀 `.jsonc`、fontmin、不分块不 zip)→ `LICENSE` 复制。最终 `dist` 根 = `index.html` + Vite 常规渲染产物 + `data.js` + `content/` + `LICENSE`。
|
||||
|
||||
④ **隔离检测口径:** 插件 `enforce: 'pre'` + `apply: 'build'`;`resolveId` 拦 specifier(`@user/client-*`、`@user/entry-client`、`@motajs/render*`、`@motajs/client*`、`@motajs/system`、`@motajs/audio`、`@motajs/legacy-*`、`vue`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue`);`moduleParsed` 按解析后路径兜底(`packages-user/client-*`、`packages/render*`、`packages/client*`、`packages/system`、`packages/audio`、`packages/legacy-*`、`src/client/**`、`node_modules/{vue,@vue,ant-design-vue,@ant-design}`);命中即 `this.error(...)`,错误含 `[mota-data-isolation]` / 命中模块 / `matched: <token>`。`checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize` 一并删除。
|
||||
④ **隔离检测口径:** 插件 `enforce: 'pre'` + `apply: 'build'`;`resolveId` 拦 specifier(`@user/client-*`、`@user/entry-client`、`@motajs/render*`、`@motajs/client*`、`@motajs/system`、`@motajs/audio`、`@motajs/legacy-*`、`vue`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue`);`moduleParsed(info: ModuleInfo)` 按 `info.id` 兜底(`packages-user/client-*`、`packages/render*`、`packages/client*`、`packages/system`、`packages/audio`、`packages/legacy-*`、`src/client/**`、`node_modules/{vue,@vue,ant-design-vue,@ant-design}`),错误消息经 `this.getModuleInfo(info.id)` 读取 `importers`;命中即 `this.error(...)`,错误含 `[mota-data-isolation]` / 命中模块 / `matched: <token>`。`checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize` 一并删除。
|
||||
|
||||
⑤ **content 口径:** jsonc 按行去掉 `//` 注释后 `JSON.parse` + `JSON.stringify`,输出纯 JSON(**含系统标记行在内的所有注释都去掉**),后缀仍 `.jsonc`;字体 fontmin glyph 子集(无字体二进制时跳过告警);不分块、不 zip。
|
||||
⑤ **content 口径与两项已知边界:** jsonc 按行去掉 `//` 注释后 `JSON.parse` + `JSON.stringify`,输出纯 JSON(**含系统标记行在内的所有注释都去掉**),后缀仍 `.jsonc`;字体 fontmin glyph 子集(无字体二进制时跳过告警);不分块、不 zip。
|
||||
- **已知边界 A(INFO-1):** 去净注释会移除 `// --- SYSTEM PREFIX END --- //`,而运行期 `PrefixedJSONCProcessor`(`packages-user/data-state/src/loader/jsoncProcessor.ts`)按 `prefixSplit` 切分原文、结果不是 2 段时报 error 66。**loader 适配经用户确认不在本计划范围**,此处只记录为已知后续项;**不因此保留注释、不改 `packages-user/**`、不改 `src/**`**。
|
||||
- **已知边界 B(INFO-2):** `index.html` 仍含 `<link id='mota-css' href='/styles.css'>`,本计划**不产出** `styles.css`,构建后该 link 会 404;`index.html` 与样式接线由用户处理,本计划不改。
|
||||
|
||||
⑥ **失败处理口径(原样告知):** **不做特殊失败处理** —— 无 BLOCKED 分支、无 baseline / 范围门禁、无对外部文件或仓库状态的检查;构建失败即打印错误并非零退出。本计划**不实跑全量构建**作为门禁(渲染端仍有两处用户 `src` WIP 的 stale import)。
|
||||
⑥ **失败处理与验证边界(原样告知):** **不做特殊失败处理** —— 无 BLOCKED 分支、无 baseline / 范围门禁、无对外部文件或仓库状态的检查;构建失败即打印错误并非零退出。**INFO-4:** 本计划**不端到端运行全量 `build:game`**(渲染端仍有两处用户 `src` WIP 的 stale import),验证只做插件单测 + 两个脚本的结构断言,该事实须写入 SUMMARY,不得表述为「全量构建已通过」。
|
||||
|
||||
⑦ **待批准命名(须用户确认):** OI-1(插件文件 / `dataIsolationPlugin` / `name: 'mota-data-isolation'` / 测试文件);OI-2(`buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats`)。未获批前不落码。
|
||||
⑦ **待批准命名(须用户确认):** OI-1(`script/vitePluginDataIsolation.ts` / `dataIsolationPlugin` / `name: 'mota-data-isolation'` / **必需**测试 `script/vitePluginDataIsolation.test.ts`,文件名小驼峰);OI-2(`buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats`)。未获批前不落码。
|
||||
|
||||
⑧ **验证方式:** 插件单测以真实 `vite.build()` 证明「拒渲染端 import / 通过纯数据端」;两个脚本做结构断言(无 `checkClientData`、数据端 `formats: ['es']` 且入口 `data.js` 在根、附属产物 `dist/data/`、jsonc 去注释、无 zip / 分块、无旧模板拷贝、数据子文件夹与渲染端分离);命令一律 `cmd /c "pnpm exec ..."`。
|
||||
⑧ **验证方式:** 插件单测(必需)以真实 `vite.build()` 证明「拒渲染端 import / 通过纯数据端」;两个脚本做**按函数切片**的结构断言(无 `checkClientData`、数据端 `formats: ['es']` 且入口 `data.js` 在根、附属产物 `dist/data/`、jsonc 去注释、无 zip / 分块、无旧模板拷贝、数据子文件夹与渲染端分离、`moduleParsed(info: ModuleInfo)` 签名);命令一律 `cmd /c "pnpm exec ..."`。
|
||||
|
||||
⑨ **不在本增量:** 不改 `src/**`(含两处 stale import);不改 `package.json`(故 `build:packages` 会指向已删除文件);不改 `dev.md` 构建表;不删 `public/**`;不新增依赖;不改 `manualChunks` / legacy 配置。
|
||||
⑨ **不在本增量:** 不改 `src/**`(含两处 stale import);不改 `packages-user/**`(含 `PrefixedJSONCProcessor` 适配);不改 `index.html` / 不产出 `styles.css`;不改 `package.json`(故 `build:packages` 会指向已删除文件);不改 `dev.md` 构建表;不删 `public/**`;不新增依赖;不改 `manualChunks` / legacy 配置。
|
||||
</action>
|
||||
<decision>是否按本计划(09-21,最小范围)执行「任务 11 · 构建重构」——新增数据端隔离 Vite 插件并注入数据端库模式 ESM 构建(入口 `dist/data.js`、附属 `dist/data/`)、重写 `build-resource`(`src/content` → `dist/content`,jsonc 纯 JSON 去净注释、fontmin、不分块不 zip)、重写 `build-game`(准备 → 渲染端 → 数据端 → content → LICENSE;去掉 babel / zip / 分块 / 旧模板拷贝)、删除 `build-packages.ts`;**仅改上述脚本,其余文件一律不动**。</decision>
|
||||
<decision>是否按本计划(09-21,最小范围)执行「任务 11 · 构建重构」——新增数据端隔离 Vite 插件(`script/vitePluginDataIsolation.ts`,`moduleParsed(info: ModuleInfo)`)并注入数据端库模式 ESM 构建(入口 `dist/data.js`、附属 `dist/data/`)、重写 `build-resource`(`src/content` → `dist/content`,jsonc 纯 JSON 去净注释、fontmin、不分块不 zip)、重写 `build-game`(准备 → 渲染端 → 数据端 → content → LICENSE;去掉 babel / zip / 分块 / 旧模板拷贝)、删除 `build-packages.ts`;**仅改上述脚本,其余文件一律不动**;并接受三项已知边界(jsonc 标记行移除与 `PrefixedJSONCProcessor` error 66、`/styles.css` 404、不端到端跑全量构建)。</decision>
|
||||
<context>
|
||||
AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。执行期含写操作与一个脚本删除。`src/content` 共 23 个 jsonc,均为「前缀注释 + 严格 JSON」,按行去注释即可安全转成纯 JSON。渲染端两处 stale import 仍存在(`src/client/initialize.ts:14` / `src/client/plugin/shop/shop.tsx:9`),属用户 WIP,本计划不修、也不据此设置构建门禁。
|
||||
AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。执行期含写操作与一个脚本删除。`src/content` 共 23 个 jsonc,均为「前缀注释 + 严格 JSON」,按行去注释即可安全转成纯 JSON(但会与 `PrefixedJSONCProcessor` 的标记行契约冲突,属已知后续项)。渲染端两处 stale import 仍存在(`src/client/initialize.ts:14` / `src/client/plugin/shop/shop.tsx:9`),属用户 WIP,本计划不修、也不据此设置构建门禁。
|
||||
</context>
|
||||
<options>
|
||||
<option id="execute-default">
|
||||
<name>可以执行(按本计划默认口径:OI-1 / OI-2 采用提议命名,构建流程与失败处理按上述口径)</name>
|
||||
<pros>最小改动面把旧 pipeline 换成新引擎产物;隔离检查由体积启发式升级为模块图硬报错;数据端入口与附属产物落点明确且与渲染端分离;content 压缩口径明确;验证全部可运行</pros>
|
||||
<cons>会删除 `build-packages.ts`(`package.json` 不改,故 `build:packages` 失效);引入若干待批准命名</cons>
|
||||
<name>可以执行(按本计划默认口径:OI-1 / OI-2 采用提议命名,构建流程、失败处理与已知边界按上述口径)</name>
|
||||
<pros>最小改动面把旧 pipeline 换成新引擎产物;隔离检查由体积启发式升级为模块图硬报错;数据端入口与附属产物落点明确且与渲染端分离;content 压缩口径明确;验证全部可运行且范围诚实</pros>
|
||||
<cons>会删除 `build-packages.ts`(`package.json` 不改,故 `build:packages` 失效);引入若干待批准命名;去净注释后运行期 jsonc 加载需另行适配 loader</cons>
|
||||
</option>
|
||||
<option id="revise">
|
||||
<name>修订 OI-1 / OI-2 命名或任一流程细节后重新汇报</name>
|
||||
@ -168,87 +179,93 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
</options>
|
||||
<resume-signal>回复「可以执行」即可;如需调整命名或流程细节,请在回复中一并指明。</resume-signal>
|
||||
<verify>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];for(const p of ['script/build-game.ts','script/build-resource.ts','script/build-packages.ts','script/build-lib.ts','index.html','vite.config.ts','src/data.ts','src/content/core.jsonc','src/content/texture/.list.jsonc','src/content/font/.list.jsonc','packages-user/data-state/src/loader/jsoncProcessor.ts']){if(!fs.existsSync(p))bad.push('missing '+p)}const jc=fs.readdirSync('src/content',{recursive:true}).filter(f=>String(f).endsWith('.jsonc')).length;if(jc!==23)bad.push('expected 23 jsonc, got '+jc);if(fs.existsSync('script/vite-plugin-data-isolation.ts'))bad.push('plugin file must not exist yet');const bg=fs.readFileSync('script/build-game.ts','utf8');if(bg.indexOf('checkClientData')<0)bad.push('build-game.ts is not the legacy implementation');if(bad.length>0){console.error('PRESTATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK prestate: legacy scripts in place, 23 jsonc, plugin absent')"</automated>
|
||||
<fails_when>non-zero exit,或 stderr 含 "PRESTATE FAILED":必需文件缺失 / jsonc 数量不是 23 / 插件文件已存在 / `build-game.ts` 已不是旧实现</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];for(const p of ['script/build-game.ts','script/build-resource.ts','script/build-packages.ts','script/build-lib.ts','index.html','vite.config.ts','src/data.ts','src/content/core.jsonc','src/content/texture/.list.jsonc','src/content/font/.list.jsonc','packages-user/data-state/src/loader/jsoncProcessor.ts']){if(!fs.existsSync(p))bad.push('missing '+p)}const jc=fs.readdirSync('src/content',{recursive:true}).filter(f=>String(f).endsWith('.jsonc')).length;if(jc!==23)bad.push('expected 23 jsonc, got '+jc);for(const p of ['script/vitePluginDataIsolation.ts','script/vitePluginDataIsolation.test.ts']){if(fs.existsSync(p))bad.push('must not exist yet: '+p)}const bg=fs.readFileSync('script/build-game.ts','utf8');if(bg.indexOf('checkClientData')<0)bad.push('build-game.ts is not the legacy implementation');const jp=fs.readFileSync('packages-user/data-state/src/loader/jsoncProcessor.ts','utf8');if(jp.indexOf('prefixSplit')<0||jp.indexOf('66')<0)bad.push('jsoncProcessor no longer matches INFO-1 (prefixSplit / error 66)');if(bad.length>0){console.error('PRESTATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK prestate: legacy scripts in place, 23 jsonc, plugin absent, jsoncProcessor marker-split confirmed')"</automated>
|
||||
<fails_when>non-zero exit,或 stderr 含 "PRESTATE FAILED":必需文件缺失 / jsonc 数量不是 23 / 插件文件(小驼峰命名)已存在 / `build-game.ts` 已不是旧实现 / `jsoncProcessor.ts` 不再按 `prefixSplit` + error 66 工作(INFO-1 前提变化)</fails_when>
|
||||
<human-check>用户明确回复「可以执行」(若就 OI-1 / OI-2 给出指定,按指定执行)</human-check>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 用户回复中明确包含「可以执行」
|
||||
- 汇报涵盖:要解决的问题、最小范围与「其余文件一律不动」清单、目标构建流程 8 步与「不改构建顺序」、隔离检测口径(specifier + 解析路径 + `matched:` token + 取代 `checkClientData`)、数据端 ESM 入口 `dist/data.js` 与附属 `dist/data/`、content 纯 JSON 去净注释、最终 `dist` 清单与排除项、**不做特殊失败处理(无 BLOCKED / 无 baseline 门禁 / 无外部状态检查)**、OI-1 / OI-2 命名、验证方式
|
||||
- 汇报涵盖:要解决的问题、最小范围与「其余文件一律不动」清单、目标构建流程 8 步与「不改构建顺序」、隔离检测口径(specifier + `moduleParsed(info: ModuleInfo)` 的 `info.id` 路径 + `matched:` token + 取代 `checkClientData`)、数据端 ESM 入口 `dist/data.js` 与附属 `dist/data/`、content 纯 JSON 去净注释、最终 `dist` 清单与排除项、**不做特殊失败处理(无 BLOCKED / 无 baseline 门禁 / 无外部状态检查)**、OI-1 / OI-2 命名、验证方式
|
||||
- 已说明删除 `build-packages.ts` 而 `package.json` 不改(`build:packages` 将失效)
|
||||
- 已说明本计划不改 `src/**`,渲染端两处 stale import 由用户处理,且不作为本计划门禁
|
||||
- **INFO-1 已报备:** 去净注释会移除 `PrefixedJSONCProcessor` 依赖的标记行(error 66),loader 适配经用户确认不在本计划范围,仅记录为已知后续项
|
||||
- **INFO-2 已报备:** `index.html` 仍引用 `/styles.css` 且本计划不产出该文件,由用户处理
|
||||
- **INFO-4 已报备:** 本计划不端到端运行全量 `build:game`,验证仅插件单测 + 结构断言
|
||||
</acceptance_criteria>
|
||||
<done>用户明确回复「可以执行」;在此之前未执行任何任务、未修改或删除任何仓库文件、未派发任何执行子代理</done>
|
||||
</task>
|
||||
|
||||
<task type="tracer">
|
||||
<name>Task 1: 数据端隔离插件 `dataIsolationPlugin` + 真实 `vite.build()` 单测</name>
|
||||
<precondition>Task 0 已闭环(用户回复「可以执行」,OI-1 已获批或按用户指定)。`vite` 与 `vitest` 可用;`script/vite-plugin-data-isolation.ts` 与测试文件尚不存在。</precondition>
|
||||
<files>script/vite-plugin-data-isolation.ts, script/vite-plugin-data-isolation.test.ts</files>
|
||||
<name>Task 1: 数据端隔离插件 `dataIsolationPlugin`(`script/vitePluginDataIsolation.ts`)+ 真实 `vite.build()` 单测</name>
|
||||
<precondition>Task 0 已闭环(用户回复「可以执行」,OI-1 已获批或按用户指定)。`vite` 与 `vitest` 可用;`script/vitePluginDataIsolation.ts` 与 `script/vitePluginDataIsolation.test.ts` 尚不存在(也不存在连字符命名的同名文件)。</precondition>
|
||||
<files>script/vitePluginDataIsolation.ts, script/vitePluginDataIsolation.test.ts</files>
|
||||
<read_first>
|
||||
- .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(D-94;D-04)
|
||||
- vite.config.ts(`@motajs/*` / `@user/*` / `@dp/*` / `@/` 别名,`base: './'`)
|
||||
- script/check-data-circular.test.ts(既有 `script/*.test.ts` 风格:显式 `import { describe, expect, it } from 'vitest'`;每个 `it` 前一行注释)
|
||||
- AGENTS.md(测试文件:每个 `it` 前一行注释);dev.md(命名、jsDoc、模块无副作用、非空判断约定)
|
||||
- script/build-game.ts 顶部(`import { OutputAsset, OutputChunk, RollupOutput } from 'rollup'` —— 类型正例导入,非 `import type`)
|
||||
- AGENTS.md(测试文件:每个 `it` 前一行注释);dev.md(代码文件名小驼峰、命名、jsDoc、模块无副作用、非空判断约定、限制 `?.`)
|
||||
</read_first>
|
||||
<action>
|
||||
(1) **新建 `script/vite-plugin-data-isolation.ts`(命名以 OI-1 获批结果为准):**
|
||||
- 自 `vite` 导入类型 `Plugin`(按 dev.md 不使用 `import type`)。
|
||||
(1) **新建 `script/vitePluginDataIsolation.ts`**(文件名小驼峰,dev.md 强制;导出命名以 OI-1 获批结果为准):
|
||||
- 自 `vite` 导入 `Plugin`,自 `rollup` 导入 `ModuleInfo`;两者均为普通 `import`(dev.md 不使用 `import type`,与 `build-game.ts` 既有写法一致)。
|
||||
- 模块内声明两组不导出的只读常量,每项 `{ test: RegExp, token: string }`:
|
||||
- **specifier 禁令**:`@user/client-`(token `@user/client-`)、`@user/entry-client`、`@motajs/render`、`@motajs/client`、`@motajs/system`、`@motajs/audio`、`@motajs/legacy-`、`vue` 与 `vue/*`(token `vue`)、`@vue/`、`ant-design-vue`、`@ant-design/icons-vue`。
|
||||
- **解析路径禁令**:`packages-user/client-`、`packages/render`、`packages/render-vue`、`packages/client`、`packages/system`、`packages/audio`、`packages/legacy-`、`src/client`、`node_modules/(vue|@vue|ant-design-vue|@ant-design)`。
|
||||
- 正则同时接受 `/` 与 `\\`(`[/\\]`);**不做**宽泛的 `client` 子串匹配(避免误判 `client.jsonc`)。
|
||||
- 导出 `dataIsolationPlugin(): Plugin`,返回 `name: 'mota-data-isolation'`、`enforce: 'pre'`、`apply: 'build'`、`resolveId(source, importer)`、`moduleParsed(id)`。
|
||||
- 导出 `dataIsolationPlugin(): Plugin`,返回 `name: 'mota-data-isolation'`、`enforce: 'pre'`、`apply: 'build'`、`resolveId(source, importer)`、`moduleParsed(info: ModuleInfo)`。
|
||||
- `resolveId`:对 `source` 做 specifier 禁令匹配;命中即 `this.error(...)`,消息以 `[mota-data-isolation]` 开头并包含 `matched: <token>`、命中模块与 `importer ?? '<entry>'`,说明「data-side build must not depend on render-side modules」;未命中返回 `null`。
|
||||
- `moduleParsed(id)`:对解析后绝对路径做路径禁令匹配;命中即 `this.error(...)`,消息同格式(含 `matched: <token>` 与 `this.getModuleInfo(id)?.importers`)。这一路覆盖别名解析、传递依赖、`src/client/**` 与 `node_modules` 的真实落点。
|
||||
- **`moduleParsed(info: ModuleInfo)`(W2,Rollup 的 `ModuleParsedHook` 形态,不得写成 `moduleParsed(id: string)`):** 用 `info.id`(解析后绝对路径)做路径禁令匹配;命中即 `this.error(...)`,消息同格式,含 `matched: <token>`、`module: <info.id>` 与 `importers:` 列表——导入者必须通过 `this.getModuleInfo(info.id)` 读取 `.importers`(`moduleParsed` 保证模块已注册;按 dev.md 限制 `?.`,用非空断言 `!` 或 `?? []` 兜底)。这一路覆盖别名解析、传递依赖、`src/client/**` 与 `node_modules` 的真实落点。
|
||||
- **不得**把数据端依赖(`@user/data-*`、`@motajs/common`、`@motajs/loader`、`@motajs/anon-tokyo`、`@motajs/types`、`@motajs/animate`)列入禁令。
|
||||
- 为导出函数与参数写 jsDoc;模块顶层零副作用。
|
||||
|
||||
(2) **新建 `script/vite-plugin-data-isolation.test.ts`:**
|
||||
(2) **新建 `script/vitePluginDataIsolation.test.ts`**(**必需产物**,不得标注为可选):
|
||||
- 用 `node:fs/promises`(`mkdtemp` / `mkdir` / `writeFile` / `rm`)与 `node:os` / `node:path`;每个用例独立临时目录,`afterEach` 清理。
|
||||
- 调用**真实** `vite.build()`:`configFile:false`、`root: <temp>`、`logLevel:'silent'`、`build: { write:false, minify:false, lib: { entry: <temp entry>, formats: ['es'], fileName: 'data' }, copyPublicDir:false }`、`plugins:[dataIsolationPlugin()]`。
|
||||
- 用例(每个 `it` 前一行注释):
|
||||
1. **拒渲染端 specifier:** entry `import 'vue';`,**不设任何 `resolve.alias`**;断言 rejects,消息含 `[mota-data-isolation]`、`matched: vue` 与 `data-side build must not depend on render-side`。
|
||||
2. **拒 `src/client` 渲染目录:** 构造 `<temp>/src/client/ui/fake.ts` 并相对导入;断言 rejects 且消息含 `matched: src/client`。
|
||||
3. **拒 `packages-user/client-*` 解析路径:** 构造 `<temp>/packages-user/client-modules/fake.ts` 并相对导入;断言 rejects 且消息含 `matched: packages-user/client-`。
|
||||
2. **拒 `src/client` 渲染目录:** 构造 `<temp>/src/client/ui/fake.ts` 并相对导入;断言 rejects 且消息含 `matched: src/client`。该用例触发的是 `moduleParsed(info)` 的 `info.id` 路径匹配(**不是** `resolveId` 的 specifier 匹配),用于锁定 `ModuleInfo` 签名。
|
||||
3. **拒 `packages-user/client-*` 解析路径:** 构造 `<temp>/packages-user/client-modules/fake.ts` 并相对导入;断言 rejects 且消息含 `matched: packages-user/client-`(同样走 `moduleParsed` 路径)。
|
||||
4. **纯数据端通过且产物为 ESM:** 构造 `<temp>/packages-user/data-common/fake.ts`(导出常量)并 re-export;断言 `build()` resolve,输出 chunk 的 `code` 含 `export`(ESM),不抛错。
|
||||
- 断言只依赖 rejects / resolve 与错误消息关键字,不依赖 chunk 文件名或顺序。
|
||||
</action>
|
||||
<behavior>
|
||||
- entry `import 'vue'` → rejects;消息含 `[mota-data-isolation]`、`matched: vue`、`data-side build must not depend on render-side`。
|
||||
- entry 导入 `<temp>/src/client/ui/fake.ts` → rejects;消息含 `matched: src/client`。
|
||||
- entry 导入 `<temp>/packages-user/client-modules/fake.ts` → rejects;消息含 `matched: packages-user/client-`。
|
||||
- entry 导入 `<temp>/src/client/ui/fake.ts` → rejects;消息含 `matched: src/client`(经 `moduleParsed(info.id)`)。
|
||||
- entry 导入 `<temp>/packages-user/client-modules/fake.ts` → rejects;消息含 `matched: packages-user/client-`(经 `moduleParsed(info.id)`)。
|
||||
- entry 导入 `<temp>/packages-user/data-common/fake.ts` → resolve;输出 chunk `code` 含 `export`。
|
||||
</behavior>
|
||||
<reversibility rating="reversible">仅新增一个插件文件与一个测试文件;删除它们即整体回退,未接线到构建脚本、未改既有文件。</reversibility>
|
||||
<verify>
|
||||
<automated>cmd /c "pnpm exec vitest run script/vite-plugin-data-isolation.test.ts"</automated>
|
||||
<automated>cmd /c "pnpm exec vitest run script/vitePluginDataIsolation.test.ts"</automated>
|
||||
<fails_when>non-zero exit,或任一断言失败(渲染端 specifier 未被拒 / `src/client` 未被拒 / `packages-user/client-*` 未被拒 / 缺对应 `matched:` token / 纯数据端构建失败 / 错误消息缺少插件标识或导入者)</fails_when>
|
||||
<automated>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('moduleParsed path check missing');if(t.indexOf('this.error(')<0)bad.push('does not abort via this.error');if(t.indexOf('mota-data-isolation')<0)bad.push('error tag missing');if(t.indexOf('matched:')<0)bad.push('error message lacks matched token');for(const tok of ['@user/client-','@user/entry-client','@motajs/render','@motajs/client','@motajs/system','@motajs/audio','@motajs/legacy-','vue','@vue/','ant-design-vue','@ant-design/icons-vue','packages-user/client-','packages/render','packages/render-vue','packages/client','packages/system','packages/audio','packages/legacy-','src/client']){if(t.indexOf(tok)<0)bad.push('forbidden set missing: '+tok)}}const tp='script/vite-plugin-data-isolation.test.ts';if(!fs.existsSync(tp))bad.push('test file missing');else{const s=fs.readFileSync(tp,'utf8');if(s.indexOf('build(')<0)bad.push('test does not call real vite build');if(s.indexOf('dataIsolationPlugin')<0)bad.push('test does not import the plugin');if(s.indexOf('matched:')<0)bad.push('test does not assert matched token');if(s.indexOf('src/client')<0)bad.push('test does not cover src/client')}if(bad.length>0){console.error('PLUGIN GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK plugin: enforce pre + specifier + resolved path + abort + matched token')"</automated>
|
||||
<fails_when>non-zero exit,或 stderr 含 "PLUGIN GATE FAILED"(插件文件缺失 / 未导出 `dataIsolationPlugin` / 非 `enforce: 'pre'` / 缺 `moduleParsed` / 缺 `this.error` / 缺错误标签或 `matched:` / 禁令缺关键 token / 测试文件缺失或未用真实 `vite.build()`)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];const p='script/vitePluginDataIsolation.ts';if(!fs.existsSync(p))bad.push('plugin file missing (must be camelCase)');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(!/moduleParsed\s*\(\s*info\s*:\s*ModuleInfo\s*\)/.test(t))bad.push('moduleParsed must take ModuleInfo (not a string id)');if(t.indexOf('info.id')<0)bad.push('moduleParsed must match on info.id');if(t.indexOf('getModuleInfo(info.id)')<0)bad.push('error message must read importers via getModuleInfo(info.id)');if(t.indexOf('importers')<0)bad.push('error message lacks importers');if(t.indexOf('this.error(')<0)bad.push('does not abort via this.error');if(t.indexOf('mota-data-isolation')<0)bad.push('error tag missing');if(t.indexOf('matched:')<0)bad.push('error message lacks matched token');for(const tok of ['@user/client-','@user/entry-client','@motajs/render','@motajs/client','@motajs/system','@motajs/audio','@motajs/legacy-','vue','@vue/','ant-design-vue','@ant-design/icons-vue','packages-user/client-','packages/render','packages/render-vue','packages/client','packages/system','packages/audio','packages/legacy-','src/client']){if(t.indexOf(tok)<0)bad.push('forbidden set missing: '+tok)}}const tp='script/vitePluginDataIsolation.test.ts';if(!fs.existsSync(tp))bad.push('test file missing (required, camelCase)');else{const s=fs.readFileSync(tp,'utf8');if(s.indexOf('build(')<0)bad.push('test does not call real vite build');if(s.indexOf('dataIsolationPlugin')<0)bad.push('test does not import the plugin');if(s.indexOf('matched:')<0)bad.push('test does not assert matched token');if(s.indexOf('src/client')<0)bad.push('test does not cover src/client');if(s.indexOf('packages-user/client-')<0)bad.push('test does not cover packages-user/client-*')}for(const old of ['script/vite-plugin-data-isolation.ts','script/vite-plugin-data-isolation.test.ts']){if(fs.existsSync(old))bad.push('kebab-case file must not exist: '+old)}if(bad.length>0){console.error('PLUGIN GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK plugin: camelCase files, enforce pre, moduleParsed(ModuleInfo), specifier + resolved path, abort via this.error, matched token')"</automated>
|
||||
<fails_when>non-zero exit,或 stderr 含 "PLUGIN GATE FAILED"(插件文件缺失或非小驼峰 / 未导出 `dataIsolationPlugin` / 非 `enforce: 'pre'` / `moduleParsed` 不是 `(info: ModuleInfo)` 签名 / 未按 `info.id` 匹配 / 未用 `getModuleInfo(info.id)` 取 `importers` / 缺 `this.error` / 缺错误标签或 `matched:` / 禁令缺关键 token / 测试文件缺失或未用真实 `vite.build()` / 存在连字符命名的同名文件)</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 插件按 OI-1 命名导出 `dataIsolationPlugin`;`enforce: 'pre'` + `resolveId` + `moduleParsed` + `this.error`;错误消息含 `[mota-data-isolation]` 与 `matched: <token>`
|
||||
- 新文件名为 `script/vitePluginDataIsolation.ts` 与 `script/vitePluginDataIsolation.test.ts`(小驼峰,dev.md);**不存在**连字符命名的同名文件;测试为必需产物
|
||||
- 插件按 OI-1 命名导出 `dataIsolationPlugin`;`enforce: 'pre'` + `resolveId` + `moduleParsed(info: ModuleInfo)` + `this.error`;错误消息含 `[mota-data-isolation]` 与 `matched: <token>`
|
||||
- `moduleParsed` 按 `info.id` 匹配,导入者经 `this.getModuleInfo(info.id)` 读取 `importers`;未写成字符串 id 签名
|
||||
- 禁令覆盖渲染端 specifier 与解析路径(含 `packages/render*` / `packages/client*` / `src/client`);未把数据端依赖列入
|
||||
- 单测以真实 `vite.build()` 证明拒 `vue`、拒 `src/client`、拒 `packages-user/client-*`、通过纯数据端并产出 ESM;每个 `it` 前一行注释
|
||||
- 未改任何既有文件;未接线到 `build-game.ts`(留给 Task 2)
|
||||
</acceptance_criteria>
|
||||
<done>插件与运行期单测就位:渲染端依赖被阻断且错误带 `matched:` token,纯数据端构建通过并产出 ESM;结构断言全绿;零既有文件改动</done>
|
||||
<done>插件与运行期单测就位:文件名小驼峰、`moduleParsed(info: ModuleInfo)` 按 `info.id` 拦截且错误带 `matched:` token 与 `importers`,纯数据端构建通过并产出 ESM;结构断言全绿;零既有文件改动</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: 重写 `build-resource` 与 `build-game`、删除 `build-packages.ts`、写 SUMMARY</name>
|
||||
<precondition>Task 1 已完成(`dataIsolationPlugin` 可用并通过单测);Task 0 已闭环(OI-2 已获批或按用户指定)。`src/content` 共 23 个 jsonc;`src/content/font/` 无字体二进制;`public/**` 仍存在(不删,仅通过 `copyPublicDir: false` 阻止进入 dist);`script/build-lib.ts` 保持原样。</precondition>
|
||||
<precondition>Task 1 已完成(`script/vitePluginDataIsolation.ts` 与 `script/vitePluginDataIsolation.test.ts` 就位且单测通过);Task 0 已闭环(OI-2 已获批或按用户指定)。`src/content` 共 23 个 jsonc;`src/content/font/` 无字体二进制;`public/**` 仍存在(不删,仅通过 `copyPublicDir: false` 阻止进入 dist);`script/build-lib.ts` 保持原样。</precondition>
|
||||
<files>script/build-resource.ts, script/build-game.ts, script/build-packages.ts, .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md</files>
|
||||
<read_first>
|
||||
- .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(D-93 / D-95 / D-96 / D-97 / D-98 / D-99 / D-100)
|
||||
- script/build-game.ts(旧编排:`buildClient` / `buildData` / `checkClientData` / `getAllChars` / `buildGame` 的 babel / archiver / compressing / 旧模板拷贝 / `dist.zip`)
|
||||
- script/build-resource.ts(旧 `splitResource` 分块 zip);script/utils.ts(`formatSize`)
|
||||
- index.html(入口 HTML,读而不改);src/content/**(23 个 jsonc 的注释形态);package.json(只读:`type: module` 决定 es 产物扩展名为 `.js`)
|
||||
- script/vitePluginDataIsolation.ts(Task 1 产物:导出 `dataIsolationPlugin`)
|
||||
- index.html(入口 HTML,读而不改;仍含 `/styles.css` link —— INFO-2);src/content/**(23 个 jsonc 的注释形态);package.json(只读:`type: module` 决定 es 产物扩展名为 `.js`)
|
||||
- AGENTS.md(禁改既有注释;修改方式须预先声明);dev.md(命名、interface、jsDoc、非空判断约定)
|
||||
</read_first>
|
||||
<action>
|
||||
(0) **只按 Task 0 获批口径执行;`src/**`、`package.json`、`index.html`、`dev.md`、`vite.config.ts`、`script/build-lib.ts` 一律不动。**
|
||||
(0) **只按 Task 0 获批口径执行;`src/**`、`packages-user/**`、`package.json`、`index.html`、`dev.md`、`vite.config.ts`、`script/build-lib.ts` 一律不动。**
|
||||
|
||||
(1) **重写 `script/build-resource.ts`(命名以 OI-2 为准):** 删除 `splitResource` / `CompressedUsage` / `LoadDataType` / `ResourceInfo` / `SplittedResource` / `ResourceContent` / `ResourcePath` / JSZip 与 `./types` 引用,改为 content 处理模块:
|
||||
- 导出 `IContentBuildOptions`:`readonly sourceDir: string`(`src/content`)、`readonly outputDir: string`(`dist/content`)、`readonly chars: string`(fontmin glyph 文本,**字符串**)。
|
||||
@ -262,9 +279,11 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
- 统计并返回 `files` / `jsonc` / `fonts`;按 dev.md 写 jsDoc;模块无顶层副作用;字面量非空判断用 `isNil`、对象用 `!`。
|
||||
|
||||
(2) **重写 `script/build-game.ts`:**
|
||||
- **新增导入:** `import { dataIsolationPlugin } from './vitePluginDataIsolation';`(文件名小驼峰,与 Task 1 产物一致)。
|
||||
- **保留**:`DEBUG_BUILD` / `DEBUG_REPLAY`、`ansi`、`logProgress`(步骤表改为:`1. 构建前准备` / `2. 构建渲染端` / `3. 构建数据端` / `4. 处理 content` / `5. 收尾`)、`buildClient` / `buildData` / `getAllChars` 名称、`distDir`、`formatSize` 报告。
|
||||
- **删除**:`RESOUCE_SIZE`、`checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize`、`generateResourceJSON` / `CompressedLoadList*`、`@babel/core` 与其变换、`archiver` / `createWriteStream` / `compressing`、`RequiredData` / `RequiredIconsData` 与 `public/project/*` 读取、`toCopy` 旧模板清单、源码 zip、资源地图 JSON、资源分块拷贝、`启动服务.exe` 拷贝、`dist.zip`、直接引用 `Fontmin`(移至 `build-resource.ts`)。
|
||||
- **`buildClient(outDir)`**:复用 `loadConfigFromFile` + `mergeConfig`;注入 `legacy(...)`(目标浏览器列表与 `renderModernChunks:false` 维持现状);`build.outDir = outDir`(`dist`)、`copyPublicDir: false`、`minify: !DEBUG_BUILD`、`emptyOutDir: false`;**不设置 `rollupOptions.input`**(默认以 `index.html` 为入口);`rollupOptions.external` 维持 `['@wasm-audio-decoders/opus-ml']`;`output` 维持 `format:'es'` / `entryFileNames` / `chunkFileNames: 'chunks/...'` / `assetFileNames: 'assets/...'`;`manualChunks` **维持现状,不改**。
|
||||
- **函数定义顺序(门禁按函数切片,必须遵守):** 自上而下保持 `async function buildClient(` → `async function buildData(` → (`getAllChars` 等辅助)→ `async function buildGame(`;顺序被改动会导致切片取空、门禁失败。
|
||||
- **`buildClient(outDir)`**:复用 `loadConfigFromFile` + `mergeConfig`;注入 `legacy(...)`(目标浏览器列表与 `renderModernChunks:false` 维持现状);`build.outDir = outDir`(`dist`)、`copyPublicDir: false`、`minify: !DEBUG_BUILD`、`emptyOutDir: false`;**不设置 `rollupOptions.input`**(默认以 `index.html` 为入口);`rollupOptions.external` 维持 `['@wasm-audio-decoders/opus-ml']`;`output` 维持 `format:'es'` / `entryFileNames` / `chunkFileNames: 'chunks/...'` / `assetFileNames: 'assets/...'`;`manualChunks` **维持现状,不改**;**不得**在本函数内注入 `dataIsolationPlugin`。
|
||||
- **`buildData(outDir, entry)`**:`build.outDir = outDir`(调用方传 `distDir`)、`copyPublicDir: false`、**`emptyOutDir: false`**、`minify: !DEBUG_REPLAY`、`lib: { entry, fileName: 'data', formats: ['es'] }`;`rollupOptions.output`:`entryFileNames: 'data.js'`、`chunkFileNames: 'data/[name].[hash].js'`、`assetFileNames: 'data/[name].[hash][extname]'` —— **入口落在 `dist/data.js`,附属产物落在 `dist/data/`**;`plugins: [dataIsolationPlugin()]`。
|
||||
- **`getAllChars()`:返回 `Promise<string>`**:读取 `dist` 下渲染端 `.js`(**跳过 `dist/data` 与 `dist/content`**)与 `src/content/**/*.jsonc` 的字符集(`Set<string>`),以 `[...chars].join('')` 返回字符串;无匹配文件时返回空字符串,不抛错。
|
||||
- **`buildGame()` 编排(顺序固定,不得倒置):**
|
||||
@ -277,26 +296,29 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
|
||||
(3) **删除 `script/build-packages.ts`**(`build-lib.ts` 保持不动)。
|
||||
|
||||
(4) **`09-21-SUMMARY.md`:** 记录范围(只动脚本)、插件扫描口径与错误行为、数据端产物落点(`dist/data.js` + `dist/data/`)、content 压缩口径(纯 JSON、去净注释、后缀 `.jsonc`)、最终 `dist` 清单与排除项、删除清单(`build-packages.ts`;`build-lib.ts` 未动;`package.json` 未改,`build:packages` 已失效)、命名获批情形、门禁结果(插件单测 + 两个脚本结构断言)、`open_items` 处置、已知边界(渲染端两处 stale import 属用户 WIP;未实跑全量构建)。
|
||||
(4) **`09-21-SUMMARY.md`:** 记录范围(只动脚本)、插件扫描口径(`resolveId` specifier + `moduleParsed(info: ModuleInfo)` 的 `info.id`)与错误行为、数据端产物落点(`dist/data.js` + `dist/data/`)、content 压缩口径(纯 JSON、去净注释、后缀 `.jsonc`)、最终 `dist` 清单与排除项、删除清单(`build-packages.ts`;`build-lib.ts` 未动;`package.json` 未改,`build:packages` 已失效)、命名获批情形、门禁结果(插件单测 + 两个脚本的**按函数切片**结构断言)、`open_items` 处置,并**明确记录三项已知边界**:
|
||||
- **INFO-1:** 去净注释与运行期 `PrefixedJSONCProcessor`(`prefixSplit` 切分、非 2 段报 **error 66**)冲突;loader 适配经用户确认不在本计划范围,列为已知后续项(需出现 `PrefixedJSONCProcessor` 与 `66` 字样)。
|
||||
- **INFO-2:** `index.html` 仍引用 `/styles.css`,本计划不产出该文件(构建后该 link 404),由用户处理(需出现 `styles.css` 字样)。
|
||||
- **INFO-4:** **未端到端运行全量 `build:game`**(渲染端有两处用户 WIP 的 stale import),验证仅为插件单测 + 结构断言;不得表述为「全量构建已通过」(需出现 `build:game` 字样)。
|
||||
</action>
|
||||
<reversibility rating="costly">删除 `build-packages.ts` 属仓库内结构性删除(可经 git 恢复);两个脚本为重写,`git checkout` 可回退;执行后 `dist` 布局改变,旧产物形态(资源分块 / `dist.zip`)不再可用。</reversibility>
|
||||
<verify>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];const t=fs.readFileSync('script/build-game.ts','utf8');for(const x of ['checkClientData','ClientDataLevel','getFileName','getFileSize','splitResource','generateResourceJSON','transformAsync','archiver','compressing','source-code.zip','loadList.json','dist.zip','editor.html','logo.png','styles.css','启动服务.exe','_server','_docs','libs']){if(t.indexOf(x)>=0)bad.push('legacy token: '+x)}if(t.indexOf('dataIsolationPlugin')<0)bad.push('missing dataIsolationPlugin');if(t.indexOf('buildContent')<0)bad.push('missing buildContent');if(t.indexOf('LICENSE')<0)bad.push('missing LICENSE copy');if(t.indexOf('copyPublicDir: false')<0)bad.push('missing copyPublicDir:false');if(t.indexOf('emptyOutDir: false')<0)bad.push('missing emptyOutDir:false (data build would wipe render output)');if(!/formats\s*:\s*\['es'\]/.test(t))bad.push('data build is not ESM-only');if(t.indexOf('entryFileNames: \'data.js\'')<0)bad.push('data entry must be data.js at dist root');if(t.indexOf('chunkFileNames: \'data/')<0||t.indexOf('assetFileNames: \'data/')<0)bad.push('data aux output must live in dist/data/');if(t.indexOf('src/content')<0||t.indexOf('src/data.ts')<0)bad.push('missing content/data entry paths');if(!/Promise<string>/.test(t))bad.push('getAllChars must return Promise<string>');const s=t.indexOf('async function buildGame(');const b=s>=0?t.slice(s):'';if(!b)bad.push('buildGame missing');const c=b.indexOf('await buildClient(');const d=b.indexOf('await buildData(');if(c<0||d<0)bad.push('buildClient/buildData not awaited');else if(c>d)bad.push('render must build before data');if(b.indexOf('emptyDir(')<0||b.indexOf('emptyDir(')>c)bad.push('dist must be cleared before the render build');if(bad.length>0){console.error('BUILD GAME GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK build-game: render then data, data.js at root, dist/data/ aux, no legacy pipeline')"</automated>
|
||||
<fails_when>non-zero exit,或 stderr 含 "BUILD GAME GATE FAILED"(仍含旧 pipeline token / 缺 `dataIsolationPlugin`、`buildContent`、`LICENSE`、`copyPublicDir: false`、`emptyOutDir: false` / 数据端非 ESM / 入口不是 `data.js` 在根 / 附属产物不在 `dist/data/` / 缺 `src/content` 或 `src/data.ts` / `getAllChars` 非 `Promise<string>` / 数据端先于渲染端构建 / 未在渲染端构建前清空 `dist`)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];const t=fs.readFileSync('script/build-game.ts','utf8');for(const x of ['checkClientData','ClientDataLevel','getFileName','getFileSize','splitResource','generateResourceJSON','transformAsync','archiver','compressing','source-code.zip','loadList.json','dist.zip','editor.html','logo.png','styles.css','启动服务.exe','_server','_docs','libs']){if(t.indexOf(x)>=0)bad.push('legacy token: '+x)}if(t.indexOf('vitePluginDataIsolation')<0)bad.push('must import dataIsolationPlugin from ./vitePluginDataIsolation');if(t.indexOf('buildContent')<0)bad.push('missing buildContent');if(t.indexOf('LICENSE')<0)bad.push('missing LICENSE copy');if(!/Promise<string>/.test(t))bad.push('getAllChars must return Promise<string>');if(t.indexOf('src/content')<0||t.indexOf('src/data.ts')<0)bad.push('missing content/data entry paths');const s=t.indexOf('async function buildClient(');const d=t.indexOf('async function buildData(');const g=t.indexOf('async function buildGame(');if(s<0||d<0||g<0||!(s<d&&d<g))bad.push('function order must be buildClient < buildData < buildGame (gate slices by function)');const client=s<0||d<0?'':t.slice(s,d);const data=d<0||g<0?'':t.slice(d,g);const game=g<0?'':t.slice(g);if(client.indexOf('copyPublicDir: false')<0)bad.push('buildClient slice missing copyPublicDir:false');if(client.indexOf('emptyOutDir: false')<0)bad.push('buildClient slice missing emptyOutDir:false');if(client.indexOf('dataIsolationPlugin')>=0)bad.push('dataIsolationPlugin must not be wired into buildClient');if(data.indexOf('copyPublicDir: false')<0)bad.push('buildData slice missing copyPublicDir:false');if(data.indexOf('emptyOutDir: false')<0)bad.push('buildData slice missing emptyOutDir:false');if(data.indexOf('dataIsolationPlugin')<0)bad.push('buildData slice missing dataIsolationPlugin');if(!/formats\s*:\s*\['es'\]/.test(data))bad.push('buildData slice is not ESM-only');if(!/entryFileNames\s*:\s*'data\.js'/.test(data))bad.push('buildData entry must be data.js at dist root');if(!/chunkFileNames\s*:\s*'data\//.test(data))bad.push('buildData aux chunks must live in dist/data/');if(!/assetFileNames\s*:\s*'data\//.test(data))bad.push('buildData aux assets must live in dist/data/');if(/chunkFileNames\s*:\s*'chunks\//.test(data)||/assetFileNames\s*:\s*'assets\//.test(data))bad.push('buildData must not write into render folders');const c=game.indexOf('await buildClient(');const dd=game.indexOf('await buildData(');if(c<0||dd<0)bad.push('buildClient/buildData not awaited in buildGame');else if(c>dd)bad.push('render must build before data');const ed=game.indexOf('emptyDir(');if(ed<0||ed>c)bad.push('dist must be cleared before the render build');if(game.indexOf('buildContent')<0)bad.push('buildGame must call buildContent');if(game.indexOf('LICENSE')<0)bad.push('buildGame must copy LICENSE');if(bad.length>0){console.error('BUILD GAME GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK build-game: function-scoped gates - render then data, data.js at root, dist/data/ aux, no legacy pipeline')"</automated>
|
||||
<fails_when>non-zero exit,或 stderr 含 "BUILD GAME GATE FAILED"(仍含旧 pipeline token / 未从 `./vitePluginDataIsolation` 引入插件 / 缺 `buildContent`、`LICENSE` / `getAllChars` 非 `Promise<string>` / 缺 `src/content` 或 `src/data.ts` / 函数定义顺序不满足切片前提 / `buildClient` 片段缺 `copyPublicDir: false` 或 `emptyOutDir: false` / 插件被错接到 `buildClient` / `buildData` 片段缺 `copyPublicDir: false`、`emptyOutDir: false`、`dataIsolationPlugin`、`formats: ['es']`、`entryFileNames: 'data.js'`、`chunkFileNames: 'data/`、`assetFileNames: 'data/` / `buildData` 写进 `chunks/` 或 `assets/` / 数据端先于渲染端构建 / 未在渲染端构建前清空 `dist`)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];const t=fs.readFileSync('script/build-resource.ts','utf8');for(const x of ['splitResource','JSZip','jszip','CompressedUsage','LoadDataType','SplittedResource','RequiredData','SYSTEM PREFIX']){if(t.indexOf(x)>=0)bad.push('legacy token: '+x)}for(const x of ['minifyJSONC','buildContent','IContentBuildOptions','IContentBuildStats','JSON.parse','JSON.stringify','Fontmin','glyph','.jsonc']){if(t.indexOf(x)<0)bad.push('missing: '+x)}if(!/chars\s*:\s*string/.test(t))bad.push('chars must be string');if(!/startsWith\s*\(\s*'\/\/'\s*\)/.test(t))bad.push('jsonc comment stripping by line missing');if(!/\.jsonc/.test(t))bad.push('jsonc suffix handling missing');if(bad.length>0){console.error('BUILD RESOURCE GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK build-resource: pure JSON minify + fontmin, no zip/split')"</automated>
|
||||
<fails_when>non-zero exit,或 stderr 含 "BUILD RESOURCE GATE FAILED"(仍含分块 / zip / 旧类型 token / 仍保留 `SYSTEM PREFIX` / 缺 `minifyJSONC`、`buildContent`、接口、`JSON.parse` + `JSON.stringify`、fontmin / `chars` 非 `string` / 缺按行去 `//` 注释)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];if(fs.existsSync('script/build-packages.ts'))bad.push('script/build-packages.ts must be deleted');if(!fs.existsSync('script/build-lib.ts'))bad.push('script/build-lib.ts must stay untouched');if(fs.existsSync('script/build-resource.test.ts'))bad.push('build-resource.test.ts is out of scope');if(!fs.existsSync('.planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md'))bad.push('09-21-SUMMARY.md missing');if(bad.length>0){console.error('FILES GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK files: build-packages deleted, build-lib untouched, SUMMARY written')"</automated>
|
||||
<fails_when>non-zero exit,或 stderr 含 "FILES GATE FAILED"(`build-packages.ts` 未删除 / `build-lib.ts` 被删或被改 / 新增了范围外测试文件 / SUMMARY 缺失)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];for(const p of ['script/vitePluginDataIsolation.ts','script/vitePluginDataIsolation.test.ts']){if(!fs.existsSync(p))bad.push('missing '+p)}for(const p of ['script/vite-plugin-data-isolation.ts','script/vite-plugin-data-isolation.test.ts']){if(fs.existsSync(p))bad.push('kebab-case file must not exist: '+p)}if(fs.existsSync('script/build-packages.ts'))bad.push('script/build-packages.ts must be deleted');if(!fs.existsSync('script/build-lib.ts'))bad.push('script/build-lib.ts must stay untouched');if(fs.existsSync('script/build-resource.test.ts'))bad.push('build-resource.test.ts is out of scope');const sp='.planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md';if(!fs.existsSync(sp))bad.push('09-21-SUMMARY.md missing');else{const sm=fs.readFileSync(sp,'utf8');for(const tok of ['PrefixedJSONCProcessor','66','styles.css','build:game']){if(sm.indexOf(tok)<0)bad.push('SUMMARY must record known boundary token: '+tok)}}if(bad.length>0){console.error('FILES GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK files: camelCase plugin present, build-packages deleted, build-lib untouched, SUMMARY records INFO-1/2/4')"</automated>
|
||||
<fails_when>non-zero exit,或 stderr 含 "FILES GATE FAILED"(小驼峰插件/测试文件缺失 / 存在连字符命名文件 / `build-packages.ts` 未删除 / `build-lib.ts` 被删或被改 / 新增了范围外测试文件 / SUMMARY 缺失或未记录 INFO-1(`PrefixedJSONCProcessor`、`66`)、INFO-2(`styles.css`)、INFO-4(`build:game`))</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `script/build-resource.ts` 重写为 content 模块:`minifyJSONC` 按行去掉 `//` 注释后输出单行纯 JSON(无系统标记行、后缀仍 `.jsonc`);`buildContent` 递归镜像 `src/content` → `dist/content`,jsonc 压缩、字体 fontmin glyph、其它原样拷贝、无字体时跳过告警;接口 `chars: string`;不再含分块 / zip / `JSZip`
|
||||
- `script/build-game.ts` 重写:准备(`dist` + `_temp` 清空一次)→ `await buildClient(distDir)`(`index.html` 默认入口、`copyPublicDir: false`、`emptyOutDir: false`)→ `await buildData(distDir, src/data.ts)`(`formats: ['es']`、`entryFileNames: 'data.js'`、chunk/asset 前缀 `data/`、`emptyOutDir: false`、注入 `dataIsolationPlugin`)→ `getAllChars`(`Promise<string>`)→ `buildContent` → 复制 `LICENSE`;旧 pipeline 标识符全部移除
|
||||
- `script/build-packages.ts` 已删除;`script/build-lib.ts`、`package.json`、`index.html`、`dev.md`、`src/**`、`public/**`、`vite.config.ts`、lockfile、`packages*/**`、`script/dev.ts` 均未改动
|
||||
- `script/build-game.ts` 重写:从 `./vitePluginDataIsolation` 引入 `dataIsolationPlugin`;准备(`dist` + `_temp` 清空一次)→ `await buildClient(distDir)`(`index.html` 默认入口、`copyPublicDir: false`、`emptyOutDir: false`,且**不**注入插件)→ `await buildData(distDir, src/data.ts)`(`formats: ['es']`、`entryFileNames: 'data.js'`、chunk/asset 前缀 `data/`、`emptyOutDir: false`、注入 `dataIsolationPlugin`)→ `getAllChars`(`Promise<string>`)→ `buildContent` → 复制 `LICENSE`;旧 pipeline 标识符全部移除
|
||||
- `script/build-packages.ts` 已删除;`script/build-lib.ts`、`package.json`、`index.html`、`dev.md`、`src/**`、`public/**`、`packages-user/**`、`vite.config.ts`、lockfile、`packages*/**`、`script/dev.ts` 均未改动
|
||||
- 无特殊失败处理:无 BLOCKED 分支、无 baseline / 范围门禁、无外部文件或仓库状态检查;构建失败即打印错误并非零退出
|
||||
- 结构与行为断言全绿(插件单测、`build-game` 结构、`build-resource` 结构、文件清单)
|
||||
- `09-21-SUMMARY.md` 已写(范围、插件口径、数据端产物落点、content 口径、dist 清单与排除项、删除清单与 `build:packages` 失效说明、命名获批、门禁结果、`open_items` 处置、已知边界)
|
||||
- 结构与行为断言全绿(插件单测、`build-game` 的**按函数切片**结构断言、`build-resource` 结构断言、文件清单断言)
|
||||
- `09-21-SUMMARY.md` 已写(范围、插件口径与 `ModuleInfo` 签名、数据端产物落点、content 口径、dist 清单与排除项、删除清单与 `build:packages` 失效说明、命名获批、门禁结果、`open_items` 处置、**INFO-1 / INFO-2 / INFO-4 三项已知边界**)
|
||||
</acceptance_criteria>
|
||||
<done>构建脚本完成新引擎适配:渲染端输出常规 `dist`,数据端产出 `dist/data.js`(ESM)与 `dist/data/` 附属产物,content 以纯 JSON 落到 `dist/content`,`LICENSE` 保留;旧 pipeline 与 `build-packages.ts` 移除;结构断言与插件单测全绿,结果写入 `09-21-SUMMARY.md`</done>
|
||||
<done>构建脚本完成新引擎适配:渲染端输出常规 `dist`,数据端产出 `dist/data.js`(ESM)与 `dist/data/` 附属产物(插件经 `moduleParsed(info: ModuleInfo)` 把关),content 以纯 JSON 落到 `dist/content`,`LICENSE` 保留;旧 pipeline 与 `build-packages.ts` 移除;按函数切片的结构断言与插件单测全绿,结果(含 INFO-1/2/4 已知边界)写入 `09-21-SUMMARY.md`</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
@ -305,45 +327,47 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| 写入/删除边界 | 含写操作与一个脚本删除;只允许 `files_modified` 白名单,`src/**` 零写入 |
|
||||
| 写入/删除边界 | 含写操作与一个脚本删除;只允许 `files_modified` 白名单,`src/**` / `packages-user/**` 零写入 |
|
||||
| 数据端 ↔ 渲染端 | 数据端独立包不得依赖渲染端;由模块图插件硬报错(取代体积启发式) |
|
||||
| content 输入 → dist | `src/content` 的 23 个 jsonc 经 `JSON.parse` 后重新序列化;格式非法会在构建期暴露 |
|
||||
| 数据端与渲染端产物目录 | `dist/data.js` + `dist/data/` 必须与 `dist/assets/`、`dist/chunks/` 分离 |
|
||||
| 依赖边界 | 不新增依赖、不改 lockfile、不改 `package.json` |
|
||||
| 命名边界 | 新文件 / 导出 / 接口须先经 Task 0 报备批准(D-04) |
|
||||
| 命名边界 | 新文件 / 导出 / 接口须先经 Task 0 报备批准(D-04);新文件名须小驼峰(dev.md) |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-09-21-01 | Tampering | 数据端构建被渲染端依赖污染(体积启发式漏判) | high | mitigate | 插件 `resolveId`(specifier)+ `moduleParsed`(解析路径,含 `src/client/**`)双路扫描并 `this.error` 终止;真实 `vite.build()` 单测覆盖拒 `vue` / 拒 `src/client` / 拒 `packages-user/client-*` / 纯数据端通过 |
|
||||
| T-09-21-02 | Tampering | 数据端产物写入 `dist` 时清空渲染端产物 | high | mitigate | `buildData` 强制 `emptyOutDir: false`;结构断言检查;`dist` 只在准备步骤清空一次,且先渲染后数据 |
|
||||
| T-09-21-03 | Tampering | 数据端附属产物混入渲染端 `assets/` / `chunks/` | medium | mitigate | `chunkFileNames` / `assetFileNames` 前缀 `data/`;入口 `entryFileNames: 'data.js'`;结构断言检查两处前缀 |
|
||||
| T-09-21-01 | Tampering | 数据端构建被渲染端依赖污染(体积启发式漏判) | high | mitigate | 插件 `resolveId`(specifier)+ `moduleParsed(info: ModuleInfo)`(`info.id` 解析路径,含 `src/client/**`)双路扫描并 `this.error` 终止;真实 `vite.build()` 单测覆盖拒 `vue` / 拒 `src/client` / 拒 `packages-user/client-*` / 纯数据端通过 |
|
||||
| T-09-21-02 | Tampering | 数据端产物写入 `dist` 时清空渲染端产物 | high | mitigate | `buildData` 强制 `emptyOutDir: false`;**函数切片**结构断言检查(放错位置即失败);`dist` 只在准备步骤清空一次,且先渲染后数据 |
|
||||
| T-09-21-03 | Tampering | 数据端附属产物混入渲染端 `assets/` / `chunks/` | medium | mitigate | `chunkFileNames` / `assetFileNames` 前缀 `data/`;入口 `entryFileNames: 'data.js'`;函数切片断言检查两处前缀且禁止 `chunks/`、`assets/` 前缀出现在 `buildData` 片段 |
|
||||
| T-09-21-04 | Tampering | jsonc 压缩残留注释或标记行 | medium | mitigate | `minifyJSONC` 按行过滤 `//` 后 `JSON.parse` + `JSON.stringify`;结构断言禁止 `SYSTEM PREFIX` 与要求 `startsWith('//')` 过滤 |
|
||||
| T-09-21-05 | Tampering | 从 `public/**` / 旧模板带进 dist(D-97 违规) | medium | mitigate | `copyPublicDir: false`;删除旧模板拷贝;结构断言禁止 `editor.html` / `logo.png` / `styles.css` / `启动服务.exe` / `_server` / `_docs` / `libs` |
|
||||
| T-09-21-06 | Tampering | 越界写入 / 改范围外文件 | medium | mitigate | `files_modified` 白名单 + prohibitions(`build-lib.ts` / `package.json` / `index.html` / `dev.md` / `src/**` 等一律不动);文件清单断言 |
|
||||
| T-09-21-07 | Tampering | 未经批准使用新命名 | medium | mitigate | Task 0 报备 OI-1 / OI-2;prohibitions 明列「未获批前不得落码」 |
|
||||
| T-09-21-06 | Tampering | 越界写入 / 改范围外文件 | medium | mitigate | `files_modified` 白名单 + prohibitions(`build-lib.ts` / `package.json` / `index.html` / `dev.md` / `src/**` / `packages-user/**` 等一律不动);文件清单断言 |
|
||||
| T-09-21-07 | Tampering | 未经批准使用新命名 / 连字符文件名 | medium | mitigate | Task 0 报备 OI-1 / OI-2;prohibitions 明列「未获批前不得落码」与小驼峰要求;文件清单断言禁止连字符命名文件存在 |
|
||||
| T-09-21-08 | Tampering | 字体缺失或格式非法导致 fontmin 抛错 | low | mitigate | 无字体二进制时跳过并告警;`buildContent` 仅在遇到字体文件时才调用 fontmin |
|
||||
| T-09-21-09 | Repudiation | 以「构建通过」掩盖未端到端运行的事实(INFO-4) | medium | mitigate | SUMMARY 必须含 `build:game` 字样并写明未端到端运行;文件清单断言校验该字样;prohibitions 禁止此类表述 |
|
||||
| T-09-21-SC | Tampering | npm/pip/cargo installs | high | mitigate | 不新增依赖、不改 `package.json` 依赖面与 lockfile;如需新增依赖须回 Task 0 报备获批 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- **Task 0:** 预执行状态(旧构建脚本 / 23 个 jsonc / 插件文件不存在)检查;人工关卡(用户回复「可以执行」;OI-1 / OI-2 如有指定按指定)。
|
||||
- **Task 1:** `dataIsolationPlugin` 结构(`enforce: 'pre'` + `resolveId` + `moduleParsed` + `this.error` + 禁令集合 + `matched:` token);真实 `vite.build()` 单测(拒 `vue` / 拒 `src/client` / 拒 `packages-user/client-*` / 纯数据端通过并产出 ESM)。
|
||||
- **Task 2:** `build-game` 结构断言(无旧 pipeline token;渲染端先于数据端;`dist` 在渲染前清空一次;数据端 ESM 且入口 `data.js` 在根、附属 `dist/data/`;`copyPublicDir: false` / `emptyOutDir: false`;`buildContent` + `LICENSE`;`getAllChars` 返回 `Promise<string>`);`build-resource` 结构断言(无分块 / zip;`minifyJSONC` 按行去注释 + `JSON.parse` / `JSON.stringify`;fontmin;`chars: string`);文件清单断言(`build-packages.ts` 已删、`build-lib.ts` 未动、SUMMARY 已写)。
|
||||
- **一致性:** `dist/content/**` 落点与 `src/data/constant.ts` 的 `content/core.jsonc` 一致;数据端 `data.js` 扩展名与 `package.json` 的 `type: module` 一致。
|
||||
- **无全仓门禁:** 不使用 `pnpm check:type` / `pnpm build` / 全量 `pnpm test:ci`(用户 WIP 存在历史类型错误);本计划不实跑全量构建。
|
||||
- **人工复核:** 结论写入 `09-21-SUMMARY.md`。
|
||||
- **Task 0:** 预执行状态(旧构建脚本 / 23 个 jsonc / 小驼峰插件文件不存在 / `jsoncProcessor` 仍按 `prefixSplit` + error 66 工作)检查;人工关卡(用户回复「可以执行」;OI-1 / OI-2 如有指定按指定);汇报须含 INFO-1 / INFO-2 / INFO-4 三项边界。
|
||||
- **Task 1:** `dataIsolationPlugin` 结构(小驼峰文件名、`enforce: 'pre'`、`resolveId`、`moduleParsed(info: ModuleInfo)` 按 `info.id`、`getModuleInfo(info.id)` 的 `importers`、`this.error`、禁令集合、`matched:` token);真实 `vite.build()` 单测(拒 `vue` / 拒 `src/client` / 拒 `packages-user/client-*` / 纯数据端通过并产出 ESM)。
|
||||
- **Task 2:** `build-game` **按函数切片**结构断言(切出 `buildClient` / `buildData` / `buildGame` 后分别断言:`buildClient` 含 `copyPublicDir: false` + `emptyOutDir: false` 且不含插件;`buildData` 含 `copyPublicDir: false`、`emptyOutDir: false`、`dataIsolationPlugin`、`formats: ['es']`、`entryFileNames: 'data.js'`、`chunkFileNames: 'data/`、`assetFileNames: 'data/` 且不含 `chunks/` / `assets/` 前缀;`buildGame` 中先 `emptyDir` 再 `await buildClient(` 再 `await buildData(`,并调用 `buildContent` 与复制 `LICENSE`;全文件无旧 pipeline token、`getAllChars` 返回 `Promise<string>`);`build-resource` 结构断言(无分块 / zip;`minifyJSONC` 按行去注释 + `JSON.parse` / `JSON.stringify`;fontmin;`chars: string`);文件清单断言(小驼峰插件与测试存在、无连字符同名文件、`build-packages.ts` 已删、`build-lib.ts` 未动、SUMMARY 已写且含 INFO-1/2/4 关键字)。
|
||||
- **一致性:** `dist/content/**` 落点与 `src/data/constant.ts` 的 `content/core.jsonc` 一致;数据端 `data.js` 扩展名与 `package.json` 的 `type: module` 一致;`build-game.ts` 的插件导入路径 `./vitePluginDataIsolation` 与 Task 1 产物文件名一致。
|
||||
- **无全仓门禁:** 不使用 `pnpm check:type` / `pnpm build` / 全量 `pnpm test:ci`(用户 WIP 存在历史类型错误);本计划**不端到端运行全量** `build:game`。
|
||||
- **人工复核:** 结论(含 INFO-1 / INFO-2 / INFO-4 已知边界)写入 `09-21-SUMMARY.md`。
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- **D-94:** 数据端构建由模块图插件把关(specifier + 解析路径双路),命中即 `this.error` 终止且错误含 `matched:` token;`checkClientData` 及体积启发式已删除。
|
||||
- **D-95:** 数据端库模式 `formats: ['es']`,入口产物 `dist/data.js`(dist 根),附属产物 `dist/data/`,与渲染端 `assets/` / `chunks/` 分离;`emptyOutDir: false`。
|
||||
- **D-96:** `src/content`(23 个 jsonc)→ `dist/content`;jsonc 压成纯 JSON、去掉所有注释(含系统标记行)、后缀仍 `.jsonc`;fontmin 压缩(无字体时跳过告警);不分块、不 zip。
|
||||
- **D-97:** `copyPublicDir: false`;dist 不含旧模板产物;`LICENSE` 复制到 `dist/LICENSE`;`index.html` 以默认入口产出常规渲染产物且文件本身不改。
|
||||
- **D-94:** 数据端构建由模块图插件把关(specifier + `moduleParsed(info: ModuleInfo)` 的解析路径双路),命中即 `this.error` 终止且错误含 `matched:` token 与 `importers`;`checkClientData` 及体积启发式已删除。
|
||||
- **D-95:** 数据端库模式 `formats: ['es']`,入口产物 `dist/data.js`(dist 根),附属产物 `dist/data/`,与渲染端 `assets/` / `chunks/` 分离;`emptyOutDir: false`;函数切片门禁可证明配置在正确位置。
|
||||
- **D-96:** `src/content`(23 个 jsonc)→ `dist/content`;jsonc 压成纯 JSON、去掉所有注释(含系统标记行)、后缀仍 `.jsonc`;fontmin 压缩(无字体时跳过告警);不分块、不 zip。**已知后续项已记录:** 去净注释与 `PrefixedJSONCProcessor`(error 66)的冲突不在本计划修复。
|
||||
- **D-97:** `copyPublicDir: false`;dist 不含旧模板产物;`LICENSE` 复制到 `dist/LICENSE`;`index.html` 以默认入口产出常规渲染产物且文件本身不改;**已知边界已记录:** `index.html` 仍引用 `/styles.css` 且本计划不产出该文件。
|
||||
- **D-93 / D-98 / D-99 / D-100:** `build-packages.ts` 删除;`build-game` 承担渲染端 + 数据端 + content;移除 `main.js` babel 二次压缩;不改 `package.json` / `dev.md` / `build-lib.ts`。
|
||||
- **边界:** 仅 `files_modified` 内的脚本与 SUMMARY 被写入;未改 `src/**` / `public/**` / `vite.config.ts` / lockfile / `packages*/**` / `script/dev.ts`;未回退用户 WIP。
|
||||
- **验证:** 插件单测 + 两个脚本结构断言 + 文件清单断言全部就位并跑到确定结论;无特殊失败处理与外部状态检查。
|
||||
- **命名(dev.md):** `script/vitePluginDataIsolation.ts` / `script/vitePluginDataIsolation.test.ts` 均为小驼峰;不存在连字符命名同名文件。
|
||||
- **边界:** 仅 `files_modified` 内的脚本与 SUMMARY 被写入;未改 `src/**` / `public/**` / `packages-user/**` / `vite.config.ts` / lockfile / `packages*/**` / `script/dev.ts`;未回退用户 WIP。
|
||||
- **验证:** 插件单测(必需)+ 按函数切片的结构断言 + 文件清单断言全部就位并跑到确定结论;无特殊失败处理与外部状态检查;**未端到端运行全量 `build:game` 的事实已写入 SUMMARY**。
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user