|
|
@@ -0,0 +1,88 @@
|
|
|
+/**
|
|
|
+ * 验证**构建产物里的内联脚本语法正确** —— 守的是一个真实的盲区。
|
|
|
+ *
|
|
|
+ * 为什么需要它(2026-09-24 实际踩到):`npm run build` **不会解析** index.html 里那些普通
|
|
|
+ * `<script>`(没有 type=module 的内联块),Vite 原样拷过去 —— 于是删代码时删出个不配对的
|
|
|
+ * `}`,构建照样绿,但页面一加载内联脚本就抛语法错误:`globalThis.hasSequence` 没置位、
|
|
|
+ * `sequenceLoaded` 没派发 → **加载遮罩永远不隐藏**,用户看到的就是一片"正在初始化,请稍候..."。
|
|
|
+ *
|
|
|
+ * 所以这条检查盯的是**产物**(dist/*.html):那里 `%VITE_*%` 已被替换成真值,
|
|
|
+ * 拿到的就是浏览器真正会执行的那段代码(源码里的 redirect.html 带占位符,本来就不是合法 JS,
|
|
|
+ * 不能用它做检查)。
|
|
|
+ *
|
|
|
+ * 怎么跑:
|
|
|
+ * npm run build && node harness/tools/verify-inline-scripts.mjs
|
|
|
+ */
|
|
|
+
|
|
|
+import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
|
+import { basename, join } from 'node:path';
|
|
|
+import vm from 'node:vm';
|
|
|
+
|
|
|
+const DIST = 'dist';
|
|
|
+let pass = 0;
|
|
|
+let fail = 0;
|
|
|
+const check = (name, ok, extra = '') => {
|
|
|
+ if (ok) {
|
|
|
+ pass++;
|
|
|
+ console.log(' ok ' + name);
|
|
|
+ } else {
|
|
|
+ fail++;
|
|
|
+ console.log(' FAIL ' + name + ' ' + extra);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+console.log('\n【1】dist 里的内联脚本能通过语法检查');
|
|
|
+
|
|
|
+if (!existsSync(DIST)) {
|
|
|
+ console.log(' ⏭ 没有 dist/ —— 先跑 `npm run build`');
|
|
|
+ process.exit(2);
|
|
|
+}
|
|
|
+
|
|
|
+const htmlFiles = readdirSync(DIST).filter((f) => f.endsWith('.html'));
|
|
|
+check(`dist 下有 html 产物(${htmlFiles.join(', ')})`, htmlFiles.length > 0);
|
|
|
+
|
|
|
+/** 抽出内联 `<script>`(跳过带 src= 的) */
|
|
|
+const extractInlineScripts = (html) =>
|
|
|
+ [...html.matchAll(/<script(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/gi)].map((m) => m[1]);
|
|
|
+
|
|
|
+for (const file of htmlFiles) {
|
|
|
+ const html = readFileSync(join(DIST, file), 'utf8');
|
|
|
+ const blocks = extractInlineScripts(html);
|
|
|
+ if (!blocks.length) {
|
|
|
+ console.log(` ⏭ ${file} 没有内联脚本`);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ blocks.forEach((code, index) => {
|
|
|
+ const label = `${file} 的第 ${index + 1} 块内联脚本`;
|
|
|
+ try {
|
|
|
+ // new vm.Script 只编译不执行 —— 语法错就抛,正好用来当检查
|
|
|
+ new vm.Script(code);
|
|
|
+ check(`${label} 语法正确(${code.length} 字符)`, true);
|
|
|
+ } catch (error) {
|
|
|
+ const message = error instanceof Error ? error.message : String(error);
|
|
|
+ check(`${label} 语法正确`, false, message);
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+console.log('\n【2】本仓库这次踩过的具体症状:占位符没被替换');
|
|
|
+{
|
|
|
+ // 产物里出现未替换的 `%VITE_*%` 说明那个变量没配 —— 轻则功能失效,重则像
|
|
|
+ // redirect.html 的 redirectMap 一样直接语法错误。这里顺带把它们报出来。
|
|
|
+ const leftovers = [];
|
|
|
+ for (const file of htmlFiles) {
|
|
|
+ const html = readFileSync(join(DIST, file), 'utf8');
|
|
|
+ for (const m of html.matchAll(/%VITE_[A-Z0-9_]+%/g)) leftovers.push(`${file}: ${m[0]}`);
|
|
|
+ }
|
|
|
+ // VITE_DMS_API 在生产构建里本来就未配置(见技术债 #9),单独提示、不算失败
|
|
|
+ const unexpected = leftovers.filter((item) => !item.includes('%VITE_DMS_API%'));
|
|
|
+ check('产物里没有被漏替换的 VITE_ 占位符(VITE_DMS_API 单独见技术债 #9)',
|
|
|
+ unexpected.length === 0, unexpected.join(' | '));
|
|
|
+ const dmsLeftover = leftovers.filter((item) => item.includes('%VITE_DMS_API%'));
|
|
|
+ if (dmsLeftover.length) {
|
|
|
+ console.log(` ℹ️ ${dmsLeftover.join(' | ')} —— 生产未配 DMS,属已知项(技术债 #9)`);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+console.log(`\n===== 通过 ${pass} 项,失败 ${fail} 项 =====`);
|
|
|
+process.exit(fail ? 1 : 0);
|