跳转至

前端页面运行期冒烟(verify-page-runtime.mjs)

位置:deploy/nfos/tools/verify-page-runtime.mjs | 语言:node(ESM,.mjs) | 是否需要 root:否

工具性质:离线冒烟(最小 DOM 桩 + 真实共享层;不写任何文件) 运行前提:目标前端目录在场(<frontendDir>/<site>/index.html 与 <frontendDir>/shared/js/);路径全部来自命令行,不自动定位仓库根 部署状态:随仓库检出运行,不部署到 /opt/Cy/services/system.d/nfos/


用途

在没有浏览器(也没有无头 Chrome)的环境里,证明前端页面模块能真正执行并完成一次渲染,而不只是“文件部署正确 + 语法通过”。

源码头部说明的动因(逐字引用):

 * 为什么需要:服务器上没有浏览器,也没有无头 Chrome,所以此前只能证明
 *   “文件被正确部署、语法通过”,**不能**证明页面模块在浏览器里能真正执行。
 *   而历史上真实发生过的问题恰恰是运行期类型错误(例如 UI.table 的
 *   rowActions 不存在、UI.modal 的 fields 不受支持)—— 语法检查发现不了。

做法(源码头部逐字):

 * 做法:用极小 DOM/浏览器桩加载真实共享层(config/auth/api/store/ui)
 *   与目标页面模块,让它完成自注册与一次渲染,捕获任何运行期异常。
 *   这不是完整浏览器,但足以抓出“函数不存在 / 参数形状不对”这类错误。

关键行为:

环节 源码行为
脚本加载顺序 解析 <site>/index.html 里 <script src="..."> 的真实顺序(跳过 http(s): 与 // 开头的,去掉 ?v= 参数);解析不到时回落到内置顺序并打印 !! index.html 未解析到脚本顺序,回落到内置顺序
共享层解析 js/core/* 映射到 <frontendDir>/shared/js/*,其余按 <site>/ 下相对路径;已部署文件优先,缺失则回落到 shared/js
单页模式 传入 [pageFile] 时,/pages/ 下的其它页被跳过;未在 index.html 声明的页面文件会补扫 js/pages/ 目录
渲染 对注册的每个页面调用 def.render(host, ctxStub),thenable 结果会 await;异常被收集为 页面 <key> 渲染异常:<msg>
空站保护 页面注册数为 0 直接判错:页面注册数为 0(整站无可见页面):检查 index.html 脚本顺序 / 页面模块报错(对应 2026-09-15 整站空白事故)
登录组件 NF.loginGate 缺失、install() 抛错、prompt 不是函数 → 记入错误
网络 fetch 是桩:返回 {ok:true,status:200} 且 data: [],不联网;只统计调用次数并打印 fetch 调用记录:<N> 次

典型场景

  • 前端改动(页面模块、js/core/* 共享层、index.html 脚本顺序)之后的冒烟:部署到服务器后、无浏览器环境下的“能不能跑起来”验证;
  • 事故复现:index.html 把 app.js 排在 pages/*.js 之后导致整站空白(2026-09-15)这类顺序问题,本工具按真实声明顺序加载,因此能复现;
  • 单页排查:只指定一个 Page 文件,缩小到某个页面的渲染异常。

用法与参数

源码给出的用法(逐字引用,即无参数时打印到 stderr 的那一行):

用法: node verify-page-runtime.mjs <frontendDir> <site> [pageFile]

参数取 const [, , FE, SITE = 'online', PAGE] = process.argv;:

参数 类型 默认值 说明
<frontendDir> 字符串(目录路径) 必填(缺失即报错退出) 前端根目录,其下有 <site>/ 与 shared/js/(如仓库的 apps/web)
<site> 字符串(目录名) online 站点子目录名;同时写入 window.NF_PLATFORM
[pageFile] 字符串(文件名) 无(不传则跑全部页面) 只测 js/pages/ 下的这一个文件,跳过其它页;与 index.html 里 /pages/ 声明匹配时会保留声明加载

无参数时的默认行为:没有默认行为 —— 缺 <frontendDir> 时向 stderr 打印上述用法行并立即 process.exit(2)(实测确认)。

示例

从仓库根目录执行(站点取默认值时也必须显式给出 <frontendDir>):

node deploy/nfos/tools/verify-page-runtime.mjs apps/web online

只冒烟单个页面:

node deploy/nfos/tools/verify-page-runtime.mjs apps/web console dashboard.js

期望输出形态(末尾为结论):

  按 index.html 顺序加载 12 个脚本
  已加载共享层;注册页面数=...
  待渲染页面:... 个
  fetch 调用记录:... 次
  ── 运行期错误 ──
  (无)
PAGE_RUNTIME_SMOKE_OK
情形 期望退出码
无运行期错误(末行 PAGE_RUNTIME_SMOKE_OK) 0
有运行期错误(末行 PAGE_RUNTIME_SMOKE_FAILED (<N> 项),最多列 25 条) 1
缺 <frontendDir>(打印用法行) 2(实测确认)

用法行是打印到 stderr 的;判定门禁时应同时看 stderr 与退出码。

依赖与权限

  • 语言/版本:Node.js(ESM,读取 node:fs / node:path / node:vm)。
  • 第三方依赖:无(不使用 jsdom、不使用无头浏览器)。
  • 仓库/部署依赖:需要一份前端目录树(仓库的 apps/web/,或已部署到服务器的同名结构):至少要有 <site>/index.html,以及 shared/js/ 或 <site>/js/core/ 下的共享层文件;缺文件会被记为错误 缺少文件:<path>。不要求从仓库根执行(路径全由参数给出)。
  • 权限:不需要 root、不需要写权限、不需要网络(纯只读)。

风险与注意事项

  • 纯离线只读:不写文件、不起服务、不联网(fetch 为桩,返回空 data);沙箱里注册的 setTimeout / requestAnimationFrame 均可用,但 setInterval 被替换为返回 0 的空实现。
  • 不是浏览器:DOM 是极简桩,querySelector 返回新元素、querySelectorAll 返回空数组、getBoundingClientRect 返回固定值。因此它抓不到 CSS 布局/视觉/交互问题,只能抓“函数不存在 / 参数形状不对 / 渲染抛异常”。
  • 脚本确实会被执行:被加载的页面模块与共享层代码会在 vm 沙箱内真实运行,所以任何模块级副作用(例如在加载期发起的 fetch)都会发生 —— 好在 fetch 被替换为桩,不会真的出网。
  • index.html 顺序是权威:改动 index.html 的 <script> 顺序会直接改变本工具的加载顺序;若解析不到脚本,会回落内置顺序并打印提醒 —— 此时“通过”不代表线上顺序正确。
  • 注册数为 0 必 FAIL:这是有意设计的“不许掩盖事故”断言;如果某个站点确实没有页面模块,需要调整该断言的适用范围,而不是把错误忽略。
  • 页面 render 异常只记录前 25 条(errors.slice(0, 25)),错误很多时需分批排查。

回滚

本工具不部署到线上、不改线上系统,且不产出任何文件:

  • 无输出文件、无临时目录、无状态文件、无缓存;
  • 不改动被加载的前端文件(只读打开);
  • 无需清理。若只是运行过本冒烟,工作区比对结果应为空。