iframe-bridge-kit 1.0.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/README.md +182 -0
- package/dist/full/core.js +1 -0
- package/dist/full/core.mjs +1 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +53 -0
- package/dist/index.mjs +28 -0
- package/dist/mini/core.js +1 -0
- package/dist/mini/core.mjs +1 -0
- package/dist/vite.d.mts +19 -0
- package/dist/vite.d.ts +19 -0
- package/dist/vite.js +719 -0
- package/dist/vite.mjs +688 -0
- package/package.json +59 -0
package/dist/vite.js
ADDED
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/vite.ts
|
|
31
|
+
var vite_exports = {};
|
|
32
|
+
__export(vite_exports, {
|
|
33
|
+
default: () => vitePluginIframeBridge
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(vite_exports);
|
|
36
|
+
var ts = __toESM(require("typescript"));
|
|
37
|
+
var path = __toESM(require("path"));
|
|
38
|
+
var fs = __toESM(require("fs"));
|
|
39
|
+
|
|
40
|
+
// package.json
|
|
41
|
+
var package_default = {
|
|
42
|
+
name: "iframe-bridge-kit",
|
|
43
|
+
version: "1.0.0",
|
|
44
|
+
description: "A type-safe communication bridge for iframes. Define strongly typed RPC APIs for cross-window messaging with ease.",
|
|
45
|
+
main: "dist/index.js",
|
|
46
|
+
module: "dist/index.mjs",
|
|
47
|
+
types: "dist/index.d.ts",
|
|
48
|
+
scripts: {
|
|
49
|
+
build: "tsup && tsup --config core.tsup.config.ts && tsup --config core.full.tsup.config.ts",
|
|
50
|
+
dev: "tsup --watch"
|
|
51
|
+
},
|
|
52
|
+
exports: {
|
|
53
|
+
".": {
|
|
54
|
+
types: "./dist/index.d.ts",
|
|
55
|
+
import: "./dist/index.mjs",
|
|
56
|
+
require: "./dist/index.js"
|
|
57
|
+
},
|
|
58
|
+
"./vite": {
|
|
59
|
+
types: "./dist/vite.d.ts",
|
|
60
|
+
import: "./dist/vite.mjs",
|
|
61
|
+
require: "./dist/vite.js"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
files: [
|
|
65
|
+
"dist",
|
|
66
|
+
"README.md"
|
|
67
|
+
],
|
|
68
|
+
keywords: [
|
|
69
|
+
"iframe",
|
|
70
|
+
"postMessage",
|
|
71
|
+
"rpc",
|
|
72
|
+
"bridge",
|
|
73
|
+
"typescript",
|
|
74
|
+
"type-safe",
|
|
75
|
+
"cross-origin",
|
|
76
|
+
"penpal",
|
|
77
|
+
"window-messaging",
|
|
78
|
+
"api-definition"
|
|
79
|
+
],
|
|
80
|
+
author: "ZhangSan <2306860505@qq.com>",
|
|
81
|
+
license: "MIT",
|
|
82
|
+
repository: {
|
|
83
|
+
type: "git",
|
|
84
|
+
url: "git+https://github.com/mchao123/iframe-bridge-kit.git"
|
|
85
|
+
},
|
|
86
|
+
homepage: "https://github.com/mchao123/iframe-bridge-kit#readme",
|
|
87
|
+
bugs: {
|
|
88
|
+
url: "https://github.com/mchao123/iframe-bridge-kit/issues"
|
|
89
|
+
},
|
|
90
|
+
devDependencies: {
|
|
91
|
+
"@types/node": "^24.10.1",
|
|
92
|
+
tsup: "^8.5.1",
|
|
93
|
+
vite: "^7.2.6",
|
|
94
|
+
typescript: "^5.9.3"
|
|
95
|
+
},
|
|
96
|
+
peerDependencies: {
|
|
97
|
+
penpal: "^7.0.4"
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/vite.ts
|
|
102
|
+
var packageName = package_default.name;
|
|
103
|
+
function extractScriptFromVue(code) {
|
|
104
|
+
const scriptRegex = /<script(?:\s+setup)?(?:\s+lang\s*=\s*["']?(ts|typescript)["']?)?[^>]*>([\s\S]*?)<\/script>/i;
|
|
105
|
+
const match = code.match(scriptRegex);
|
|
106
|
+
if (match) {
|
|
107
|
+
const lang = match[1] || "js";
|
|
108
|
+
const script = match[2] || "";
|
|
109
|
+
const beforeScript = code.substring(0, match.index + match[0].indexOf(">") + 1);
|
|
110
|
+
const startLine = beforeScript.split("\n").length;
|
|
111
|
+
return { script, lang, startLine };
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
function createTypeCheckerProgram(code, filePath) {
|
|
116
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
117
|
+
const isVueFile = normalizedPath.endsWith(".vue");
|
|
118
|
+
const tempFileName = isVueFile ? normalizedPath.replace(".vue", ".__bridge_temp__.ts") : normalizedPath + ".__bridge_temp__.ts";
|
|
119
|
+
let needsCleanup = false;
|
|
120
|
+
if (isVueFile) {
|
|
121
|
+
try {
|
|
122
|
+
fs.writeFileSync(tempFileName, code, "utf-8");
|
|
123
|
+
needsCleanup = true;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.error(`[iframe-bridge] Failed to write temp file: ${tempFileName}`, err);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const cleanup = () => {
|
|
129
|
+
if (needsCleanup && fs.existsSync(tempFileName)) {
|
|
130
|
+
try {
|
|
131
|
+
fs.unlinkSync(tempFileName);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const configPath = ts.findConfigFile(process.cwd(), ts.sys.fileExists, "tsconfig.json");
|
|
137
|
+
let compilerOptions = {
|
|
138
|
+
target: ts.ScriptTarget.ESNext,
|
|
139
|
+
module: ts.ModuleKind.ESNext,
|
|
140
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
141
|
+
strict: true,
|
|
142
|
+
esModuleInterop: true,
|
|
143
|
+
skipLibCheck: true,
|
|
144
|
+
noEmit: true
|
|
145
|
+
};
|
|
146
|
+
if (configPath) {
|
|
147
|
+
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
148
|
+
if (configFile.config) {
|
|
149
|
+
const parsed = ts.parseJsonConfigFileContent(
|
|
150
|
+
configFile.config,
|
|
151
|
+
ts.sys,
|
|
152
|
+
path.dirname(configPath)
|
|
153
|
+
);
|
|
154
|
+
compilerOptions = { ...compilerOptions, ...parsed.options };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const targetFile = isVueFile ? tempFileName : normalizedPath;
|
|
158
|
+
const program = ts.createProgram([targetFile], compilerOptions);
|
|
159
|
+
const sourceFile = program.getSourceFile(targetFile);
|
|
160
|
+
return { program, sourceFile, cleanup };
|
|
161
|
+
}
|
|
162
|
+
function getTypeString(checker, type, inTypeAlias) {
|
|
163
|
+
return inTypeAlias ? checker.typeToString(
|
|
164
|
+
type,
|
|
165
|
+
void 0,
|
|
166
|
+
ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.InTypeAlias | // 强制展开类型别名
|
|
167
|
+
ts.TypeFormatFlags.UseFullyQualifiedType | ts.TypeFormatFlags.WriteArrayAsGenericType
|
|
168
|
+
) : checker.typeToString(
|
|
169
|
+
type,
|
|
170
|
+
void 0,
|
|
171
|
+
ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseFullyQualifiedType | ts.TypeFormatFlags.WriteArrayAsGenericType
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
function getJsDoc(node, sourceFile) {
|
|
175
|
+
const jsDocs = ts.getJSDocCommentsAndTags(node);
|
|
176
|
+
if (jsDocs.length === 0) {
|
|
177
|
+
return void 0;
|
|
178
|
+
}
|
|
179
|
+
const fullText = sourceFile.getFullText();
|
|
180
|
+
const comments = [];
|
|
181
|
+
for (const jsDoc of jsDocs) {
|
|
182
|
+
if (ts.isJSDoc(jsDoc)) {
|
|
183
|
+
const start = jsDoc.getStart(sourceFile);
|
|
184
|
+
const end = jsDoc.getEnd();
|
|
185
|
+
const text = fullText.substring(start, end);
|
|
186
|
+
comments.push(text);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return comments.length > 0 ? comments.join("\n") : void 0;
|
|
190
|
+
}
|
|
191
|
+
function parseBridgeFromCode(code, filePath) {
|
|
192
|
+
const results = [];
|
|
193
|
+
let cleanup = () => {
|
|
194
|
+
};
|
|
195
|
+
let scriptCode = code;
|
|
196
|
+
const isVueFile = filePath.endsWith(".vue");
|
|
197
|
+
if (isVueFile) {
|
|
198
|
+
const extracted = extractScriptFromVue(code);
|
|
199
|
+
if (!extracted || extracted.lang !== "ts" && extracted.lang !== "typescript") {
|
|
200
|
+
return { results, cleanup };
|
|
201
|
+
}
|
|
202
|
+
scriptCode = extracted.script;
|
|
203
|
+
}
|
|
204
|
+
const { program, sourceFile, cleanup: programCleanup } = createTypeCheckerProgram(scriptCode, filePath);
|
|
205
|
+
cleanup = programCleanup;
|
|
206
|
+
if (!sourceFile) {
|
|
207
|
+
console.warn(`[iframe-bridge] Could not parse source file: ${filePath}`);
|
|
208
|
+
return { results, cleanup };
|
|
209
|
+
}
|
|
210
|
+
const checker = program.getTypeChecker();
|
|
211
|
+
const typeDeclarations = [];
|
|
212
|
+
const typeImports = /* @__PURE__ */ new Map();
|
|
213
|
+
const collectTypeImports = (node) => {
|
|
214
|
+
if (ts.isIdentifier(node)) {
|
|
215
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
216
|
+
if (symbol && symbol.declarations && symbol.declarations.length > 0) {
|
|
217
|
+
const decl = symbol.declarations[0];
|
|
218
|
+
if (ts.isImportSpecifier(decl)) {
|
|
219
|
+
const importDecl = decl.parent.parent.parent;
|
|
220
|
+
if (ts.isImportDeclaration(importDecl) && ts.isStringLiteral(importDecl.moduleSpecifier)) {
|
|
221
|
+
let modulePath = importDecl.moduleSpecifier.text;
|
|
222
|
+
const moduleSymbol = checker.getSymbolAtLocation(importDecl.moduleSpecifier);
|
|
223
|
+
if (moduleSymbol) {
|
|
224
|
+
const decls = moduleSymbol.getDeclarations();
|
|
225
|
+
if (decls && decls.length > 0 && ts.isSourceFile(decls[0])) {
|
|
226
|
+
modulePath = decls[0].fileName;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const propertyName = decl.propertyName?.text;
|
|
230
|
+
const name = decl.name.text;
|
|
231
|
+
const importStr = propertyName ? `${propertyName} as ${name}` : name;
|
|
232
|
+
if (!typeImports.has(modulePath)) {
|
|
233
|
+
typeImports.set(modulePath, /* @__PURE__ */ new Set());
|
|
234
|
+
}
|
|
235
|
+
typeImports.get(modulePath).add(importStr);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
ts.forEachChild(node, collectTypeImports);
|
|
241
|
+
};
|
|
242
|
+
for (const stmt of sourceFile.statements) {
|
|
243
|
+
if (ts.isTypeAliasDeclaration(stmt) || ts.isInterfaceDeclaration(stmt)) {
|
|
244
|
+
typeDeclarations.push({
|
|
245
|
+
name: stmt.name.text,
|
|
246
|
+
content: stmt.getText(sourceFile)
|
|
247
|
+
});
|
|
248
|
+
collectTypeImports(stmt);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const defineBridgeNames = /* @__PURE__ */ new Set();
|
|
252
|
+
for (const stmt of sourceFile.statements) {
|
|
253
|
+
if (ts.isImportDeclaration(stmt) && ts.isStringLiteral(stmt.moduleSpecifier) && stmt.moduleSpecifier.text === packageName) {
|
|
254
|
+
const namedBindings = stmt.importClause?.namedBindings;
|
|
255
|
+
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
|
256
|
+
for (const element of namedBindings.elements) {
|
|
257
|
+
const originalName = element.propertyName?.text ?? element.name.text;
|
|
258
|
+
if (originalName === "defineBridge") {
|
|
259
|
+
defineBridgeNames.add(element.name.text);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (defineBridgeNames.size === 0) {
|
|
266
|
+
return { results, cleanup };
|
|
267
|
+
}
|
|
268
|
+
const visit = (node) => {
|
|
269
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && defineBridgeNames.has(node.expression.text)) {
|
|
270
|
+
const [nameArg, methodsArg] = node.arguments;
|
|
271
|
+
if (!nameArg || !ts.isStringLiteral(nameArg)) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const bridgeName = nameArg.text;
|
|
275
|
+
if (!methodsArg || !ts.isObjectLiteralExpression(methodsArg)) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const methods = [];
|
|
279
|
+
for (const prop of methodsArg.properties) {
|
|
280
|
+
let methodName = null;
|
|
281
|
+
let params = "";
|
|
282
|
+
let returnType = "void";
|
|
283
|
+
let jsdoc = void 0;
|
|
284
|
+
if (ts.isMethodDeclaration(prop)) {
|
|
285
|
+
methodName = prop.name && ts.isIdentifier(prop.name) ? prop.name.text : null;
|
|
286
|
+
if (!methodName) continue;
|
|
287
|
+
const signature = checker.getSignatureFromDeclaration(prop);
|
|
288
|
+
if (signature) {
|
|
289
|
+
const paramStrings = prop.parameters.map((p) => {
|
|
290
|
+
const paramName = p.name.getText(sourceFile);
|
|
291
|
+
const paramSymbol = checker.getSymbolAtLocation(p.name);
|
|
292
|
+
let paramType = "unknown";
|
|
293
|
+
if (paramSymbol) {
|
|
294
|
+
const type = checker.getTypeOfSymbolAtLocation(paramSymbol, p);
|
|
295
|
+
paramType = getTypeString(checker, type);
|
|
296
|
+
} else if (p.type) {
|
|
297
|
+
paramType = p.type.getText(sourceFile);
|
|
298
|
+
}
|
|
299
|
+
const optional = p.questionToken ? "?" : "";
|
|
300
|
+
return `${paramName}${optional}: ${paramType}`;
|
|
301
|
+
});
|
|
302
|
+
params = paramStrings.join(", ");
|
|
303
|
+
const retType = signature.getReturnType();
|
|
304
|
+
returnType = getTypeString(checker, retType);
|
|
305
|
+
}
|
|
306
|
+
jsdoc = getJsDoc(prop, sourceFile);
|
|
307
|
+
} else if (ts.isPropertyAssignment(prop)) {
|
|
308
|
+
methodName = prop.name && ts.isIdentifier(prop.name) ? prop.name.text : null;
|
|
309
|
+
if (!methodName) continue;
|
|
310
|
+
const init = prop.initializer;
|
|
311
|
+
if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) {
|
|
312
|
+
const signature = checker.getSignatureFromDeclaration(init);
|
|
313
|
+
if (signature) {
|
|
314
|
+
const paramStrings = init.parameters.map((p) => {
|
|
315
|
+
const paramName = p.name.getText(sourceFile);
|
|
316
|
+
const paramSymbol = checker.getSymbolAtLocation(p.name);
|
|
317
|
+
let paramType = "unknown";
|
|
318
|
+
if (paramSymbol) {
|
|
319
|
+
const type = checker.getTypeOfSymbolAtLocation(paramSymbol, p);
|
|
320
|
+
paramType = getTypeString(checker, type);
|
|
321
|
+
} else if (p.type) {
|
|
322
|
+
paramType = p.type.getText(sourceFile);
|
|
323
|
+
}
|
|
324
|
+
const optional = p.questionToken ? "?" : "";
|
|
325
|
+
return `${paramName}${optional}: ${paramType}`;
|
|
326
|
+
});
|
|
327
|
+
params = paramStrings.join(", ");
|
|
328
|
+
const retType = signature.getReturnType();
|
|
329
|
+
returnType = getTypeString(checker, retType);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
jsdoc = getJsDoc(prop, sourceFile);
|
|
333
|
+
} else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
334
|
+
methodName = prop.name.text;
|
|
335
|
+
const symbol = checker.getSymbolAtLocation(prop.name);
|
|
336
|
+
if (symbol) {
|
|
337
|
+
const type = checker.getTypeOfSymbolAtLocation(symbol, prop);
|
|
338
|
+
const callSignatures = type.getCallSignatures();
|
|
339
|
+
if (callSignatures.length > 0) {
|
|
340
|
+
const signature = callSignatures[0];
|
|
341
|
+
const paramStrings = signature.getParameters().map((param) => {
|
|
342
|
+
const paramType = checker.getTypeOfSymbolAtLocation(param, prop);
|
|
343
|
+
const paramName = param.getName();
|
|
344
|
+
return `${paramName}: ${getTypeString(checker, paramType)}`;
|
|
345
|
+
});
|
|
346
|
+
params = paramStrings.join(", ");
|
|
347
|
+
const retType = signature.getReturnType();
|
|
348
|
+
returnType = getTypeString(checker, retType);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
jsdoc = getJsDoc(prop, sourceFile);
|
|
352
|
+
if (!jsdoc && symbol && symbol.valueDeclaration) {
|
|
353
|
+
jsdoc = getJsDoc(symbol.valueDeclaration, sourceFile);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (methodName) {
|
|
357
|
+
methods.push({
|
|
358
|
+
name: methodName,
|
|
359
|
+
params,
|
|
360
|
+
returnType,
|
|
361
|
+
jsdoc
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const emitMap = [];
|
|
366
|
+
if (node.typeArguments && node.typeArguments.length > 0) {
|
|
367
|
+
const emitTypeNode = node.typeArguments[0];
|
|
368
|
+
const emitType = checker.getTypeFromTypeNode(emitTypeNode);
|
|
369
|
+
emitType.getProperties().forEach((prop) => {
|
|
370
|
+
const name = prop.getName();
|
|
371
|
+
let type = "any";
|
|
372
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, node);
|
|
373
|
+
type = getTypeString(checker, propType);
|
|
374
|
+
emitMap.push({ name, type });
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
const bridgeImports = /* @__PURE__ */ new Map();
|
|
378
|
+
mergeImports(bridgeImports, typeImports);
|
|
379
|
+
results.push({
|
|
380
|
+
name: bridgeName,
|
|
381
|
+
methods,
|
|
382
|
+
sourceFile: filePath,
|
|
383
|
+
typeDeclarations,
|
|
384
|
+
imports: bridgeImports,
|
|
385
|
+
emitMap
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
ts.forEachChild(node, visit);
|
|
389
|
+
};
|
|
390
|
+
ts.forEachChild(sourceFile, visit);
|
|
391
|
+
return { results, cleanup };
|
|
392
|
+
}
|
|
393
|
+
function extractTypeExport(filePath, typeName) {
|
|
394
|
+
if (!fs.existsSync(filePath)) return null;
|
|
395
|
+
try {
|
|
396
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
397
|
+
const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
|
|
398
|
+
let result = null;
|
|
399
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
400
|
+
if (result) return;
|
|
401
|
+
if (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isClassDeclaration(node)) {
|
|
402
|
+
if (node.name?.text === typeName) {
|
|
403
|
+
let text = node.getText(sourceFile);
|
|
404
|
+
text = text.replace(/^\s*export\s+/, "");
|
|
405
|
+
text = text.replace(/^\s*declare\s+/, "");
|
|
406
|
+
result = text;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
return result;
|
|
411
|
+
} catch (e) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
function processTypeString(typeStr, outDir, registry, preserveModules = []) {
|
|
416
|
+
const imports = /* @__PURE__ */ new Map();
|
|
417
|
+
const outputDir = path.resolve(process.cwd(), outDir);
|
|
418
|
+
const importPattern = /import\("([^"]+)"\)\.(\w+)/g;
|
|
419
|
+
const processed = typeStr.replace(importPattern, (match, importPath, typeName) => {
|
|
420
|
+
let normalizedPath = importPath.replace(/\\/g, "/");
|
|
421
|
+
const nodeModulesIndex = normalizedPath.lastIndexOf("/node_modules/");
|
|
422
|
+
const isNodeModule = nodeModulesIndex !== -1;
|
|
423
|
+
if (isNodeModule) {
|
|
424
|
+
const afterNodeModules = normalizedPath.substring(nodeModulesIndex + 14);
|
|
425
|
+
const parts = afterNodeModules.split("/");
|
|
426
|
+
let moduleName = parts[0].startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
427
|
+
let shouldPreserve = preserveModules.some((m) => moduleName === m || moduleName.startsWith(m + "/"));
|
|
428
|
+
if (preserveModules.includes("vue") && moduleName.startsWith("@vue/")) {
|
|
429
|
+
shouldPreserve = true;
|
|
430
|
+
moduleName = "vue";
|
|
431
|
+
}
|
|
432
|
+
if (shouldPreserve) {
|
|
433
|
+
if (!imports.has(moduleName)) {
|
|
434
|
+
imports.set(moduleName, /* @__PURE__ */ new Set());
|
|
435
|
+
}
|
|
436
|
+
imports.get(moduleName).add(typeName);
|
|
437
|
+
return typeName;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
let absoluteImportPath = normalizedPath;
|
|
441
|
+
if (!fs.existsSync(absoluteImportPath) && !absoluteImportPath.endsWith(".ts")) {
|
|
442
|
+
if (fs.existsSync(absoluteImportPath + ".ts")) {
|
|
443
|
+
absoluteImportPath += ".ts";
|
|
444
|
+
} else if (fs.existsSync(absoluteImportPath + ".d.ts")) {
|
|
445
|
+
absoluteImportPath += ".d.ts";
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const registryKey = `${absoluteImportPath}::${typeName}`;
|
|
449
|
+
if (registry.has(registryKey)) {
|
|
450
|
+
return registry.get(registryKey).uniqueName;
|
|
451
|
+
}
|
|
452
|
+
const definition = extractTypeExport(absoluteImportPath, typeName);
|
|
453
|
+
if (definition) {
|
|
454
|
+
const uniqueSuffix = Math.random().toString(36).substring(2, 8);
|
|
455
|
+
const uniqueName = `${typeName}_${uniqueSuffix}`;
|
|
456
|
+
const renameRegex = new RegExp(`\\b${typeName}\\b(?!\\s*[:(])`, "g");
|
|
457
|
+
const renamedDefinition = definition.replace(renameRegex, uniqueName);
|
|
458
|
+
registry.set(registryKey, {
|
|
459
|
+
uniqueName,
|
|
460
|
+
definition: renamedDefinition
|
|
461
|
+
});
|
|
462
|
+
return uniqueName;
|
|
463
|
+
}
|
|
464
|
+
const relativeFromOutput = path.relative(outputDir, absoluteImportPath).replace(/\\/g, "/");
|
|
465
|
+
const cleanPath = relativeFromOutput.replace(/(\.d)?\.ts$/, "").replace(/\.vue$/, "");
|
|
466
|
+
const finalPath = cleanPath.startsWith(".") ? cleanPath : "./" + cleanPath;
|
|
467
|
+
if (!imports.has(finalPath)) {
|
|
468
|
+
imports.set(finalPath, /* @__PURE__ */ new Set());
|
|
469
|
+
}
|
|
470
|
+
imports.get(finalPath).add(typeName);
|
|
471
|
+
return typeName;
|
|
472
|
+
});
|
|
473
|
+
return { processed, imports };
|
|
474
|
+
}
|
|
475
|
+
function mergeImports(target, source) {
|
|
476
|
+
for (const [mod, types] of source) {
|
|
477
|
+
if (!target.has(mod)) {
|
|
478
|
+
target.set(mod, /* @__PURE__ */ new Set());
|
|
479
|
+
}
|
|
480
|
+
for (const t of types) {
|
|
481
|
+
target.get(mod).add(t);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
function generateDtsContent(info, outDir, preserveModules = []) {
|
|
486
|
+
const allImports = /* @__PURE__ */ new Map();
|
|
487
|
+
const registry = /* @__PURE__ */ new Map();
|
|
488
|
+
const outputDir = path.resolve(process.cwd(), outDir);
|
|
489
|
+
if (info.imports) {
|
|
490
|
+
for (const [pathKey, types] of info.imports) {
|
|
491
|
+
let normalizedPath = pathKey.replace(/\\/g, "/");
|
|
492
|
+
if (normalizedPath.includes("/node_modules/")) {
|
|
493
|
+
const index = normalizedPath.lastIndexOf("/node_modules/");
|
|
494
|
+
const after = normalizedPath.substring(index + 14);
|
|
495
|
+
const parts = after.split("/");
|
|
496
|
+
let mod = parts[0].startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
497
|
+
if (allImports.has(mod)) {
|
|
498
|
+
types.forEach((t) => allImports.get(mod).add(t));
|
|
499
|
+
} else {
|
|
500
|
+
allImports.set(mod, new Set(types));
|
|
501
|
+
}
|
|
502
|
+
} else if (path.isAbsolute(normalizedPath)) {
|
|
503
|
+
const rel = path.relative(outputDir, normalizedPath).replace(/\\/g, "/");
|
|
504
|
+
const clean = rel.replace(/(\.d)?\.ts$/, "").replace(/\.vue$/, "");
|
|
505
|
+
const final = clean.startsWith(".") ? clean : "./" + clean;
|
|
506
|
+
if (allImports.has(final)) {
|
|
507
|
+
types.forEach((t) => allImports.get(final).add(t));
|
|
508
|
+
} else {
|
|
509
|
+
allImports.set(final, new Set(types));
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
if (allImports.has(normalizedPath)) {
|
|
513
|
+
types.forEach((t) => allImports.get(normalizedPath).add(t));
|
|
514
|
+
} else {
|
|
515
|
+
allImports.set(normalizedPath, new Set(types));
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
const processedMethods = info.methods.map((m) => {
|
|
521
|
+
const resultParams = processTypeString(m.params, outDir, registry, preserveModules);
|
|
522
|
+
const resultReturn = processTypeString(m.returnType, outDir, registry, preserveModules);
|
|
523
|
+
mergeImports(allImports, resultParams.imports);
|
|
524
|
+
mergeImports(allImports, resultReturn.imports);
|
|
525
|
+
return {
|
|
526
|
+
...m,
|
|
527
|
+
params: resultParams.processed,
|
|
528
|
+
returnType: resultReturn.processed.startsWith("Promise") ? resultReturn.processed : `Promise<${resultReturn.processed}>`
|
|
529
|
+
};
|
|
530
|
+
});
|
|
531
|
+
const emitMap = info.emitMap || [];
|
|
532
|
+
const processedEmitTypes = emitMap.map((e) => {
|
|
533
|
+
const res = processTypeString(e.type, outDir, registry, preserveModules);
|
|
534
|
+
mergeImports(allImports, res.imports);
|
|
535
|
+
return { name: e.name, type: res.processed };
|
|
536
|
+
});
|
|
537
|
+
const lines = [
|
|
538
|
+
"// Auto-generated by vite-plugin-iframe-bridge",
|
|
539
|
+
"// Do not edit this file manually",
|
|
540
|
+
""
|
|
541
|
+
];
|
|
542
|
+
if (allImports.size > 0) {
|
|
543
|
+
for (const [mod, types] of allImports) {
|
|
544
|
+
const typeList = Array.from(types).sort().join(", ");
|
|
545
|
+
lines.push(`import type { ${typeList} } from '${mod}';`);
|
|
546
|
+
}
|
|
547
|
+
lines.push("");
|
|
548
|
+
}
|
|
549
|
+
if (registry.size > 0) {
|
|
550
|
+
lines.push("// Copied type definitions");
|
|
551
|
+
const sortedDefs = Array.from(registry.values()).sort((a, b) => a.uniqueName.localeCompare(b.uniqueName));
|
|
552
|
+
for (const { definition } of sortedDefs) {
|
|
553
|
+
lines.push(definition);
|
|
554
|
+
lines.push("");
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (processedEmitTypes.length > 0) {
|
|
558
|
+
lines.push("export interface EmitMap {");
|
|
559
|
+
processedEmitTypes.forEach((e) => {
|
|
560
|
+
lines.push(` "${e.name}": ${e.type};`);
|
|
561
|
+
});
|
|
562
|
+
lines.push("}");
|
|
563
|
+
lines.push("");
|
|
564
|
+
lines.push("export declare function onMessage<K extends keyof EmitMap>(type: K, cb: (data: EmitMap[K]) => void, once?: boolean): () => void;");
|
|
565
|
+
lines.push("export declare function onMessage(type: string, cb: Function, once?: boolean): () => void;");
|
|
566
|
+
lines.push("export declare function offMessage<K extends keyof EmitMap>(type: K, fn?: (data: EmitMap[K]) => void): void;");
|
|
567
|
+
lines.push("export declare function offMessage(type: string, fn?: Function): void;");
|
|
568
|
+
} else {
|
|
569
|
+
lines.push("export declare function onMessage(type: string, cb: Function, once?: boolean): () => void;");
|
|
570
|
+
lines.push("export declare function offMessage(type: string, fn?: Function): void;");
|
|
571
|
+
}
|
|
572
|
+
lines.push("export declare function isInit(): boolean;");
|
|
573
|
+
lines.push("export declare function onInit(cb: Function): void;");
|
|
574
|
+
lines.push("");
|
|
575
|
+
lines.push(`export default {} as {`);
|
|
576
|
+
lines.push(
|
|
577
|
+
...processedMethods.map(
|
|
578
|
+
(m) => (m.jsdoc ? ` ${m.jsdoc}
|
|
579
|
+
` : "") + ` ${m.name}: (${m.params}) => ${m.returnType},`
|
|
580
|
+
)
|
|
581
|
+
);
|
|
582
|
+
lines.push("}");
|
|
583
|
+
lines.push("");
|
|
584
|
+
return lines.join("\n");
|
|
585
|
+
}
|
|
586
|
+
function writeDtsFile(info, options) {
|
|
587
|
+
const outDir = options.outDir || "bridges";
|
|
588
|
+
const absoluteOutDir = path.resolve(process.cwd(), outDir);
|
|
589
|
+
const dtsContent = generateDtsContent(info, outDir, options.preserveModules);
|
|
590
|
+
const dtsDir = path.join(absoluteOutDir, info.name);
|
|
591
|
+
if (!fs.existsSync(dtsDir)) {
|
|
592
|
+
fs.mkdirSync(dtsDir, { recursive: true });
|
|
593
|
+
}
|
|
594
|
+
fs.writeFileSync(path.join(dtsDir, "index.d.ts"), dtsContent, "utf-8");
|
|
595
|
+
fs.writeFileSync(path.join(dtsDir, "index.d.mts"), dtsContent, "utf-8");
|
|
596
|
+
console.log(`[iframe-bridge] Generated: ${dtsDir}`);
|
|
597
|
+
}
|
|
598
|
+
function processFile(filePath, code, options) {
|
|
599
|
+
if (!code.includes(packageName)) {
|
|
600
|
+
return [];
|
|
601
|
+
}
|
|
602
|
+
if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".vue")) {
|
|
603
|
+
return [];
|
|
604
|
+
}
|
|
605
|
+
try {
|
|
606
|
+
const { results: bridges, cleanup } = parseBridgeFromCode(code, filePath);
|
|
607
|
+
for (const bridge of bridges) {
|
|
608
|
+
writeDtsFile(bridge, options);
|
|
609
|
+
writeBridgeRuntime(bridge.name, options);
|
|
610
|
+
}
|
|
611
|
+
cleanup();
|
|
612
|
+
return bridges;
|
|
613
|
+
} catch (err) {
|
|
614
|
+
console.error(`[iframe-bridge] Error parsing ${filePath}:`, err);
|
|
615
|
+
return [];
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function writeBridgeRuntime(bridgeName, options) {
|
|
619
|
+
const isFull = options.full !== false;
|
|
620
|
+
const allowedOrigins = options.allowedOrigins || ["*"];
|
|
621
|
+
const outDir = options.outDir || "bridges";
|
|
622
|
+
const variant = isFull ? "full" : "mini";
|
|
623
|
+
const basePaths = [
|
|
624
|
+
// 1. 假设我们在 dist 目录中运行
|
|
625
|
+
path.resolve(__dirname, variant),
|
|
626
|
+
// 2. 假设我们在 src 目录中运行
|
|
627
|
+
path.resolve(__dirname, "../dist", variant)
|
|
628
|
+
];
|
|
629
|
+
let coreDir = "";
|
|
630
|
+
for (const p of basePaths) {
|
|
631
|
+
if (fs.existsSync(p)) {
|
|
632
|
+
coreDir = p;
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (!coreDir) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
const absoluteOutDir = path.resolve(process.cwd(), outDir, bridgeName);
|
|
640
|
+
if (!fs.existsSync(absoluteOutDir)) {
|
|
641
|
+
fs.mkdirSync(absoluteOutDir, { recursive: true });
|
|
642
|
+
}
|
|
643
|
+
const extensions = ["js", "mjs"];
|
|
644
|
+
const replaceContent = JSON.stringify(allowedOrigins).slice(1, -1);
|
|
645
|
+
for (const ext of extensions) {
|
|
646
|
+
const srcFile = path.join(coreDir, `core.${ext}`);
|
|
647
|
+
if (fs.existsSync(srcFile)) {
|
|
648
|
+
try {
|
|
649
|
+
let content = fs.readFileSync(srcFile, "utf-8");
|
|
650
|
+
content = content.replace(/["']__AllowedOrigins__["']/g, replaceContent);
|
|
651
|
+
const destPath = path.join(absoluteOutDir, `index.${ext}`);
|
|
652
|
+
fs.writeFileSync(destPath, content, "utf-8");
|
|
653
|
+
} catch (err) {
|
|
654
|
+
console.error(`[iframe-bridge] Failed to copy core.${ext}:`, err);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
function vitePluginIframeBridge(options = {}) {
|
|
660
|
+
const bridgeRegistry = /* @__PURE__ */ new Map();
|
|
661
|
+
return {
|
|
662
|
+
name: "vite-plugin-iframe-bridge",
|
|
663
|
+
// 开发服务器启动时配置
|
|
664
|
+
configureServer(server) {
|
|
665
|
+
server.watcher.on("change", (filePath) => {
|
|
666
|
+
if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".vue")) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
try {
|
|
670
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
671
|
+
const bridges = processFile(filePath, code, options);
|
|
672
|
+
for (const bridge of bridges) {
|
|
673
|
+
bridgeRegistry.set(bridge.name, bridge);
|
|
674
|
+
}
|
|
675
|
+
} catch (err) {
|
|
676
|
+
console.error(`[iframe-bridge] Error processing ${filePath}:`, err);
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
const scanDirectory = (dir) => {
|
|
680
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
681
|
+
for (const entry of entries) {
|
|
682
|
+
const fullPath = path.join(dir, entry.name);
|
|
683
|
+
if (entry.isDirectory()) {
|
|
684
|
+
if (entry.name !== "node_modules" && !entry.name.startsWith(".")) {
|
|
685
|
+
scanDirectory(fullPath);
|
|
686
|
+
}
|
|
687
|
+
} else if (entry.isFile()) {
|
|
688
|
+
if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx") || entry.name.endsWith(".vue")) {
|
|
689
|
+
try {
|
|
690
|
+
const code = fs.readFileSync(fullPath, "utf-8");
|
|
691
|
+
const bridges = processFile(fullPath, code, options);
|
|
692
|
+
for (const bridge of bridges) {
|
|
693
|
+
bridgeRegistry.set(bridge.name, bridge);
|
|
694
|
+
}
|
|
695
|
+
} catch (err) {
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
const srcDir = path.resolve(process.cwd(), "src");
|
|
702
|
+
if (fs.existsSync(srcDir)) {
|
|
703
|
+
console.log("[iframe-bridge] Scanning for bridge definitions...");
|
|
704
|
+
scanDirectory(srcDir);
|
|
705
|
+
console.log(`[iframe-bridge] Found ${bridgeRegistry.size} bridge(s)`);
|
|
706
|
+
}
|
|
707
|
+
},
|
|
708
|
+
buildStart() {
|
|
709
|
+
bridgeRegistry.clear();
|
|
710
|
+
},
|
|
711
|
+
transform(code, id) {
|
|
712
|
+
const bridges = processFile(id, code, options);
|
|
713
|
+
for (const bridge of bridges) {
|
|
714
|
+
bridgeRegistry.set(bridge.name, bridge);
|
|
715
|
+
}
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
}
|