@easy-electron/preload 1.0.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.cjs +1072 -0
- package/dist/index.d.cts +234 -0
- package/dist/index.d.mts +234 -0
- package/dist/index.d.ts +234 -0
- package/dist/index.mjs +1048 -0
- package/dist/runtime-entry.cjs +11 -0
- package/dist/runtime-entry.d.cts +106 -0
- package/dist/runtime-entry.d.mts +106 -0
- package/dist/runtime-entry.d.ts +106 -0
- package/dist/runtime-entry.mjs +2 -0
- package/dist/shared/preload.CkLc48BZ.mjs +24 -0
- package/dist/shared/preload.OqYa61Ym.cjs +29 -0
- package/package.json +54 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1048 @@
|
|
|
1
|
+
import { ConfigUtil } from '@easy-electron/utils';
|
|
2
|
+
import { Logger } from '@easy-electron/logger';
|
|
3
|
+
import { TsParser } from '@easy-electron/ts-parser';
|
|
4
|
+
import fg from 'fast-glob';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
export { E as EVENT_MODULE_KEY, I as IPC_MODULE_KEY, d as defineEvents, a as defineIpc } from './shared/preload.CkLc48BZ.mjs';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_EASY_PRELOAD_GENERATOR_CONFIG = {
|
|
10
|
+
dir: "./electron",
|
|
11
|
+
source: ["**/*.ipc.ts", "**/ipc/**/*.ts"],
|
|
12
|
+
events: ["**/*.events.ts", "**/events/**/*.ts"],
|
|
13
|
+
ignores: ["**/node_modules/**", "**/dist/**", "**/dist-electron/**"],
|
|
14
|
+
output: "./electron/preload.ts",
|
|
15
|
+
typesOutput: "./types/electron.d.ts",
|
|
16
|
+
globalName: "elec",
|
|
17
|
+
preserveComments: true
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
class IpcFileScanner {
|
|
21
|
+
dir;
|
|
22
|
+
source;
|
|
23
|
+
ignores;
|
|
24
|
+
parser;
|
|
25
|
+
logger = Logger.scope("IpcFileScanner");
|
|
26
|
+
/**
|
|
27
|
+
* @param dir 扫描根目录
|
|
28
|
+
* @param source 文件匹配模式(glob 格式,支持数组)
|
|
29
|
+
* @param ignores 忽略的文件模式
|
|
30
|
+
*/
|
|
31
|
+
constructor(dir, source, ignores) {
|
|
32
|
+
this.dir = dir;
|
|
33
|
+
this.source = source;
|
|
34
|
+
this.ignores = ignores;
|
|
35
|
+
this.parser = new TsParser();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 扫描所有匹配的 IPC 定义文件,返回解析结果列表
|
|
39
|
+
*/
|
|
40
|
+
scan() {
|
|
41
|
+
const patterns = Array.isArray(this.source) ? this.source : [this.source];
|
|
42
|
+
const files = fg.sync(patterns, {
|
|
43
|
+
cwd: this.dir,
|
|
44
|
+
absolute: true,
|
|
45
|
+
ignore: this.ignores
|
|
46
|
+
});
|
|
47
|
+
const results = [];
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
try {
|
|
50
|
+
const defi = this.parseFile(file);
|
|
51
|
+
if (defi) {
|
|
52
|
+
results.push(defi);
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
this.logger.warn(`\u89E3\u6790 IPC \u6587\u4EF6\u5931\u8D25: ${file}`, e);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return results;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 解析单个 IPC 定义文件
|
|
62
|
+
*
|
|
63
|
+
* 查找文件中的 `export default defineIpc(...)` 调用,
|
|
64
|
+
* 提取模块名和所有处理器定义。
|
|
65
|
+
*
|
|
66
|
+
* @param filePath 文件路径
|
|
67
|
+
* @returns IPC 定义信息,文件中无 defineIpc 调用时返回 null
|
|
68
|
+
*/
|
|
69
|
+
parseFile(filePath) {
|
|
70
|
+
const parsed = this.parser.parse(filePath);
|
|
71
|
+
const defaultExport = parsed.defaultExport;
|
|
72
|
+
if (!defaultExport || defaultExport.kind !== "call" || !defaultExport.call) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const call = defaultExport.call;
|
|
76
|
+
if (call.callee !== "defineIpc") {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return this.parseDefineIpcCall(filePath, call, parsed);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 解析 defineIpc 函数调用,提取模块名和处理器映射
|
|
83
|
+
*
|
|
84
|
+
* 支持两种调用形式:
|
|
85
|
+
* - `defineIpc({ ... })` — 无模块名
|
|
86
|
+
* - `defineIpc('moduleName', { ... })` — 有模块名
|
|
87
|
+
*
|
|
88
|
+
* @param filePath 文件路径
|
|
89
|
+
* @param call 函数调用表达式信息
|
|
90
|
+
* @param parsed 完整的文件解析结果(用于提取 import 信息)
|
|
91
|
+
* @returns IPC 定义信息
|
|
92
|
+
*/
|
|
93
|
+
parseDefineIpcCall(filePath, call, parsed) {
|
|
94
|
+
const args = call.arguments;
|
|
95
|
+
if (args.length < 1) {
|
|
96
|
+
this.logger.warn(`defineIpc \u8C03\u7528\u53C2\u6570\u4E0D\u8DB3: ${filePath}`);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
let moduleName;
|
|
100
|
+
let handlersArg;
|
|
101
|
+
if (args.length >= 2 && args[0].kind === "string") {
|
|
102
|
+
moduleName = String(args[0].value);
|
|
103
|
+
handlersArg = args[1];
|
|
104
|
+
} else {
|
|
105
|
+
handlersArg = args[0];
|
|
106
|
+
}
|
|
107
|
+
if (handlersArg.kind !== "object" || !handlersArg.properties) {
|
|
108
|
+
this.logger.warn(`defineIpc \u5904\u7406\u5668\u53C2\u6570\u4E0D\u662F\u5BF9\u8C61\u5B57\u9762\u91CF: ${filePath}`);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
112
|
+
for (const prop of handlersArg.properties) {
|
|
113
|
+
const handlerInfo = this.parseHandler(prop);
|
|
114
|
+
if (handlerInfo) {
|
|
115
|
+
handlers.set(prop.name, handlerInfo);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const imports = this.collectImports(parsed, filePath);
|
|
119
|
+
return {
|
|
120
|
+
filePath,
|
|
121
|
+
module: moduleName,
|
|
122
|
+
handlers,
|
|
123
|
+
imports
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 解析单个处理器属性
|
|
128
|
+
*
|
|
129
|
+
* 支持三种写法:
|
|
130
|
+
* 1. 箭头函数(默认 handle 类型):
|
|
131
|
+
* `getData: async (_event, id: string): Promise<Data> => { ... }`
|
|
132
|
+
*
|
|
133
|
+
* 2. 方法简写(默认 handle 类型):
|
|
134
|
+
* `getUser() { return ... }`
|
|
135
|
+
*
|
|
136
|
+
* 3. 对象形式(显式指定类型):
|
|
137
|
+
* `logMessage: { type: 'on', handler: (_event, msg: string) => { ... } }`
|
|
138
|
+
*
|
|
139
|
+
* @param prop 对象属性信息
|
|
140
|
+
* @returns IPC 处理器信息,无法解析时返回 null
|
|
141
|
+
*/
|
|
142
|
+
parseHandler(prop) {
|
|
143
|
+
const value = prop.value;
|
|
144
|
+
if (value.kind === "function" && value.function) {
|
|
145
|
+
return this.extractHandlerFromFunction(value.function, "handle", prop.comment);
|
|
146
|
+
}
|
|
147
|
+
if (value.kind === "object" && value.properties) {
|
|
148
|
+
return this.parseObjectHandler(value.properties, prop.comment);
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 从对象形式的处理器定义中提取信息
|
|
154
|
+
*
|
|
155
|
+
* 对象中可包含:
|
|
156
|
+
* - `type`: 处理器类型字符串('handle' | 'on' | 'handleOnce' | 'once')
|
|
157
|
+
* - `handler`: 处理器函数
|
|
158
|
+
*
|
|
159
|
+
* @param properties 对象属性列表
|
|
160
|
+
* @param comment 外层属性的 JSDoc 注释
|
|
161
|
+
* @returns IPC 处理器信息
|
|
162
|
+
*/
|
|
163
|
+
parseObjectHandler(properties, comment) {
|
|
164
|
+
let handlerType = "handle";
|
|
165
|
+
let handlerFunc;
|
|
166
|
+
for (const p of properties) {
|
|
167
|
+
if (p.name === "type" && p.value.kind === "string") {
|
|
168
|
+
handlerType = String(p.value.value);
|
|
169
|
+
} else if (p.name === "handler") {
|
|
170
|
+
handlerFunc = p.value;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!handlerFunc || handlerFunc.kind !== "function" || !handlerFunc.function) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
return this.extractHandlerFromFunction(handlerFunc.function, handlerType, comment);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 从函数表达式中提取处理器的参数和返回类型信息
|
|
180
|
+
*
|
|
181
|
+
* 智能跳过第一个 event 参数:
|
|
182
|
+
* - 参数名以 _ 开头或名为 event/e → 视为 event 参数,跳过
|
|
183
|
+
* - 否则保留所有参数
|
|
184
|
+
*
|
|
185
|
+
* @param func 函数表达式信息
|
|
186
|
+
* @param handlerType 处理器类型
|
|
187
|
+
* @param comment JSDoc 注释
|
|
188
|
+
* @returns IPC 处理器信息
|
|
189
|
+
*/
|
|
190
|
+
extractHandlerFromFunction(func, handlerType, comment) {
|
|
191
|
+
const params = this.skipEventParam(func.parameters);
|
|
192
|
+
const paramNames = params.map((p) => p.name);
|
|
193
|
+
const paramTypes = params.map((p) => p.type ?? "any");
|
|
194
|
+
let returnType = func.returnType ?? (func.isAsync ? "Promise<any>" : "any");
|
|
195
|
+
if (func.isAsync && func.returnType && !func.returnType.startsWith("Promise")) {
|
|
196
|
+
returnType = `Promise<${func.returnType}>`;
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
paramNames,
|
|
200
|
+
paramTypes,
|
|
201
|
+
returnType,
|
|
202
|
+
comment: comment ?? void 0,
|
|
203
|
+
handlerType
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* 智能跳过函数参数列表中的 event 参数
|
|
208
|
+
*
|
|
209
|
+
* 判断第一个参数是否为 Electron IPC 事件对象:
|
|
210
|
+
* - 参数名以 _ 开头(如 _event, _e)
|
|
211
|
+
* - 参数名为 event 或 e
|
|
212
|
+
* - 参数类型包含 Event
|
|
213
|
+
*
|
|
214
|
+
* @param params 原始参数列表
|
|
215
|
+
* @returns 跳过 event 参数后的参数列表
|
|
216
|
+
*/
|
|
217
|
+
skipEventParam(params) {
|
|
218
|
+
if (params.length === 0) return params;
|
|
219
|
+
const first = params[0];
|
|
220
|
+
const name = first.name;
|
|
221
|
+
const type = first.type ?? "";
|
|
222
|
+
const isEventParam = name.startsWith("_") || name === "event" || name === "e" || type.includes("Event");
|
|
223
|
+
return isEventParam ? params.slice(1) : params;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* 收集文件中引用的类型信息
|
|
227
|
+
*
|
|
228
|
+
* 包括两部分:
|
|
229
|
+
* 1. import 语句中导入的类型(过滤掉 runtime 导入)
|
|
230
|
+
* 2. 文件内定义的 export interface/type(作为同文件类型)
|
|
231
|
+
*
|
|
232
|
+
* @param parsed 文件解析结果
|
|
233
|
+
* @param filePath 文件路径(用于本地类型的模块路径)
|
|
234
|
+
* @returns 类型名 -> 模块路径 的映射
|
|
235
|
+
*/
|
|
236
|
+
collectImports(parsed, filePath) {
|
|
237
|
+
const imports = /* @__PURE__ */ new Map();
|
|
238
|
+
for (const imp of parsed.imports) {
|
|
239
|
+
if (imp.moduleSpecifier.includes("epreload/runtime") || imp.namedImports.some((n) => n.name === "defineIpc")) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (imp.defaultImport) {
|
|
243
|
+
imports.set(imp.defaultImport, imp.moduleSpecifier);
|
|
244
|
+
}
|
|
245
|
+
for (const named of imp.namedImports) {
|
|
246
|
+
const name = named.alias ?? named.name;
|
|
247
|
+
imports.set(name, imp.moduleSpecifier);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const localModulePath = this.toModulePath(filePath);
|
|
251
|
+
for (const iface of parsed.interfaces) {
|
|
252
|
+
if (iface.isExported) {
|
|
253
|
+
imports.set(iface.name, localModulePath);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
for (const typeAlias of parsed.typeAliases) {
|
|
257
|
+
if (typeAlias.isExported) {
|
|
258
|
+
imports.set(typeAlias.name, localModulePath);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return imports;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* 将文件绝对路径转换为相对模块路径(去掉扩展名)
|
|
265
|
+
*
|
|
266
|
+
* @param filePath 文件绝对路径
|
|
267
|
+
* @returns 相对模块路径
|
|
268
|
+
*/
|
|
269
|
+
toModulePath(filePath) {
|
|
270
|
+
return filePath.replace(/\.ts$/, "");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
class EventHandlerModel {
|
|
275
|
+
/**
|
|
276
|
+
* 数据类型
|
|
277
|
+
*/
|
|
278
|
+
dataType;
|
|
279
|
+
/**
|
|
280
|
+
* JSDoc 注释
|
|
281
|
+
*/
|
|
282
|
+
comment;
|
|
283
|
+
/**
|
|
284
|
+
* 是否为一次性监听
|
|
285
|
+
*/
|
|
286
|
+
once;
|
|
287
|
+
/**
|
|
288
|
+
* Remove 方法类型
|
|
289
|
+
*/
|
|
290
|
+
remove;
|
|
291
|
+
constructor(dataType, comment, once = false, remove = false) {
|
|
292
|
+
this.dataType = dataType;
|
|
293
|
+
this.comment = comment;
|
|
294
|
+
this.once = once;
|
|
295
|
+
this.remove = remove;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* 获取回调函数类型签名
|
|
299
|
+
*/
|
|
300
|
+
getCallbackSignature() {
|
|
301
|
+
return `(data: ${this.dataType}) => void`;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* 获取监听方法类型签名
|
|
305
|
+
*/
|
|
306
|
+
getListenerSignature() {
|
|
307
|
+
return `(callback: ${this.getCallbackSignature()}) => () => void`;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* 获取 remove 方法类型签名(single)
|
|
311
|
+
*/
|
|
312
|
+
getRemoveSingleSignature() {
|
|
313
|
+
return `(callback: ${this.getCallbackSignature()}) => void`;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* 获取 removeAll 方法类型签名
|
|
317
|
+
*/
|
|
318
|
+
getRemoveAllSignature() {
|
|
319
|
+
return `() => void`;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
class EventFileScanner {
|
|
324
|
+
dir;
|
|
325
|
+
source;
|
|
326
|
+
ignores;
|
|
327
|
+
parser;
|
|
328
|
+
logger = Logger.scope("EventFileScanner");
|
|
329
|
+
/**
|
|
330
|
+
* @param dir 扫描根目录
|
|
331
|
+
* @param source 文件匹配模式(glob 格式,支持数组)
|
|
332
|
+
* @param ignores 忽略的文件模式
|
|
333
|
+
*/
|
|
334
|
+
constructor(dir, source, ignores) {
|
|
335
|
+
this.dir = dir;
|
|
336
|
+
this.source = source;
|
|
337
|
+
this.ignores = ignores;
|
|
338
|
+
this.parser = new TsParser();
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* 扫描所有匹配的事件定义文件,返回解析结果列表
|
|
342
|
+
*/
|
|
343
|
+
scan() {
|
|
344
|
+
const patterns = Array.isArray(this.source) ? this.source : [this.source];
|
|
345
|
+
const files = fg.sync(patterns, {
|
|
346
|
+
cwd: this.dir,
|
|
347
|
+
absolute: true,
|
|
348
|
+
ignore: this.ignores
|
|
349
|
+
});
|
|
350
|
+
const results = [];
|
|
351
|
+
for (const file of files) {
|
|
352
|
+
try {
|
|
353
|
+
const defi = this.parseFile(file);
|
|
354
|
+
if (defi) {
|
|
355
|
+
results.push(defi);
|
|
356
|
+
}
|
|
357
|
+
} catch (e) {
|
|
358
|
+
this.logger.warn(`\u89E3\u6790\u4E8B\u4EF6\u6587\u4EF6\u5931\u8D25: ${file}`, e);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return results;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* 解析单个事件定义文件
|
|
365
|
+
*
|
|
366
|
+
* 查找文件中的 `export default defineEvents(...)` 调用,
|
|
367
|
+
* 提取所有事件处理器定义。
|
|
368
|
+
*
|
|
369
|
+
* @param filePath 文件路径
|
|
370
|
+
* @returns 事件定义信息,文件中无 defineEvents 调用时返回 null
|
|
371
|
+
*/
|
|
372
|
+
parseFile(filePath) {
|
|
373
|
+
const parsed = this.parser.parse(filePath);
|
|
374
|
+
const defaultExport = parsed.defaultExport;
|
|
375
|
+
if (!defaultExport || defaultExport.kind !== "call" || !defaultExport.call) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
const call = defaultExport.call;
|
|
379
|
+
if (call.callee !== "defineEvents") {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
return this.parseDefineEventsCall(filePath, call, parsed);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* 解析 defineEvents 函数调用,提取事件处理器映射
|
|
386
|
+
*
|
|
387
|
+
* 支持两种调用形式:
|
|
388
|
+
* - `defineEvents({ ... })` — 无模块名
|
|
389
|
+
* - `defineEvents('moduleName', { ... })` — 有模块名
|
|
390
|
+
*
|
|
391
|
+
* @param filePath 文件路径
|
|
392
|
+
* @param call 函数调用表达式信息
|
|
393
|
+
* @param parsed 完整的文件解析结果(用于提取 import 信息)
|
|
394
|
+
* @returns 事件定义信息
|
|
395
|
+
*/
|
|
396
|
+
parseDefineEventsCall(filePath, call, parsed) {
|
|
397
|
+
const args = call.arguments;
|
|
398
|
+
if (args.length < 1) {
|
|
399
|
+
this.logger.warn(`defineEvents \u8C03\u7528\u53C2\u6570\u4E0D\u8DB3: ${filePath}`);
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
let moduleName;
|
|
403
|
+
let handlersArg;
|
|
404
|
+
if (args.length >= 2 && args[0].kind === "string") {
|
|
405
|
+
moduleName = String(args[0].value);
|
|
406
|
+
handlersArg = args[1];
|
|
407
|
+
} else {
|
|
408
|
+
handlersArg = args[0];
|
|
409
|
+
}
|
|
410
|
+
if (handlersArg.kind !== "object" || !handlersArg.properties) {
|
|
411
|
+
this.logger.warn(`defineEvents \u5904\u7406\u5668\u53C2\u6570\u4E0D\u662F\u5BF9\u8C61\u5B57\u9762\u91CF: ${filePath}`);
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
415
|
+
for (const prop of handlersArg.properties) {
|
|
416
|
+
const model = this.parseEventHandler(prop);
|
|
417
|
+
if (model) {
|
|
418
|
+
handlers.set(prop.name, model);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const imports = this.collectImports(parsed, filePath);
|
|
422
|
+
return {
|
|
423
|
+
filePath,
|
|
424
|
+
module: moduleName,
|
|
425
|
+
handlers,
|
|
426
|
+
imports
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* 解析单个事件处理器属性
|
|
431
|
+
*
|
|
432
|
+
* 支持两种写法:
|
|
433
|
+
*
|
|
434
|
+
* 1. 对象形式:
|
|
435
|
+
* ```ts
|
|
436
|
+
* onDownloadProgress: {
|
|
437
|
+
* sample: (data: DownloadProgressData) => data,
|
|
438
|
+
* remove: 'single',
|
|
439
|
+
* once: true
|
|
440
|
+
* }
|
|
441
|
+
* ```
|
|
442
|
+
*
|
|
443
|
+
* 2. 简写函数形式(无 remove,非 once):
|
|
444
|
+
* ```ts
|
|
445
|
+
* onAlert: (data: AlertData) => data
|
|
446
|
+
* ```
|
|
447
|
+
*
|
|
448
|
+
* @param prop 对象属性信息
|
|
449
|
+
* @returns EventHandlerModel 实例,无法解析时返回 null
|
|
450
|
+
*/
|
|
451
|
+
parseEventHandler(prop) {
|
|
452
|
+
const value = prop.value;
|
|
453
|
+
if (value.kind === "object" && value.properties) {
|
|
454
|
+
return this.parseObjectEventHandler(value.properties, prop.comment);
|
|
455
|
+
}
|
|
456
|
+
if (value.kind === "function" && value.function) {
|
|
457
|
+
const dataType = this.extractDataTypeFromFunction(value.function);
|
|
458
|
+
const once = prop.name.startsWith("once");
|
|
459
|
+
return new EventHandlerModel(dataType, prop.comment, once, false);
|
|
460
|
+
}
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* 从对象形式的事件处理器中提取信息
|
|
465
|
+
*
|
|
466
|
+
* @param properties 对象属性列表
|
|
467
|
+
* @param comment 外层属性的 JSDoc 注释
|
|
468
|
+
* @returns EventHandlerModel 实例
|
|
469
|
+
*/
|
|
470
|
+
parseObjectEventHandler(properties, comment) {
|
|
471
|
+
let dataType = "any";
|
|
472
|
+
let once = false;
|
|
473
|
+
let remove = false;
|
|
474
|
+
for (const p of properties) {
|
|
475
|
+
switch (p.name) {
|
|
476
|
+
case "sample":
|
|
477
|
+
if (p.value.kind === "function" && p.value.function) {
|
|
478
|
+
dataType = this.extractDataTypeFromFunction(p.value.function);
|
|
479
|
+
}
|
|
480
|
+
break;
|
|
481
|
+
case "remove":
|
|
482
|
+
remove = this.parseRemoveType(p.value);
|
|
483
|
+
break;
|
|
484
|
+
case "once":
|
|
485
|
+
once = p.value.kind === "boolean" && p.value.value === true;
|
|
486
|
+
break;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return new EventHandlerModel(dataType, comment, once, remove);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* 从函数表达式中提取数据类型
|
|
493
|
+
*
|
|
494
|
+
* 事件 sample 函数签名: `(data: DataType) => data`
|
|
495
|
+
* 从第一个参数的类型注解中提取数据类型。
|
|
496
|
+
*
|
|
497
|
+
* @param func 函数表达式信息
|
|
498
|
+
* @returns 数据类型字符串,无法提取时返回 'any'
|
|
499
|
+
*/
|
|
500
|
+
extractDataTypeFromFunction(func) {
|
|
501
|
+
const params = func.parameters;
|
|
502
|
+
if (params.length === 0) {
|
|
503
|
+
return "void";
|
|
504
|
+
}
|
|
505
|
+
return params[0].type ?? "any";
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* 解析 remove 属性值为 RemoveType
|
|
509
|
+
* @param arg remove 属性值
|
|
510
|
+
* @returns RemoveType 值
|
|
511
|
+
*/
|
|
512
|
+
parseRemoveType(arg) {
|
|
513
|
+
if (arg.kind === "string") {
|
|
514
|
+
const val = String(arg.value);
|
|
515
|
+
if (val === "single" || val === "all" || val === "both") {
|
|
516
|
+
return val;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (arg.kind === "boolean" && arg.value === false) {
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* 收集文件中引用的类型信息
|
|
526
|
+
*
|
|
527
|
+
* 包括两部分:
|
|
528
|
+
* 1. import 语句中导入的类型(过滤掉 runtime 导入)
|
|
529
|
+
* 2. 文件内定义的 export interface/type(作为同文件类型)
|
|
530
|
+
*
|
|
531
|
+
* @param parsed 文件解析结果
|
|
532
|
+
* @param filePath 文件路径
|
|
533
|
+
* @returns 类型名 -> 模块路径 的映射
|
|
534
|
+
*/
|
|
535
|
+
collectImports(parsed, filePath) {
|
|
536
|
+
const imports = /* @__PURE__ */ new Map();
|
|
537
|
+
for (const imp of parsed.imports) {
|
|
538
|
+
if (imp.moduleSpecifier.includes("epreload/runtime") || imp.namedImports.some((n) => n.name === "defineEvents")) {
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (imp.defaultImport) {
|
|
542
|
+
imports.set(imp.defaultImport, imp.moduleSpecifier);
|
|
543
|
+
}
|
|
544
|
+
for (const named of imp.namedImports) {
|
|
545
|
+
const name = named.alias ?? named.name;
|
|
546
|
+
imports.set(name, imp.moduleSpecifier);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const localModulePath = filePath.replace(/\.ts$/, "");
|
|
550
|
+
for (const iface of parsed.interfaces) {
|
|
551
|
+
if (iface.isExported) {
|
|
552
|
+
imports.set(iface.name, localModulePath);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
for (const typeAlias of parsed.typeAliases) {
|
|
556
|
+
if (typeAlias.isExported) {
|
|
557
|
+
imports.set(typeAlias.name, localModulePath);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return imports;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
class EasyPreloadGenerator {
|
|
565
|
+
config;
|
|
566
|
+
logger = Logger.scope("EasyPreloadGenerator");
|
|
567
|
+
constructor(config) {
|
|
568
|
+
this.config = ConfigUtil.deepMergeConfig(DEFAULT_EASY_PRELOAD_GENERATOR_CONFIG, config);
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* 执行生成,扫描 IPC 和 Event 定义文件,输出 preload 和类型文件
|
|
572
|
+
*/
|
|
573
|
+
generate() {
|
|
574
|
+
const ipcDefs = this.scanIpc();
|
|
575
|
+
const eventDefs = this.scanEvents();
|
|
576
|
+
const preloadContent = this.generatePreload(ipcDefs, eventDefs);
|
|
577
|
+
const typesContent = this.generateTypes(ipcDefs, eventDefs);
|
|
578
|
+
this.writeFile(this.config.output, preloadContent);
|
|
579
|
+
this.writeFile(this.config.typesOutput, typesContent);
|
|
580
|
+
this.logger.info(`\u751F\u6210\u5B8C\u6210: ${this.config.output}, ${this.config.typesOutput}`);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* 扫描 IPC 定义文件
|
|
584
|
+
*/
|
|
585
|
+
scanIpc() {
|
|
586
|
+
const scanner = new IpcFileScanner(this.config.dir, this.config.source, this.config.ignores);
|
|
587
|
+
return scanner.scan();
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* 扫描 Event 定义文件
|
|
591
|
+
*/
|
|
592
|
+
scanEvents() {
|
|
593
|
+
const scanner = new EventFileScanner(this.config.dir, this.config.events, this.config.ignores);
|
|
594
|
+
return scanner.scan();
|
|
595
|
+
}
|
|
596
|
+
// ==================== Preload 文件生成 ====================
|
|
597
|
+
/**
|
|
598
|
+
* 生成 preload.ts 文件内容
|
|
599
|
+
*
|
|
600
|
+
* 结构:
|
|
601
|
+
* ```ts
|
|
602
|
+
* contextBridge.exposeInMainWorld('globalName', {
|
|
603
|
+
* // 无模块 IPC 方法(顶层)
|
|
604
|
+
* ping: () => ipcRenderer.invoke('ping'),
|
|
605
|
+
* // 有模块 IPC 方法(嵌套对象)
|
|
606
|
+
* test: {
|
|
607
|
+
* getData: (id) => ipcRenderer.invoke('test:getData', id),
|
|
608
|
+
* },
|
|
609
|
+
* // 事件(嵌套在 events 下)
|
|
610
|
+
* events: {
|
|
611
|
+
* onXxx: (callback) => { ... },
|
|
612
|
+
* module: { onYyy: (callback) => { ... } },
|
|
613
|
+
* },
|
|
614
|
+
* })
|
|
615
|
+
* ```
|
|
616
|
+
*/
|
|
617
|
+
generatePreload(ipcDefs, eventDefs) {
|
|
618
|
+
const lines = [];
|
|
619
|
+
const typeImports = this.collectTypeImports(ipcDefs, eventDefs, this.config.output);
|
|
620
|
+
lines.push("import { contextBridge, ipcRenderer } from 'electron'");
|
|
621
|
+
lines.push("import type { IpcRendererEvent } from 'electron'");
|
|
622
|
+
for (const imp of typeImports) {
|
|
623
|
+
lines.push(imp);
|
|
624
|
+
}
|
|
625
|
+
lines.push("");
|
|
626
|
+
lines.push("// Auto-generated preload file");
|
|
627
|
+
lines.push("// Do not edit this file manually");
|
|
628
|
+
lines.push("");
|
|
629
|
+
lines.push(`contextBridge.exposeInMainWorld('${this.config.globalName}', {`);
|
|
630
|
+
this.generatePreloadIpc(ipcDefs, lines);
|
|
631
|
+
this.generatePreloadEvents(eventDefs, lines);
|
|
632
|
+
lines.push("})");
|
|
633
|
+
lines.push("");
|
|
634
|
+
return lines.join("\n");
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* 生成 preload 中的 IPC 调用代码
|
|
638
|
+
*
|
|
639
|
+
* 无模块的处理器直接放在顶层,有模块的嵌套在模块名对象下。
|
|
640
|
+
* 根据 handlerType 选择 invoke(handle/handleOnce)或 send(on/once)。
|
|
641
|
+
*/
|
|
642
|
+
generatePreloadIpc(ipcDefs, lines) {
|
|
643
|
+
const noModule = [];
|
|
644
|
+
const withModule = [];
|
|
645
|
+
for (const def of ipcDefs) {
|
|
646
|
+
if (def.module) {
|
|
647
|
+
withModule.push(def);
|
|
648
|
+
} else {
|
|
649
|
+
noModule.push(def);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
for (const def of noModule) {
|
|
653
|
+
for (const [name, handler] of def.handlers) {
|
|
654
|
+
lines.push(` ${this.generatePreloadIpcMethod(name, name, handler)},`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
for (const def of withModule) {
|
|
658
|
+
lines.push(` ${def.module}: {`);
|
|
659
|
+
for (const [name, handler] of def.handlers) {
|
|
660
|
+
const channel = `${def.module}:${name}`;
|
|
661
|
+
lines.push(` ${this.generatePreloadIpcMethod(name, channel, handler)},`);
|
|
662
|
+
}
|
|
663
|
+
lines.push(" },");
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* 生成单个 IPC 方法的 preload 代码
|
|
668
|
+
*
|
|
669
|
+
* @param name 方法名
|
|
670
|
+
* @param channel IPC 通道名
|
|
671
|
+
* @param handler 处理器信息
|
|
672
|
+
* @returns 方法代码字符串(不含尾部逗号)
|
|
673
|
+
*/
|
|
674
|
+
generatePreloadIpcMethod(name, channel, handler) {
|
|
675
|
+
const params = handler.paramNames.map((n, i) => `${n}: ${handler.paramTypes[i]}`).join(", ");
|
|
676
|
+
const args = handler.paramNames.length > 0 ? ", " + handler.paramNames.join(", ") : "";
|
|
677
|
+
const type = handler.handlerType ?? "handle";
|
|
678
|
+
switch (type) {
|
|
679
|
+
case "handle":
|
|
680
|
+
case "handleOnce":
|
|
681
|
+
return `${name}: (${params}): ${this.ensurePromise(handler.returnType)} => ipcRenderer.invoke('${channel}'${args})`;
|
|
682
|
+
case "on":
|
|
683
|
+
case "once":
|
|
684
|
+
return `${name}: (${params}): void => ipcRenderer.send('${channel}'${args})`;
|
|
685
|
+
default:
|
|
686
|
+
return `${name}: (${params}): ${this.ensurePromise(handler.returnType)} => ipcRenderer.invoke('${channel}'${args})`;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* 生成 preload 中的事件监听代码
|
|
691
|
+
*
|
|
692
|
+
* 所有事件嵌套在 `events` 对象下。
|
|
693
|
+
* 无模块的事件直接放在 events 下,有模块的嵌套在 events.module 下。
|
|
694
|
+
*/
|
|
695
|
+
generatePreloadEvents(eventDefs, lines) {
|
|
696
|
+
if (eventDefs.length === 0) return;
|
|
697
|
+
const noModule = [];
|
|
698
|
+
const withModule = [];
|
|
699
|
+
for (const def of eventDefs) {
|
|
700
|
+
if (def.module) {
|
|
701
|
+
withModule.push(def);
|
|
702
|
+
} else {
|
|
703
|
+
noModule.push(def);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
lines.push(" events: {");
|
|
707
|
+
for (const def of noModule) {
|
|
708
|
+
for (const [name, handler] of def.handlers) {
|
|
709
|
+
const channel = this.toEventChannel(name);
|
|
710
|
+
this.generatePreloadEventMethods(name, channel, handler, lines, " ");
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
for (const def of withModule) {
|
|
714
|
+
lines.push(` ${def.module}: {`);
|
|
715
|
+
for (const [name, handler] of def.handlers) {
|
|
716
|
+
const channel = `${def.module}:${this.toEventChannel(name)}`;
|
|
717
|
+
this.generatePreloadEventMethods(name, channel, handler, lines, " ");
|
|
718
|
+
}
|
|
719
|
+
lines.push(" },");
|
|
720
|
+
}
|
|
721
|
+
lines.push(" },");
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* 生成单个事件的监听/移除方法代码
|
|
725
|
+
*
|
|
726
|
+
* 根据 handler 的 once 和 remove 属性生成:
|
|
727
|
+
* - on/once 监听方法
|
|
728
|
+
* - removeSingle 方法(remove 为 'single' 或 'both')
|
|
729
|
+
* - removeAll 方法(remove 为 'all' 或 'both')
|
|
730
|
+
*
|
|
731
|
+
* @param name 事件名(如 onDownloadProgress)
|
|
732
|
+
* @param channel IPC 通道名
|
|
733
|
+
* @param handler 事件处理器模型
|
|
734
|
+
* @param lines 输出行数组
|
|
735
|
+
* @param indent 缩进字符串
|
|
736
|
+
*/
|
|
737
|
+
generatePreloadEventMethods(name, channel, handler, lines, indent) {
|
|
738
|
+
const dataType = handler.dataType;
|
|
739
|
+
const listenMethod = handler.once ? "once" : "on";
|
|
740
|
+
const listenerName = handler.once && !name.startsWith("once") ? name.replace(/^on/, "once") : name;
|
|
741
|
+
lines.push(`${indent}${listenerName}: (callback: (data: ${dataType}) => void) => {`);
|
|
742
|
+
lines.push(`${indent} const listener = (_event: IpcRendererEvent, data: ${dataType}) => callback(data)`);
|
|
743
|
+
lines.push(`${indent} ipcRenderer.${listenMethod}('${channel}', listener)`);
|
|
744
|
+
if (!handler.once) {
|
|
745
|
+
lines.push(`${indent} return () => ipcRenderer.removeListener('${channel}', listener)`);
|
|
746
|
+
}
|
|
747
|
+
lines.push(`${indent}},`);
|
|
748
|
+
const baseName = this.stripEventPrefix(name);
|
|
749
|
+
this.generatePreloadRemoveMethods(baseName, channel, dataType, handler.remove, lines, indent);
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* 生成事件的 remove 方法代码
|
|
753
|
+
*/
|
|
754
|
+
generatePreloadRemoveMethods(baseName, channel, dataType, remove, lines, indent) {
|
|
755
|
+
if (remove === "single" || remove === "both") {
|
|
756
|
+
lines.push(`${indent}remove${baseName}: (callback: (data: ${dataType}) => void) => {`);
|
|
757
|
+
lines.push(`${indent} const listener = (_event: IpcRendererEvent, data: ${dataType}) => callback(data)`);
|
|
758
|
+
lines.push(`${indent} ipcRenderer.removeListener('${channel}', listener)`);
|
|
759
|
+
lines.push(`${indent}},`);
|
|
760
|
+
}
|
|
761
|
+
if (remove === "all" || remove === "both") {
|
|
762
|
+
lines.push(`${indent}remove${baseName}All: () => {`);
|
|
763
|
+
lines.push(`${indent} ipcRenderer.removeAllListeners('${channel}')`);
|
|
764
|
+
lines.push(`${indent}},`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
// ==================== 类型文件生成 ====================
|
|
768
|
+
/**
|
|
769
|
+
* 生成类型定义文件内容
|
|
770
|
+
*
|
|
771
|
+
* 结构:
|
|
772
|
+
* ```ts
|
|
773
|
+
* interface ElecApi {
|
|
774
|
+
* ping: () => Promise<string>
|
|
775
|
+
* test: { getData: (id: string) => Promise<Data> }
|
|
776
|
+
* }
|
|
777
|
+
* export interface ElecEvents {
|
|
778
|
+
* onXxx: (callback: (data: T) => void) => () => void
|
|
779
|
+
* }
|
|
780
|
+
* declare global {
|
|
781
|
+
* interface Window { elec: ElecApi & { events: ElecEvents } }
|
|
782
|
+
* }
|
|
783
|
+
* export {}
|
|
784
|
+
* ```
|
|
785
|
+
*/
|
|
786
|
+
generateTypes(ipcDefs, eventDefs) {
|
|
787
|
+
const lines = [];
|
|
788
|
+
const typeImports = this.collectTypeImports(ipcDefs, eventDefs, this.config.typesOutput);
|
|
789
|
+
for (const imp of typeImports) {
|
|
790
|
+
lines.push(imp);
|
|
791
|
+
}
|
|
792
|
+
lines.push("");
|
|
793
|
+
lines.push("// Auto-generated type definitions");
|
|
794
|
+
lines.push("// Do not edit this file manually");
|
|
795
|
+
lines.push("");
|
|
796
|
+
this.generateTypesIpc(ipcDefs, lines);
|
|
797
|
+
const hasEvents = eventDefs.length > 0;
|
|
798
|
+
if (hasEvents) {
|
|
799
|
+
lines.push("");
|
|
800
|
+
this.generateTypesEvents(eventDefs, lines);
|
|
801
|
+
}
|
|
802
|
+
lines.push("");
|
|
803
|
+
lines.push("declare global {");
|
|
804
|
+
lines.push(" interface Window {");
|
|
805
|
+
if (hasEvents) {
|
|
806
|
+
lines.push(` ${this.config.globalName}: ElecApi & { events: ElecEvents }`);
|
|
807
|
+
} else {
|
|
808
|
+
lines.push(` ${this.config.globalName}: ElecApi`);
|
|
809
|
+
}
|
|
810
|
+
lines.push(" }");
|
|
811
|
+
lines.push("}");
|
|
812
|
+
lines.push("");
|
|
813
|
+
lines.push("export {}");
|
|
814
|
+
lines.push("");
|
|
815
|
+
return lines.join("\n");
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* 生成 ElecApi 接口中的 IPC 方法类型
|
|
819
|
+
*/
|
|
820
|
+
generateTypesIpc(ipcDefs, lines) {
|
|
821
|
+
const noModule = [];
|
|
822
|
+
const withModule = [];
|
|
823
|
+
for (const def of ipcDefs) {
|
|
824
|
+
if (def.module) {
|
|
825
|
+
withModule.push(def);
|
|
826
|
+
} else {
|
|
827
|
+
noModule.push(def);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
lines.push("interface ElecApi {");
|
|
831
|
+
for (const def of noModule) {
|
|
832
|
+
for (const [name, handler] of def.handlers) {
|
|
833
|
+
this.generateTypesIpcMethod(name, handler, lines, " ");
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
for (const def of withModule) {
|
|
837
|
+
lines.push(` ${def.module}: {`);
|
|
838
|
+
for (const [name, handler] of def.handlers) {
|
|
839
|
+
this.generateTypesIpcMethod(name, handler, lines, " ");
|
|
840
|
+
}
|
|
841
|
+
lines.push(" }");
|
|
842
|
+
}
|
|
843
|
+
lines.push("}");
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* 生成单个 IPC 方法的类型签名
|
|
847
|
+
*
|
|
848
|
+
* @param name 方法名
|
|
849
|
+
* @param handler 处理器信息
|
|
850
|
+
* @param lines 输出行数组
|
|
851
|
+
* @param indent 缩进字符串
|
|
852
|
+
*/
|
|
853
|
+
generateTypesIpcMethod(name, handler, lines, indent) {
|
|
854
|
+
if (this.config.preserveComments && handler.comment) {
|
|
855
|
+
const commentLines = handler.comment.split("\n");
|
|
856
|
+
for (const cl of commentLines) {
|
|
857
|
+
lines.push(`${indent}${cl.trim()}`);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
const params = handler.paramNames.map((n, i) => `${n}: ${handler.paramTypes[i]}`).join(", ");
|
|
861
|
+
const type = handler.handlerType ?? "handle";
|
|
862
|
+
const returnType = type === "on" || type === "once" ? "void" : this.ensurePromise(handler.returnType);
|
|
863
|
+
lines.push(`${indent}${name}: (${params}) => ${returnType}`);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* 生成 ElecEvents 接口中的事件方法类型
|
|
867
|
+
*/
|
|
868
|
+
generateTypesEvents(eventDefs, lines) {
|
|
869
|
+
const noModule = [];
|
|
870
|
+
const withModule = [];
|
|
871
|
+
for (const def of eventDefs) {
|
|
872
|
+
if (def.module) {
|
|
873
|
+
withModule.push(def);
|
|
874
|
+
} else {
|
|
875
|
+
noModule.push(def);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
lines.push("export interface ElecEvents {");
|
|
879
|
+
for (const def of noModule) {
|
|
880
|
+
for (const [name, handler] of def.handlers) {
|
|
881
|
+
this.generateTypesEventMethods(name, handler, lines, " ");
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
for (const def of withModule) {
|
|
885
|
+
lines.push(` ${def.module}: {`);
|
|
886
|
+
for (const [name, handler] of def.handlers) {
|
|
887
|
+
this.generateTypesEventMethods(name, handler, lines, " ");
|
|
888
|
+
}
|
|
889
|
+
lines.push(" }");
|
|
890
|
+
}
|
|
891
|
+
lines.push("}");
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* 生成单个事件的类型签名(监听 + remove 方法)
|
|
895
|
+
*/
|
|
896
|
+
generateTypesEventMethods(name, handler, lines, indent) {
|
|
897
|
+
const listenerName = handler.once && !name.startsWith("once") ? name.replace(/^on/, "once") : name;
|
|
898
|
+
if (this.config.preserveComments && handler.comment) {
|
|
899
|
+
const commentLines = handler.comment.split("\n");
|
|
900
|
+
for (const cl of commentLines) {
|
|
901
|
+
lines.push(`${indent}${cl.trim()}`);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (handler.once) {
|
|
905
|
+
lines.push(`${indent}${listenerName}: ${handler.getListenerSignature()}`);
|
|
906
|
+
} else {
|
|
907
|
+
lines.push(`${indent}${listenerName}: ${handler.getListenerSignature()}`);
|
|
908
|
+
}
|
|
909
|
+
const baseName = this.stripEventPrefix(name);
|
|
910
|
+
if (handler.remove === "single" || handler.remove === "both") {
|
|
911
|
+
lines.push(`${indent}remove${baseName}: ${handler.getRemoveSingleSignature()}`);
|
|
912
|
+
}
|
|
913
|
+
if (handler.remove === "all" || handler.remove === "both") {
|
|
914
|
+
lines.push(`${indent}remove${baseName}All: ${handler.getRemoveAllSignature()}`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
// ==================== 工具方法 ====================
|
|
918
|
+
/**
|
|
919
|
+
* 收集所有 IPC 和 Event 定义中引用的类型导入语句
|
|
920
|
+
*
|
|
921
|
+
* 将各文件的 imports 映射合并,按模块路径分组,
|
|
922
|
+
* 生成相对于输出文件的 import type 语句。
|
|
923
|
+
*
|
|
924
|
+
* @param ipcDefs IPC 定义列表
|
|
925
|
+
* @param eventDefs Event 定义列表
|
|
926
|
+
* @returns import 语句列表
|
|
927
|
+
*/
|
|
928
|
+
collectTypeImports(ipcDefs, eventDefs, outputPath) {
|
|
929
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
930
|
+
const addImports = (imports, sourceFilePath) => {
|
|
931
|
+
for (const [typeName, modulePath] of imports) {
|
|
932
|
+
const resolvedPath = this.resolveImportPath(sourceFilePath, modulePath, outputPath);
|
|
933
|
+
if (!grouped.has(resolvedPath)) {
|
|
934
|
+
grouped.set(resolvedPath, /* @__PURE__ */ new Set());
|
|
935
|
+
}
|
|
936
|
+
grouped.get(resolvedPath).add(typeName);
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
for (const def of ipcDefs) {
|
|
940
|
+
addImports(def.imports, def.filePath);
|
|
941
|
+
}
|
|
942
|
+
for (const def of eventDefs) {
|
|
943
|
+
addImports(def.imports, def.filePath);
|
|
944
|
+
}
|
|
945
|
+
const result = [];
|
|
946
|
+
for (const [modulePath, types] of grouped) {
|
|
947
|
+
const typeNames = Array.from(types).sort().join(", ");
|
|
948
|
+
result.push(`import type { ${typeNames} } from '${modulePath}'`);
|
|
949
|
+
}
|
|
950
|
+
return result;
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* 将源文件中的模块路径转换为相对于输出文件的路径
|
|
954
|
+
*
|
|
955
|
+
* 支持三种输入:
|
|
956
|
+
* - 相对路径(以 . 开头): 相对于源文件解析后再转换
|
|
957
|
+
* - 绝对路径: 直接计算相对于输出文件的路径
|
|
958
|
+
* - 包导入(非相对/绝对路径): 直接返回
|
|
959
|
+
*
|
|
960
|
+
* @param sourceFilePath 源文件绝对路径
|
|
961
|
+
* @param modulePath 模块路径(相对、绝对或包名)
|
|
962
|
+
* @returns 相对于输出文件的模块路径
|
|
963
|
+
*/
|
|
964
|
+
resolveImportPath(sourceFilePath, modulePath, outputPath) {
|
|
965
|
+
let absoluteModulePath;
|
|
966
|
+
if (path.isAbsolute(modulePath)) {
|
|
967
|
+
absoluteModulePath = modulePath;
|
|
968
|
+
} else if (modulePath.startsWith(".")) {
|
|
969
|
+
const sourceDir = path.dirname(sourceFilePath);
|
|
970
|
+
absoluteModulePath = path.resolve(sourceDir, modulePath);
|
|
971
|
+
} else {
|
|
972
|
+
return modulePath;
|
|
973
|
+
}
|
|
974
|
+
const outputDir = path.dirname(path.resolve(outputPath));
|
|
975
|
+
let relativePath = path.relative(outputDir, absoluteModulePath);
|
|
976
|
+
relativePath = relativePath.replace(/\\/g, "/");
|
|
977
|
+
if (!relativePath.startsWith(".")) {
|
|
978
|
+
relativePath = "./" + relativePath;
|
|
979
|
+
}
|
|
980
|
+
return relativePath;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* 将事件名转换为 IPC 通道名(camelCase → kebab-case)
|
|
984
|
+
*
|
|
985
|
+
* 去掉 on/once 前缀后转换:
|
|
986
|
+
* - onDownloadProgress → download-progress
|
|
987
|
+
* - onceReady → ready
|
|
988
|
+
*
|
|
989
|
+
* @param name 事件名
|
|
990
|
+
* @returns 通道名
|
|
991
|
+
*/
|
|
992
|
+
toEventChannel(name) {
|
|
993
|
+
let baseName = name;
|
|
994
|
+
if (baseName.startsWith("once")) {
|
|
995
|
+
baseName = baseName.slice(4);
|
|
996
|
+
} else if (baseName.startsWith("on")) {
|
|
997
|
+
baseName = baseName.slice(2);
|
|
998
|
+
}
|
|
999
|
+
baseName = baseName.charAt(0).toLowerCase() + baseName.slice(1);
|
|
1000
|
+
return baseName.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* 去掉事件名的 on/once 前缀,返回大写开头的基础名
|
|
1004
|
+
*
|
|
1005
|
+
* - onDownloadProgress → DownloadProgress
|
|
1006
|
+
* - onceReady → Ready
|
|
1007
|
+
*
|
|
1008
|
+
* @param name 事件名
|
|
1009
|
+
* @returns 基础名(首字母大写)
|
|
1010
|
+
*/
|
|
1011
|
+
stripEventPrefix(name) {
|
|
1012
|
+
if (name.startsWith("once")) {
|
|
1013
|
+
return name.slice(4);
|
|
1014
|
+
}
|
|
1015
|
+
if (name.startsWith("on")) {
|
|
1016
|
+
return name.slice(2);
|
|
1017
|
+
}
|
|
1018
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* 确保返回类型被 Promise 包裹
|
|
1022
|
+
*
|
|
1023
|
+
* - 已经是 Promise<T> → 原样返回
|
|
1024
|
+
* - 其他类型 → 包裹为 Promise<T>
|
|
1025
|
+
*
|
|
1026
|
+
* @param returnType 原始返回类型
|
|
1027
|
+
* @returns Promise 包裹后的返回类型
|
|
1028
|
+
*/
|
|
1029
|
+
ensurePromise(returnType) {
|
|
1030
|
+
if (returnType.startsWith("Promise<")) {
|
|
1031
|
+
return returnType;
|
|
1032
|
+
}
|
|
1033
|
+
return `Promise<${returnType}>`;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* 写入文件,自动创建目录
|
|
1037
|
+
*/
|
|
1038
|
+
writeFile(filePath, content) {
|
|
1039
|
+
const dir = path.dirname(filePath);
|
|
1040
|
+
if (!fs.existsSync(dir)) {
|
|
1041
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1042
|
+
}
|
|
1043
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
1044
|
+
this.logger.info(`\u5DF2\u5199\u5165: ${filePath}`);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
export { EasyPreloadGenerator };
|