- 前端
- 后端
- Web框架
【免费下载链接】modern.js
A progressive web framework based on React and Rsbuild.
本文以 tests/skill/README.md 为核心,讲解 Modern.js 仓库中 Skills 端到端验证体系的完整设计:run.mjs/feature-enable.mjs两个入口如何把 fixture 复制到临时目录、驱动scan-project.mjs/migrate.mjs/enable.mjs等 skill 脚本执行真实改写,并围绕「provenance(来源结构不被改坏)、automated(应自动迁移)、manual(应进人工清单)、blocking(必须阻断且零改动)」四类断言验证行为边界。读完后,你将理解这套测试如何防止自动迁移脚本产生「假成功」或「半成品改写」,以及如何按仓库约定新增 fixture 回归用例。
一、tests/skill 的定位:验证 Skills 的端到端测试
tests/skill/README.md 对目录的定位只有一句话:「验证 Modern.js Skills 的端到端测试」。这里的 Skills 指 skills/ 目录下两个可被 Agent 调用的技能包:
- skills/modernjs-migrate-to-v3/:把 Modern.js 2.0 应用迁移到 3.0,优先做可安全自动化的依赖/配置/入口/import 改写,剩余复杂项收敛为人工清单;
- skills/modernjs-feature-enable/:在已有的 v3 应用里启用 BFF、SSG、styled-components 等可选功能(v3 已移除
modern new,该 skill 是其手动等价物)。
README 中给出的运行方式即整条验证链路的核心:
node tests/skill/run.mjs退出码非 0 表示有断言失败。实际目录里还有第二个入口 tests/skill/feature-enable.mjs,用于验证 feature-enable skill(BFF/SSG/Tailwind/自定义 Server 等能力的启用)。两个脚本共享同一套测试范式,下文分别展开。
README 还指出 fixtures/v2-app/ 这类 fixture 是一个 Modern.js v2 示例应用,覆盖关键 v2→v3 差异(v2 依赖 + plugin-tailwindcss、modern.config的dev.port/server.ssr.mode/html.appIcon、App.config/App.init/useRuntimeContext、@modern-js/runtime/bff|serverimport、自定义 server)。从当前 fixture 目录清单看,这一初始样例已演进为规模更大的分层 fixture 集:real-v2-*(真实形态 baseline)、v2-edge-*(边界/blocker)、legacy-v2-*(存量 2.0 形态)、v3-app-*/v3-workspace-*(feature-enable 用与负向用例),每个裁剪自真实项目或生成器输出的 fixture 都附带PROVENANCE.md说明来源。
二、migrate-to-v3 的 e2e 测试:run.mjs 的三层 fixture 与四类断言
tests/skill/run.mjs 头部注释完整声明了 fixture 分三层(详见各 fixture 的 PROVENANCE.md):
- A. 真实自动迁移 baseline:
real-v2-generator-app(generator 默认defineConfig形态),验证 v2→v3 自动迁移主路径; - B. origin/v2 integration 真实形态(
applyBaseConfig包装):real-v2-bff-hono/real-v2-server-config/real-v2-tailwindcss-v2,验证结构性迁移进 manual、不假成功; - C. 存量 / 防御 / blocker edge:
legacy-v2-app-config+ 各v2-edge-*; - D. 负向用例:
v3-workspace-app等 workspace 协议 + 无 v2 信号的项目,验证「阻断、不改文件」。
断言则按 provenance(来源结构不被改坏)、automated(应自动迁的内容)、manual(应进人工的内容)、blocking(应直接中止)分类。
2.1 测试骨架:复制 fixture → 跑 migrate → 断言 → 清理
run.mjs 的核心基础设施只有两个函数。check记录通过/失败计数并打印✓/✗(run.mjs L33-L41):
let pass = 0; let fail = 0; function check(name, cond) { if (cond) { pass += 1; console.log(` ✓ ${name}`); } else { fail += 1; console.log(` ✗ ${name}`); } }prepare把指定 fixture 复制到系统临时目录(fs.mkdtempSync(os.tmpdir(), 'mj-migrate-')),按需以node <repo>/skills/modernjs-migrate-to-v3/scripts/migrate.mjs <work> --to=3.0.0执行迁移,并返回读取辅助(run.mjs L44-L67):
function prepare(fixture, runMigrate = true) { const work = fs.mkdtempSync(path.join(os.tmpdir(), 'mj-migrate-')); tmpDirs.push(work); copyDir(path.join(HERE, 'fixtures', fixture), work); if (runMigrate) { execFileSync('node', [path.join(SCRIPTS, 'migrate.mjs'), work, '--to=3.0.0'], { encoding: 'utf8' }); } return { work, read: rel => fs.readFileSync(path.join(work, rel), 'utf8'), has: rel => fs.existsSync(path.join(work, rel)), report: () => JSON.parse(fs.readFileSync(path.join(work, '.agents/runs/modernjs-migrate/report.json'), 'utf8')), }; }这套骨架体现了 e2e 测试的关键设计:
- 测试对象是脚本的真实产物——断言全部基于迁移后临时目录里的文件内容(
modern.config.ts、package.json、src/App.tsx等)和.agents/runs/modernjs-migrate/report.json报告; - fixture 只读:所有改写发生在临时目录,fixture 本身永不被测试污染;
try/finally清理:脚本结尾遍历tmpDirs逐一fs.rmSync(run.mjs L1230-L1232),最后输出结果:N 通过 / M 失败,fail > 0时process.exit(1)(run.mjs L1227-L1228),与 README「退出码非 0 表示有断言失败」的约定一一对应。
报告结构本身也是被测契约:report.json含toVersion、targetVersionSource、changed(已自动改写项)、manual(需人工项)四个关键字段,这从 skills/modernjs-migrate-to-v3/scripts/migrate.mjs 结尾的报告写入逻辑可以得到印证。
2.2 A 层:默认目标版本与自动迁移主路径
A0 用例(run.mjs L70-L92)验证一个易被忽略的细节:未显式传--to时,migrate.mjs 默认解析 Modern.js 最新 v3 版本,而不是写死 3.0.0。测试通过环境变量注入避免依赖网络:
execFileSync('node', [path.join(SCRIPTS, 'migrate.mjs'), latest.work], { encoding: 'utf8', env: { ...process.env, MODERNJS_MIGRATE_LATEST_V3_VERSION: '3.9.9' }, }); // 断言:devDependencies['@modern-js/app-tools'] === '3.9.9' // 且 report.toVersion === '3.9.9'、 // report.targetVersionSource === 'env:MODERNJS_MIGRATE_LATEST_V3_VERSION'对应地,scan-project.mjs 所在目录的 migrate.mjs 中确实存在MODERNJS_MIGRATE_LATEST_V3_VERSION的读取逻辑,并在无法访问 npm registry 时中止而非回退到 3.0.0——这与 SKILL.md 中「不会回退到 3.0.0」的规则互为印证。
A 层主路径(real-v2-generator-app)的断言覆盖了 README 所说的「关键 v2→v3 差异」自动迁移主路径,逐条对应 SKILL.md 步骤 2 的自动完成清单:
| 断言项 | 验证内容 | 对应 fixture 输入 |
|---|---|---|
[provenance] 扫描判定为 v2 项目 | scan-project.mjs输出含(v2) | package.json 中@modern-js/app-tools: 2.71.1 |
[auto] @modern-js/app-tools 升到 3.0.0 | 依赖升级 | 同上 |
[provenance] 仍为 defineConfig | 未被改成applyBaseConfig | modern.config.ts |
[auto] appTools({ bundler }) → appTools() | v3 默认 Rspack,删除bundler参数 | fixture 配置plugins: [appTools({ bundler: 'rspack' })] |
[auto] 顶层 runtime 块已移除 + 合并进空 defineRuntimeConfig({}) | runtime: { router: true }迁入src/modern.runtime.ts | 同上 |
[routes] 标准约定式路由保留 page/layout,未生成 routes/index.tsx、entry.tsx、legacy-* | 约定式路由红线 | src/routes/{page,layout}.tsx |
[auto] 移除 v3 不支持的 modern new/upgrade scripts | 清理package.jsonscripts | fixture 的"new": "modern new"、"upgrade": "modern upgrade" |
[auto] manual 为空(默认 app 可全自动迁移) | 干净项目不应有假人工项 | — |
A2 用例(real-v2-generator-app-js)进一步验证CJSmodule.exports静态配置同样走自动迁移主路径,且不被误报「函数式/动态配置」。
2.3 B 层:applyBaseConfig 包装下的「不假成功」语义
B 层三个 fixture 均来自 origin/v2 integration 的真实形态(applyBaseConfig包装)。测试断言的是一套分级诚实语义(如 run.mjs L204-L237 的 B1):
- provenance:
applyBaseConfig(包装保留、runtime.router结构留在 config 内、不生成src/modern.runtime.ts——即结构性迁移「不半自动改坏」; - manual:report 的 manual 里必须明确出现「结构迁移未完成」+
applyBaseConfig字样; - automated:文件级安全改写照常发生(依赖升级、import 路径映射、tailwind 移除);
- 协议尊重:
workspace:*依赖保留不强升固定版本,并进人工清单「随 monorepo 升级」。
B2(real-v2-server-config)额外验证:server/index.ts里的@modern-js/runtime/server会被安全改写为@modern-js/server-runtime(import 路径是纯文本可无歧义替换项),但server/index.tshook →modern.server.ts的语义迁移与package.json的modernConfig.runtime都进人工清单。B3(real-v2-tailwindcss-v2)验证 tailwind 自动移除三连:删@modern-js/plugin-tailwindcss依赖、生成postcss.config.cjs、移除tailwindcssPlugin()调用与 import。
2.4 C 层:存量形态与 30+ 个 edge/blocker 用例
C0(legacy-v2-app-config)验证存量 2.0 形态的完整迁移面:dev.port→server.port、App.config抽取生成src/modern.runtime.ts、@modern-js/runtime/bff→@modern-js/plugin-bff/client并补依赖 + 在 config 里按[appTools(), bffPlugin()]顺序追加插件、server/index.ts→ 可构建的server/modern.server.ts骨架(含defineServerConfig)、React 18 下useRuntimeContext()→useContext(RuntimeContext)、html.appIcon字符串自动迁移为{ icons: [{ src, size }] }对象,同时App.init、自定义 server、ssr进人工清单。该 fixture 的 PROVENANCE.md 还说明了它存在的理由:App.config/App.init/自定义server/index.ts是 v2 分支真实存量结构,不作为主 happy path,仅作「存量形态自动迁移 + manual 边界」验证。
v2-edge-*系列则是对「不假成功、不改坏」红线的系统性回归,每个 fixture 对应一个已发现的风险点(目录名即语义),典型如:
- 不误迁:
v2-edge-devnested只含嵌套dev.client.port时不创建server.port(C3);v2-edge-tools-dev-nested的tools.dev.port不被误迁、report 也不把它标为自动迁移(C25); - 不重复:已有双引号
bffPluginimport 时不重复 import(C8);已有 react import 时不重复声明(C9);React default import 必须保留且useContext合并进同一 import(C11); - 不覆盖:已有非空
modern.runtime.ts不能被覆盖,原 runtime 作为注释追加待人工合并(C15v2-edge-runtime-conflict,注释文案「原 modern.config 顶层 runtime」); - 不假改写:
useRuntimeContext as 别名不改写、进人工清单(C12);BFF 无appToolsimport 且无法补 → 不写半成品plugins,进 manual(C14); - 注释/字符串免疫:注释里的
defineConfig/runtime:/dev: { port }不参与定位、原样保留,真实配置照常迁移(C18/C23);普通字符串里提到@modern-js/runtime/bff不改写、不补依赖(C22);而带 magic comment 的真实import()/require()必须迁移(C24); - 事务性阻断:
routes/index.tsx非法形态写盘前直接阻断(C30);pages/foo.tsx与pages/foo/index.tsx映射冲突时事务性零改动——src/pages保留、src/routes不创建、package.json未升级、无 report.json(C31); - 协议语义:workspace 项目补
plugin-bff沿用workspace:*而非写死 3.0.0(C19/C20);link:协议不能照搬给别的包 → 进 manual 提示手动添加(C21)。
C33(v2-real-comprehensive)是对齐真实项目迁移前形态的综合大用例,一次覆盖:移除@modern-js/plugin-server、outputv2 字段改名(cssModuleLocalIdentName→cssModules.localIdentName、disableSourceMap→sourceMap: false布尔取反)、tools.webpack→rspack/webpackChain→bundlerChain、自定义入口 function-decl bootstrap 改写为createRoot()/render()、缺根routes/layout时自动生成最小布局、用户自有legacy-*目录原样保留、dev 块整块移除不留悬挂逗号等,最终断言 report 对 custom server、resolveMainFields、devServer给出点名的人工项。
2.5 D 层:负向阻断——「不确定就不动」
两个负向用例验证的是最容易被忽视的一类行为:不该迁移时必须中止且零改动。以v3-workspace-app为例(run.mjs L657-L714):
scan-project.mjs非 0 退出、不写 context.json(ambiguous 阻断);- 直接跑
migrate.mjs二次保护:非 0 退出、modern.config.ts与package.json逐字节不变、不产生report.json、workspace:*不被强升固定版本。
D2(v3-workspace-app-string-literal)进一步收紧信号判定:配置里字符串字面量中写 legacy 配置示例不构成 v2 信号,scan 仍然阻断。这一行为在 skills/modernjs-migrate-to-v3/scripts/scan-project.mjs 中有对应实现:maskCommentsAndStrings(L43-L95)把注释和字符串内容替换为等长空白后再做结构/标识符信号匹配;v2-only 信号清单(顶层runtime、appTools({ bundler })、applyBaseConfig、plugin-tailwindcss、runtime/bff|serverimport、App.config/init、src/pages、自定义server/index等,L300-L325)明确排除了 v3 也存在的routes/modern.runtime.ts/appTools();workspace 协议 + 无任何 v2-only 信号时直接fail(L327-L338),产出物context.json落在<projectDir>/.agents/runs/modernjs-migrate/。migrate.mjs 侧另有独立的前置自保护(isWorkspaceProto+detectV2Signals,不依赖 scan),与 SKILL.md「前置自保护……不依赖 scan」的描述一致。
三、fixture 工程规范:为什么这些目录必须提交
tests/skill/fixtures/README.md 把 fixture 定性为「测试输入,性质类似 parser/compiler 的 golden cases」,并给出三条治理规则,值得作为同类 e2e 测试的工程参照:
- 为什么需要提交:迁移类 skill 靠结构输入防回归——
pages到routes、runtime冲突、workspace 协议、自定义 server、Tailwind import、BFF 导出形态等问题,只有真实文件结构才能稳定复现;每个 fixture 对应一个已发现的风险点,目录名里的edge、real、conflict、half表示覆盖的行为边界,删掉 fixture 会让对应回归只剩人工记忆;体积受控——只保留最小源码、配置和PROVENANCE.md,不提交node_modules、dist、.modern、.agents/runs等生成物。 - 新增准入:场景不能用现有 fixture 表达、且对应一个明确 bug/验收点/历史回归;只保留复现所需的最少文件;不提交安装/构建产物;裁剪自真实项目或生成器输出的要补
PROVENANCE.md。 - Reviewer 口径:数量多是因为 skill 的风险来自项目形态差异而不是单个函数分支;保留它们可以把用户实测问题固化成可重复验证的回归用例。
以 real-v2-generator-app/PROVENANCE.md 为例,它逐文件记录了来源:modern.config.ts来自 origin/v2 MWA generator 模板 handlebars、package.json来自 base-template、src/routes/{layout,page}.tsx来自 entry-generator 约定式路由默认模板,并明确该 fixture 的职责是「验证 v2→v3 自动迁移能力,核心结构不得被改成 v3 形态」。这种溯源机制让每个 golden case 的「存在理由」可审计。
四、feature-enable 的 e2e 测试:feature-enable.mjs
tests/skill/feature-enable.mjs 复用同一套「复制 fixture → 跑skills/modernjs-feature-enable/scripts/{scan,enable}.mjs→ 断言文件与 report」范式,验证目标是「从『未启用 BFF』迁到『可安装/可构建的 BFF 已启用』形态」。主要断言面:
- BFF 自动启用(
v3-app-no-bff):新增@modern-js/plugin-bff且版本与app-tools一致;configplugins追加bffPlugin()([appTools(), bffPlugin()]顺序、import 只 1 处);tsconfig增加@api/*别名(./api/lambda/*)并把api加入include,同时保留原有@/*别名;scaffoldapi/lambda/hello.ts(export const get);端到端示例接进真实页面——默认模板首页安全替换为import { get as hello } from '@api/hello'+useEffect,自定义业务首页则不覆盖、新建src/routes/bff-demo/page.tsx(D8 用例专门验证自定义 welcome 首页不被覆盖)。 - 幂等:第二次
enable bff --json必须changed.length === 0、manual 里提示「已启用」、bffPlugin()仍只 1 处;默认页已接入后再跑同样changed为空。 - 半启用修复:config 有
bffPlugin()调用但缺 import 绑定 → 补齐绑定且不重复加调用;已有api/lambda/index.ts→ import 走@api/index而非裸@api(alias 才能解析);export async function get形态的已有 API 同样被复用(D12)。 - 绑定解析边界:普通字符串里的伪 import 不算绑定;specifier 在但缺 export → 把 export 加进现有大括号;
import type不算 value 绑定,另插一条 value import 且 type import 原样保留;CJS(module.exports/require)配置插入const { bffPlugin } = require(...)绑定而不引入 ESM import(v3-app-cjs-config)。 - SSG 值语义:
output.ssg: false/undefined/数组值/ssgByEntries空对象、全 false、动态值都不算已启用(scan 判「未启用」),启用时只翻顶层真实ssg,字符串"ssg: false in a string"、注释、嵌套experimental: { ssg: false }原样保留;output值为数组/动态表达式 → 进 manual 不动文件;ssgByEntries含 true 才算已启用,且已启用文案要精确点名ssgByEntries(对应源码util.ts:117/adapterSSR.ts:214的语义)。 - scaffold 分层:
tailwindcss(Rsbuild 原生脚手架:装 tailwindcss/postcss/autoprefixer +tailwind.config.ts+postcss.config.cjs+@tailwindCSS,并自动以正确的相对路径import '../tailwind.css'接入src/routes/layout.tsx)与server(生成含middlewares/renderMiddlewares/plugins/onError字段的server/modern.server.ts骨架、无 TS 类型语法保证取消注释不 SyntaxError)的 report 必须是tier: 'scaffold'、complete: false——骨架自动、业务语义人工;microFrontend则是tier: 'manual',输出可执行 checklist 且未改任何文件。 --install的诚实失败(D13,v3-app-install-fail):安装失败时进程非 0 退出、report.install.ok === false且带approve-builds/手动安装等补救提示、stderr 进 manual,但源文件改动已落盘;重试路径下即使 enable 已幂等(changed=0)仍会执行 install 且失败仍非 0——不把失败当完成。
五、如何运行与在什么前提下适用
在仓库根目录执行(本仓库为只读环境,以下命令适用于你自己 clone 的副本):
node tests/skill/run.mjs # migrate-to-v3 端到端验证 node tests/skill/feature-enable.mjs # feature-enable 端到端验证两个脚本均自包含:不依赖 workspace 安装产物(fixture 明确「不是运行产物,也不会参与 workspace 安装」),全程在系统临时目录操作,finally中清理。适用前提:Node.js 需满足 scan-project.mjs 的MIN_NODE = [18, 20, 8]约束(该约束针对被测项目,测试脚本本身仅依赖node:child_process/fs/os/path标准库);run.mjsA0 用例通过MODERNJS_MIGRATE_LATEST_V3_VERSION环境变量注入版本,避免测试依赖 npm registry 网络。
六、小结:这套 e2e 设计可复用的要点
- 脚本产物即契约:断言不 mock,直接跑真实脚本(scan/migrate/enable),验证真实落盘文件 +
report.json;report 的changed/manual(以及 feature-enable 的tier/complete/install)分层本身就是被测试的行为规格。 - 四类断言正交:provenance(来源结构不被改坏)/ automated(该自动的必须自动)/ manual(该人工的必须进清单且文案点名)/ blocking(不确定的必须中止且事务性零改动——不改 config、不升级依赖、不产生 report)。
- fixture = golden case:每个目录名编码一个风险点(edge/real/conflict/half),PROVENANCE.md 记录裁剪来源,新增需满足「对应明确 bug/回归」的准入,把用户实测问题固化为可重复验证的回归。
- 注释/字符串/协议免疫:信号判定先做
maskCommentsAndStrings,import 走真实 specifier 扫描(importSpecifiers,覆盖import()/require()含 magic comment),workspace/link 协议依赖不强升不照搬——这些「负向断言」的占比甚至高于正向断言,是防止自动化脚本「假成功」的核心防线。
进一步阅读:skills/modernjs-migrate-to-v3/SKILL.md(迁移 skill 的完整步骤与人工清单映射)、skills/modernjs-feature-enable/SKILL.md(能力矩阵与执行纪律)、skills/modernjs-migrate-to-v3/scripts/scan-project.mjs(v2/v3 判定与 ambiguous 阻断实现)、skills/modernjs-migrate-to-v3/references/migrate-config.md 等人工项参考文档。
- 前端
- 后端
- Web框架
【免费下载链接】modern.js
A progressive web framework based on React and Rsbuild.
相关推荐
Symfony功能测试:端到端的自动化验证
Symfony功能测试:端到端的自动化验证 你还在手动测试Web应用的每个功能吗?面对频繁迭代的业务需求,重复的点击操作不仅耗时还容易出错。本文将带你掌握Sym
后端Web框架Material UI 端到端测试架构解析:从 Fixture 组件到 Playwright 自动化验证
Material UI 端到端测试架构解析:从 Fixture 组件到 Playwright 自动化验证 导读 本篇技术指南基于 Material UI 仓库的
前端UI组件设计系统Detox Copilot:用自然语言为 Detox 编写移动端端到端测试
Detox Copilot:用自然语言为 Detox 编写移动端端到端测试 导读 本文基于 Detox 官方博客发布公告,系统介绍 Detox Copilot(
测试移动开发质量保障开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考