| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- /**
- * 验证:`.vue` 的 `<script>` 里**没有裸的 HTML 注释起始符**。
- *
- * 为什么要有这条(2026-09-28 实际踩过):
- * Vite 的依赖预扫描把 **`.vue` 也当 HTML**(`htmlTypesRE = /\.(html|vue|svelte|astro|imba)$/`),
- * 扫描前会先做一次 `raw.replace(/<!--.*?-->/gs, "<!---->")`(带 `s`,**可跨行**)。
- * 于是 script 里一个**不成对的** `<!--`,会和**后面任意一个 `-->`**(哪怕隔着几十行)
- * 配成"HTML 注释"被整段吃掉 —— 行数因此变少、后面的代码可能并进上一行的 `//` 注释,
- * esbuild 随即报 `Unexpected "."`,整个依赖扫描失败。
- *
- * 症状特点(所以必须单独有个脚本盯着):
- * - **`npm run build` 抓不到**:构建走 vue 插件路径,不做这步清扫
- * - **平时启动也看不到**:只在依赖缓存失效、真正重新扫描时炸一次(改 package.json/lock、
- * `npx vite --force`、换分支等)
- * - **`<template>` 里的 `<!-- … -->` 是合法的**,本脚本只查 `<script>` 区块,
- * 且只报"同行没有配对 `-->`"的那些(同行配对的会被替换成 `<!---->`,无害)
- *
- * 怎么跑(纯 node,不需要打包):
- * node harness/tools/verify-vue-script-html-comments.mjs
- * 退出码:0 = 干净;1 = 有裸起始符(会打印 file:line)
- */
- import { readdirSync, readFileSync, statSync } from 'node:fs';
- import { join } from 'node:path';
- const OPEN = '<!-' + '-';
- const CLOSE = '--' + '>';
- const SCRIPT_OPEN_RE = /<script\b[^>]*>/g;
- const SCRIPT_CLOSE = '</' + 'script>';
- const SKIP_DIRS = new Set(['node_modules', 'dist', '.git', 'harness']);
- /** 收集 src 下所有 .vue */
- const collectVueFiles = (dir) => {
- const out = [];
- for (const entry of readdirSync(dir)) {
- if (SKIP_DIRS.has(entry)) continue;
- const full = join(dir, entry);
- if (statSync(full).isDirectory()) out.push(...collectVueFiles(full));
- else if (entry.endsWith('.vue')) out.push(full);
- }
- return out;
- };
- const files = collectVueFiles('src');
- const offenders = [];
- let pairedButHarmless = 0;
- for (const file of files) {
- const text = readFileSync(file, 'utf8');
- // 1) 找出所有 <script> 区块的字符区间
- const ranges = [];
- SCRIPT_OPEN_RE.lastIndex = 0;
- let m;
- while ((m = SCRIPT_OPEN_RE.exec(text))) {
- const start = m.index + m[0].length;
- const end = text.indexOf(SCRIPT_CLOSE, start);
- ranges.push([start, end === -1 ? text.length : end]);
- }
- if (!ranges.length) continue;
- // 2) 在 script 区间内逐行找裸起始符
- let offset = 0;
- const lines = text.split('\n');
- for (let i = 0; i < lines.length; i += 1) {
- const line = lines[i];
- let idx = line.indexOf(OPEN);
- while (idx !== -1) {
- const abs = offset + idx;
- const inScript = ranges.some(([a, b]) => abs >= a && abs < b);
- if (inScript) {
- if (line.indexOf(CLOSE, idx + OPEN.length) === -1) {
- offenders.push({ file, line: i + 1, text: line.trim().slice(0, 100) });
- } else {
- pairedButHarmless += 1;
- }
- }
- idx = line.indexOf(OPEN, idx + OPEN.length);
- }
- offset += line.length + 1;
- }
- }
- console.log(`扫描 ${files.length} 个 .vue 文件的 <script> 区块`);
- console.log(` 同行已配对的起始符(会被替换成 <!---->,无害):${pairedButHarmless} 处`);
- if (offenders.length) {
- console.log(`\n❌ 发现 ${offenders.length} 处裸起始符 —— 会让 Vite 的依赖扫描吃掉后面的代码:`);
- for (const o of offenders) console.log(` ${o.file}:${o.line} ${o.text}`);
- console.log('\n修法:把字面量挪到 .ts(参考 src/utils/stream-xml-filter.ts 的 POLICY_TABLE_START),');
- console.log('或改写这段文字,别让它在 .vue 的 script 里出现。');
- process.exit(1);
- }
- console.log('\n✅ 通过:没有裸的 HTML 注释起始符');
|