feat(tools): 生成跨模块别名表并提供导入查询 CLI

This commit is contained in:
TermiNexus committed 2026-10-25 12:22:59 +08:00
1 parent b8fb22f401
commit f29ad2caaa
4 files changed
+62510

No files matched your search

+13
View File
@@ -488,6 +488,19 @@
—— 即车库 / 商店的物品配置容器,不是用户属性。 —— 即车库 / 商店的物品配置容器,不是用户属性。
- 下一步:给这 8 个 CC 写「CC → 动作」适配器(原包在 module_47243 的模型层里聚合后派发), - 下一步:给这 8 个 CC 写「CC → 动作」适配器(原包在 module_47243 的模型层里聚合后派发),
用户属性那条链路(`UserPropertiesCC` → `SetUserPropertiesAction`)已经可用。 用户属性那条链路(`UserPropertiesCC` → `SetUserPropertiesAction`)已经可用。
- 跨模块别名表(`tools/gen_module_imports.js`):
- 模块两种包装都解析:简单模块 `var x = i(<id>)`,核心模块
`!(function (p0…pN) { … })(t, n, i, i(<id1>), …)`(内层参数 p3… 对应尾部 require 列表)。
- 扫描 `alias = <depVar>.$_$.<exportName>`,产出 **126 模块 / 8159 条别名**,
并带 `ByExport` 反向索引(某个导出被哪些模块以什么别名消费)。
产物:`names/module_imports.json`。
- 查询 CLI `tools/find_imports.js`:
`node find_imports.js 10032::b1q`(谁导入了它)/ `--module 18334` / `--alias 18334 B_`。
实测 `10032::b1q` → 只有 `18334::B_`,即 `SetUserPropertiesAction` 的唯一消费点。
- 用途:定位「CC → 动作」的转换点。当前已知 CC 访问器
(`ju`/`Su`/`Eu`/`Iu`,对应 `BuyableCC.buyable`/`currencyId`、`ItemCategoryCC.category` 等)
是 module_85196 的内部函数、不导出,18334 也不直接读 CC 槽位,
说明转换经 DI 服务接口完成;下一步用别名表追该接口的实现方法。
- 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE, - 委派表里的已知形态:`shop` / `home` / `localBattleUserState` / `partner` 是内联 IIFE,
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个); 已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
`launchParams` 是纯透传(`(pass-through)`,无 reducer),已把字段收口为 `Map<String, String>`。 `launchParams` 是纯透传(`(pass-through)`,无 reducer),已把字段收口为 `Map<String, String>`。
File diff suppressed because it is too large. Load diff
+49
View File
@@ -0,0 +1,49 @@
// find_imports.js —— 查询跨模块别名表
//
// 用法:
// node find_imports.js 10032::b1q # 谁导入了某个导出(<depModule>::<exportName>)
// node find_imports.js --module 18334 # 某模块导入了什么
// node find_imports.js --alias 18334 B_ # 某模块里某个别名的来源
'use strict';
const fs = require('fs');
const path = require('path');
const P = path.resolve(__dirname, '..');
const Data = JSON.parse(fs.readFileSync(path.join(P, 'names', 'module_imports.json'), 'utf8'));
const Args = process.argv.slice(2);
if (Args[0] === '--module') {
const Id = Args[1];
const M = Data.Modules[Id];
if (!M) {
console.log('没有模块 ' + Id);
process.exit(1);
}
console.log(`module_${Id}(${M.Style}),导入 ${M.Imports.length} 条:`);
for (const I of M.Imports) console.log(` ${I.Alias} = ${I.Dep}.$_$.${I.Export}`);
process.exit(0);
}
if (Args[0] === '--alias') {
const Id = Args[1];
const Alias = Args[2];
const M = Data.Modules[Id];
if (!M) {
console.log('没有模块 ' + Id);
process.exit(1);
}
const Hits = M.Imports.filter((I) => I.Alias === Alias);
console.log(Hits.length ? JSON.stringify(Hits, null, 1) : '没找到别名 ' + Alias);
process.exit(0);
}
if (!Args[0]) {
console.log('用法:node find_imports.js <depModule>::<exportName> | --module <id> | --alias <id> <alias>');
process.exit(1);
}
const Key = Args[0];
const Consumers = Data.ByExport[Key] || [];
console.log(`${Key} 被 ${Consumers.length} 处导入:`);
for (const C of Consumers) console.log(' ' + C);
+130
View File
@@ -0,0 +1,130 @@
// gen_module_imports.js —— 跨模块别名表:module → {alias → (depModule, exportName)}
//
// 模块文件两种包装(`window.__modules__[id] = (t, n, i) => { … }`,`i` 是 require 函数):
// A. 简单模块:体内 `var x = i(<id>)` 直接拿到依赖模块对象;
// B. 核心模块:`!(function (p0…pN) { … })(t, n, i, i(<id1>), i(<id2>), …)`,
// 内层参数 p3…pN 依次对应尾部的 `i(<id>)` 调用。
// 两种风格的「导入」都写作 `alias = <depVar>.$_$.<exportName>`。
//
// 输出:names/module_imports.json
// { Note, Modules: { <id>: { Imports: [{ Alias, Dep, Export }] } },
// ByExport: { "<dep>::<export>": [ "<module>::<alias>" ] } }
//
// 用法:cd 3DTank/tools && node gen_module_imports.js
'use strict';
const fs = require('fs');
const path = require('path');
const P = path.resolve(__dirname, '..');
const SrcDir = path.join(P, 'src');
const OutPath = path.join(P, 'names', 'module_imports.json');
function ReadTopArgs(S) {
// 尾部:…})(t, n, i, i(85183), …);\n};
const T = S.replace(/\r/g, '');
const T2 = T.replace(/};\s*$/, '').replace(/\);\s*$/, '');
const Open = T2.lastIndexOf('})(');
if (Open < 0) return null;
const Raw = T2.slice(Open + 3);
const Args = [];
let Depth = 0, Cur = '';
for (let I = 0; I < Raw.length; I++) {
const C = Raw[I];
if (C === '(') Depth++;
else if (C === ')') Depth--;
if (C === ',' && Depth === 0) { Args.push(Cur.trim()); Cur = ''; continue; }
Cur += C;
}
if (Cur.trim()) Args.push(Cur.trim());
return Args;
}
const Modules = {};
const Stats = { Files: 0, StyleIIFE: 0, StylePlain: 0, NoImports: 0, Aliases: 0 };
const Files = fs.readdirSync(SrcDir).filter((F) => /^module_\d+\.js$/.test(F)).sort();
for (const F of Files) {
Stats.Files++;
const S = fs.readFileSync(path.join(SrcDir, F), 'utf8').replace(/\r/g, '');
const IdMatch = /^;\(window\.__modules__[^)]*\)\[(\d+)\]/.exec(S);
const Id = IdMatch ? IdMatch[1] : F.match(/(\d+)/)[1];
// 依赖变量 → 模块 id
const DepVar = new Map();
// 内层参数风格(B)
const Head = /=\s*\(\w+,\s*\w+,\s*\w+\)\s*=>\s*\{\s*!\(function\s*\(([^)]*)\)/.exec(S);
if (Head) {
const Params = Head[1].split(',').map((x) => x.trim()).filter(Boolean);
const Args = ReadTopArgs(S);
if (Args && Args.length === Params.length) {
for (let I = 3; I < Args.length; I++) {
const M = /^\w+\(\s*(\d+)\s*\)$/.exec(Args[I]);
if (M) DepVar.set(Params[I], M[1]);
}
}
}
// 体内 inline require(A 与 B 通用):var/let x = i(<id>)
const InlineRe = /(?:var|let|const|,)\s+([\w$]+)\s*=\s*\w+\(\s*(\d+)\s*\)/g;
let M;
while ((M = InlineRe.exec(S))) DepVar.set(M[1], M[2]);
// alias = depVar.$_$.export
const Imports = [];
const DepVars = [...DepVar.keys()];
if (DepVars.length) {
const Re = new RegExp('([\\w$]+)\\s*=\\s*(' + DepVars.map((V) => V.replace(/\$/g, '\\$')).join('|') + ')\\.\\$_\\$\\.([\\w$]+)', 'g');
let A;
while ((A = Re.exec(S))) {
Imports.push({ Alias: A[1], Dep: DepVar.get(A[2]), Export: A[3] });
}
}
// 去重
const Seen = new Set();
const Unique = Imports.filter((I) => {
const K = I.Alias + '|' + I.Dep + '|' + I.Export;
if (Seen.has(K)) return false;
Seen.add(K);
return true;
});
if (Head) Stats.StyleIIFE++;
else Stats.StylePlain++;
if (!Unique.length) Stats.NoImports++;
Stats.Aliases += Unique.length;
Modules[Id] = { File: F, Style: Head ? 'iife' : 'plain', Imports: Unique };
}
// 反向索引:"<dep>::<export>" → ["<module>::<alias>"]
const ByExport = {};
for (const [Id, Info] of Object.entries(Modules)) {
for (const I of Info.Imports) {
const K = I.Dep + '::' + I.Export;
(ByExport[K] = ByExport[K] || []).push(Id + '::' + I.Alias);
}
}
fs.writeFileSync(
OutPath,
JSON.stringify(
{
Note:
'跨模块别名表:每个模块的 `alias = <depVar>.$_$.<exportName>`。' +
'依赖变量来自内层 IIFE 参数(对应尾部 i(<id>) 列表)或体内 `var x = i(<id>)`。' +
'ByExport 为反向索引,供「某个导出被谁消费」查询。',
Stats,
Modules,
ByExport,
},
null,
1,
),
'utf8',
);
console.log(
`模块 ${Stats.Files}(iife ${Stats.StyleIIFE} / plain ${Stats.StylePlain}),` +
`别名 ${Stats.Aliases} 条,无导入模块 ${Stats.NoImports} 个。`,
);