feat(tools): 生成跨模块别名表并提供导入查询 CLI
This commit is contained in:
1 parent
b8fb22f401
commit
f29ad2caaa
4 files changed
+62510
No files matched your search
@@ -488,6 +488,19 @@
|
||||
—— 即车库 / 商店的物品配置容器,不是用户属性。
|
||||
- 下一步:给这 8 个 CC 写「CC → 动作」适配器(原包在 module_47243 的模型层里聚合后派发),
|
||||
用户属性那条链路(`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,
|
||||
已按字段具名为 `(inline@<field>)` 并从函数体抓到顶层动作名(69 / 1 / 2 / 11 个);
|
||||
`launchParams` 是纯透传(`(pass-through)`,无 reducer),已把字段收口为 `Map<String, String>`。
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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);
|
||||
@@ -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} 个。`,
|
||||
);
|
||||
Reference in new issue
Block a user