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