From 9b1ec36c709e7a9e27c47397d1ac2be17c192516 Mon Sep 17 00:00:00 2001 From: yanhui <2353496031@qq.com> Date: Thu, 22 Oct 2026 12:06:48 +0800 Subject: [PATCH] =?UTF-8?q?fix(router):=20=E8=A1=A5=E5=9B=9E=20BinReplenis?= =?UTF-8?q?hMonitor=20=E7=9A=84=20lazy=20=E5=A3=B0=E6=98=8E=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=85=A8=E7=AB=99=E7=99=BD=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现象:生产 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 个输入框),两个业务路由正常回到登录引导,无致命错误。 --- frontend/package.json | 3 +- frontend/scripts/verify-router-lazy.mjs | 70 +++++++++++++++++++++++++ frontend/src/router/index.jsx | 4 ++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 frontend/scripts/verify-router-lazy.mjs diff --git a/frontend/package.json b/frontend/package.json index 6b5c08b..1d159a8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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" diff --git a/frontend/scripts/verify-router-lazy.mjs b/frontend/scripts/verify-router-lazy.mjs new file mode 100644 index 0000000..ff7f704 --- /dev/null +++ b/frontend/scripts/verify-router-lazy.mjs @@ -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; + }); +} diff --git a/frontend/src/router/index.jsx b/frontend/src/router/index.jsx index a6ecd7d..6a023db 100644 --- a/frontend/src/router/index.jsx +++ b/frontend/src/router/index.jsx @@ -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 = ;