项目:vitest-playground | 版本:v1.1 | 生成日期:2026-10-06 | 依据:源码分析
本篇把散落在前文的"已知问题"集中立项:每条给出位置、证据、影响与建议,最后给出分阶段演进路线图。定位依据全部来自本次源码走读;docs/superpowers/plans/ 的历史方案文档(runner-hardening 已落地、container-isolation 仅有方案)是多项债务的出处。优先级判定用三个维度:是否影响正确性、是否影响安全水位、是否影响维护成本。
README.md 的"学习章节"小节全部链接。./src/pages/vitest-learn/basics、./src/pages/vitest-learn/structure 等,但 src/pages/vitest-learn/ 下只有 data.ts、chapter.tsx 等文件,不存在任何章节子目录(ls 与 00 篇目录树均确认);课程内容实际全部在 data.ts 单文件内。#第一章vitest-基础入门 纯文本清单)或直接指向线上路由 /vitest-learn/basics;同时删除"学习章节"里与 data.ts 现实矛盾的目录式结构暗示。半小时的纯文档改动,建议尽快做。tests/__mocks__/mermaid.ts(内容 export default {})、tests/__mocks__/raw.ts(内容 export default 'mock-md-content')。mermaid 导入、无 ?raw 导入;vitest.config.ts 未配置任何指向 __mocks__ 的 alias;vitest 的 __mocks__ 自动机制也需要显式 vi.mock 调用才会生效。两文件是 Ant Design Pro 模板时代的遗留(《00-项目总览》§8 已记)。.vitest-sandbox/(已 gitignore)。.vitest-runs/(vitestServerRunner.ts 第 21 行 RUNS_ROOT);.vitest-sandbox/ 内残留早期共享沙箱方案的样例文件,其 vitest.config.js 形态(coverage 写在顶层键)与现行 writeRunConfig(coverage 在 test 键内)不一致(《00-项目总览》§4 注释"仅作历史参考")。docs/superpowers/plans/ 即可。docs/superpowers/plans/2026-09-28-container-isolation.md(完整方案)对照 vitestServerRunner.ts(无任何实现)。VITEST_RUNNER_BACKEND=local|docker 双后端、resolveBackend 纯函数、spawnVitestDocker、Dockerfile(node:22-slim、非 root、baked node_modules 符号链接)与容器参数(--network none --memory 512m --cpus 1 --pids-limit 128 --read-only);源码与 tests/ 中无对应符号,RunInSandboxOptions 无 backend 字段。AGENTS.md 铁律 2 的"不要暴露公网"警告与 nginx 限流现状一起写入 runbook 醒目位置(现状已部分做到)。src/pages/vitest-learn/chapter.tsx 的 handleRunTest(codeToRun = showSolution && solution ? solution : code)与 success 分支的 setLessonProgress({status:'passed'})。computeBadges)的可信度——查看答案一键通关可刷满"测试大师";对个人学习是自律问题,对教学场景是考核有效性问题。handleRunTest 记进度前判断 codeToRun === currentLesson.solution 时不写 status:'passed'(或写独立的 status:'cheated' 态);更完整的做法是把 showSolution 事件传给服务端由服务端裁决,但那会改请求契约,收益不成比例。注意补测试(该行为目前无测试表达期望,见《09-测试与质量保障》§5.2-1)。src/pages/vitest-learn/runner.ts 第 52 行 const [isReady] = useState(true);。isReady 恒为 true,被 chapter.tsx 传给 CodeEditor 的运行按钮 disabled={!isReady}——永远不生效;AGENTS.md/00 篇记载其为 WebContainer 时代异步就绪检测的兼容垫片。isReady(连带 CodeEditor 的 prop 与 chapter.tsx 传参)或保留并在注释里写明"恒真,兼容保留"。倾向删除,一次 pnpm tsc 就能保证改动完整。src/pages/vitest-learn/CodeEditor.tsx 第 194 行结果面板占位文案:点击"运行测试"查看结果(首次运行需联网安装依赖)。node_modules/vitest/vitest.mjs,README.md 运行注意明确"依赖就绪后即可离线运行";"联网安装依赖"是 WebContainer 时代的残留语义。src/components/AppLayout.tsx 第 26-37 行:total 与 passed 均为 useMemo(fn, [])。localStorage 非响应式数据源,其他页面标记通过后回到挂载了 AppLayout 的页面时数字不变——只有路由跳转导致的重新挂载才刷新(《04-数据模型》§8 已记)。useMemo 无 version 机制,但首页通常整页重挂载,实际感知更弱)。location.pathname 进 useMemo 依赖(路由变化即重算,成本是每次导航重读 localStorage,规模下无感知);彻底方案是 progress.ts 提供订阅机制(storage 事件 + 内存通知)。当前规模建议前者。src/pages/vitest-learn/data.ts 第 34-36 行 export interface APIData { name: string }。vitestServerRunner.ts——spawnVitest 的 child.on('close', () => {...})(约 386-391 行)未捕获退出码;finishRun 调 parseVitestJson(raw, 0, coverageData)(约 410、419 行)硬编码 0;parseVitestJson 内 exitCode !== 0 && failed === 0 的提示分支(约 205-207 行)因此永不触发。close 回调改为 child.on('close', (code) => ...),把 code 穿透 finishRun 传入 parseVitestJson;补一条"套件级失败"集成用例(《09》§5.2-3)。改动半径:三个私有函数签名。src/pages/vitest-learn/progress.ts 第 26-28 行;触发点 chapter.tsx 的 handleCodeChange(每次击键写入)。loadProgress 有 try/catch(读取容错),saveProgress 的 localStorage.setItem 裸奔;进度 JSON 含全部课时完整代码(全量写策略见《10-性能设计与优化》§6)。handleCodeChange 的异常可能打断 React 事件处理;触发概率低(56 课时 × 数 KB 远小于 5MB 配额)但存在(隐私模式下配额可能更小 ⚠️ 待确认各浏览器行为)。saveProgress 包 try/catch,失败时 console.warn 一次(防日志风暴可做节流告警);与《10》§6 的 debounce 建议可同批做。vitestServerRunner.ts 的 parseBenchTable(duration: mean,mean 为 vitest bench 表格的秒值、亚毫秒小数)对照 CodeEditor.tsx 第 251 行渲染 ${Math.round(t.duration)}ms。duration: 0.0009(秒)对应表格 mean 列 0.0009;前端直接把它当毫秒取整显示——9.7 课时结果面板的耗时列多数显示 0ms 或 1ms,真实 mean(如 0.9ms)不可见。mean ms 是正确换算的(mean.toFixed(3) 按 vitest 语义直接拼 ms——注意 vitest 表格 mean 列单位就是 ms 级小数,此处 output 与 tests 的换算不一致 ⚠️ 待确认 vitest 4.1.8 bench mean 列的精确单位后统一)。duration: mean * 1000(若 mean 是秒),要么前端渲染对 bench 保留三位小数。补契约用例锁定。.eslintrc*/eslint.config.*/prettier 配置;src/pages/vitest-learn/progressPage.tsx 第 54 行残留 // eslint-disable-next-line react-hooks/exhaustive-deps 注释。ls 与配置文件清单(详见《14-配置与环境详解》§6);disable 注释暗示模板时代有 ESLint 而现在没有执行主体。useMemo([]) 问题正是 lint 类会捕获的);disable 注释指向不存在的检查器,语义悬空。三阶段的排序逻辑:第一阶段全部是零风险、半小时级的卫生改动,能立即降低接手者的理解成本,优先清掉;第二阶段处理"正确性与可信度"(exitCode、bench 单位、防刷)并补上最薄的测试缺口,其中防刷建议与组件测试同批做(互为验证);第三阶段是唯一需要立项的架构工作——容器化是安全演进的主线(方案已备好),可观测性与性能优化都应等容器化落定后再做(容器会改变超时与冷启动预算,先优化会白做)。每阶段完成的判定沿用项目既有门禁:pnpm tsc && pnpm test && pnpm build && pnpm check:lessons 四绿 + 涉及运行器的改动过《08-安全与权限设计》§7 安全清单。最后重申一套文档债务的处理约定:本套文档(00-15)在上述任何一项落地后需要同步修订对应条目,避免文档本身成为新的"问题 1"。