fix(router): 补回 BinReplenishMonitor 的 lazy 声明,修复全站白屏

现象:生产 https://ck.yydd.ai:8443 打开任意页面都是白屏,连登录页都进不去。
控制台报 `BinReplenishMonitor is not defined`,一个 /api 请求都没发出去。

根因:PR #74 新增「缺货补货监控」时,在 router/index.jsx 的路由表里写了
`element: renderLazyComponent(BinReplenishMonitor)`,但没有像其它 22 个页面那样
在文件顶部补 `const BinReplenishMonitor = lazy(() => import('../pages/BinReplenishMonitor'))`。
该行在模块顶层求值 → 加载即抛 ReferenceError → 整个 React 应用挂掉。
`vite build` 不会报错(未声明标识符在 JS 里是合法语法),所以一路构建通过、部署后白屏。
PR #75 只补了后端的 binReplenishRoutes 引入,前端的这处漏声明一直没人发现。

修复:
1. router/index.jsx 补上 lazy 声明,并留下注释说明「漏一个就是全站白屏」。
2. 新增 frontend/scripts/verify-router-lazy.mjs,静态扫描 router/index.jsx:
   凡是 renderLazyComponent(X) 用到的 X,必须在同文件有 `const X = lazy(` 声明,
   否则构建直接失败并指出缺少的组件名与行号。已挂到 `npm run build` 前置。

验证:
- 该检查对修复前的 origin/main 版本报错并准确指出
  `BinReplenishMonitor (router/index.jsx:143)`;对修复后版本通过(23 used / 23 declared)。
- eslint 通过;vite build 成功(4270 modules / 34s)。
- playwright 真渲染:修复前 /login 白屏 + pageerror;修复后 /login 完整渲染登录表单
  (123 字、3 个输入框),两个业务路由正常回到登录引导,无致命错误。
This commit is contained in:
yanhui 2026-10-22 12:06:48 +08:00
parent 03fd3f97f3
commit 9b1ec36c70
3 changed files with 76 additions and 1 deletions

View File

@ -7,8 +7,9 @@
"test": "node --test test/*.test.mjs",
"prebuild": "node ./scripts/write-build-version.mjs",
"dev": "node ./scripts/write-build-version.mjs && vite --configLoader native",
"build": "vite build && node ./scripts/verify-planning-entry.mjs",
"build": "node ./scripts/verify-router-lazy.mjs && vite build && node ./scripts/verify-planning-entry.mjs",
"check": "npm run lint && npm test && npm run build",
"verify:router-lazy": "node ./scripts/verify-router-lazy.mjs",
"verify:public-build": "node ./scripts/verify-public-build.mjs",
"lint": "eslint .",
"preview": "vite preview"

View File

@ -0,0 +1,70 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
import { pathToFileURL } from 'node:url';
/**
* 校验路由表里每个 renderLazyComponent(X) 用到的组件 X 都在同文件里
* 用 `const X = lazy(() => import(...))` 声明过。
*
* 为什么要这个检查(2026-10-22 生产事故):
* PR #74 新增「缺货补货监控」时,router/index.jsx 里写了
* `element: renderLazyComponent(BinReplenishMonitor)` 却漏了 lazy 声明。
* 这行在模块顶层执行 → 加载即抛 `ReferenceError: BinReplenishMonitor is not defined`
* → 整个 React 应用挂掉、**全站白屏,连登录页都渲染不出来**。
* 而 `vite build` 完全能构建成功(未声明标识符在 JS 里是合法语法),
* 所以必须在构建期用静态扫描拦住。
*/
export async function verifyRouterLazy(routerPath = path.resolve(process.cwd(), 'src/router/index.jsx')) {
const source = await readFile(routerPath, 'utf8');
const used = new Set();
for (const match of source.matchAll(/renderLazyComponent\s*\(\s*([A-Za-z_$][\w$]*)/g)) {
used.add(match[1]);
}
const declared = new Set();
for (const match of source.matchAll(/const\s+([A-Za-z_$][\w$]*)\s*=\s*lazy\s*\(/g)) {
declared.add(match[1]);
}
const missing = [...used].filter((name) => !declared.has(name)).sort();
if (missing.length > 0) {
const lines = missing.map((name) => {
const idx = source.indexOf(`renderLazyComponent(${name}`);
const line = idx === -1 ? '?' : source.slice(0, idx).split('\n').length;
return ` - ${name} (router/index.jsx:${line} 使用,缺少 const ${name} = lazy(() => import('...')) 声明)`;
});
throw new Error(
'router/index.jsx 有组件被 renderLazyComponent 使用但未声明为 lazy,' +
'这会导致运行时 ReferenceError 并让整站白屏:\n' +
lines.join('\n'),
);
}
return { routerPath, used: [...used].sort(), declared: [...declared].sort() };
}
async function main() {
const routerArg = process.argv[2];
const routerPath = routerArg
? path.resolve(process.cwd(), routerArg)
: path.resolve(process.cwd(), 'src/router/index.jsx');
const result = await verifyRouterLazy(routerPath);
process.stdout.write(
`Verified router lazy declarations: ${result.used.length} used, ${result.declared.length} declared ` +
`(${path.relative(process.cwd(), result.routerPath)})\n`,
);
}
const isDirectExecution = process.argv[1]
? pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url
: false;
if (isDirectExecution) {
main().catch((error) => {
process.stderr.write(`${error.message}\n`);
process.exitCode = 1;
});
}

View File

@ -25,6 +25,10 @@ const DailyInventory = lazy(() => import('../pages/DailyInventory'));
const StockSalesAnalysis = lazy(() => import('../pages/StockSalesAnalysis'));
const PickingBinSalesAnalysis = lazy(() => import('../pages/PickingBinSalesAnalysis'));
const ReturnsInventoryMonitor = lazy(() => import('../pages/ReturnsInventoryWarning/Monitor'));
// ⚠️ 每个在下面路由表里被 renderLazyComponent(...) 用到的页面都必须先在这里声明。
// 漏一个 = 模块顶层抛 ReferenceError(xxx is not defined)→ 整个应用白屏、连登录页都进不去。
// PR #74 就漏了 BinReplenishMonitor,导致生产 10-22 全站白屏(2026-10-22 修复)。
const BinReplenishMonitor = lazy(() => import('../pages/BinReplenishMonitor'));
const lazyLoadingElement = <PageLoadingFallback />;