| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 |
- /**
- * 验证**构建产物里的内联脚本语法正确** —— 守的是一个真实的盲区。
- *
- * 为什么需要它(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);
|