@aprovan/patchwork-compiler 0.1.0 → 0.1.2-dev.6bd527d
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 +261 -247
- package/dist/index.cjs +2822 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +828 -0
- package/dist/index.d.ts +130 -54
- package/dist/index.js +990 -115
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2822 @@
|
|
|
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/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
CompileOptionsSchema: () => CompileOptionsSchema,
|
|
34
|
+
DEFAULT_CLI_IMAGE_CONFIG: () => DEFAULT_CLI_IMAGE_CONFIG,
|
|
35
|
+
DEFAULT_IMAGE_CONFIG: () => DEFAULT_IMAGE_CONFIG,
|
|
36
|
+
DEV_SANDBOX: () => DEV_SANDBOX,
|
|
37
|
+
EsbuildConfigSchema: () => EsbuildConfigSchema,
|
|
38
|
+
HttpBackend: () => HttpBackend,
|
|
39
|
+
ImageConfigSchema: () => ImageConfigSchema,
|
|
40
|
+
ImageRegistry: () => ImageRegistry,
|
|
41
|
+
IndexedDBBackend: () => IndexedDBBackend,
|
|
42
|
+
InputSpecSchema: () => InputSpecSchema,
|
|
43
|
+
ManifestSchema: () => ManifestSchema,
|
|
44
|
+
MountModeSchema: () => MountModeSchema,
|
|
45
|
+
MountOptionsSchema: () => MountOptionsSchema,
|
|
46
|
+
ParentBridge: () => ParentBridge,
|
|
47
|
+
PlatformSchema: () => PlatformSchema,
|
|
48
|
+
VFSStore: () => VFSStore,
|
|
49
|
+
cdnTransformPlugin: () => cdnTransformPlugin,
|
|
50
|
+
createCompiler: () => createCompiler,
|
|
51
|
+
createFieldAccessProxy: () => createFieldAccessProxy,
|
|
52
|
+
createHttpServiceProxy: () => createHttpServiceProxy,
|
|
53
|
+
createIframeServiceProxy: () => createIframeServiceProxy,
|
|
54
|
+
createImageRegistry: () => createImageRegistry,
|
|
55
|
+
createProjectFromFiles: () => createProjectFromFiles,
|
|
56
|
+
createSingleFileProject: () => createSingleFileProject,
|
|
57
|
+
detectMainFile: () => detectMainFile,
|
|
58
|
+
disposeIframeBridge: () => disposeIframeBridge,
|
|
59
|
+
extractNamespaces: () => extractNamespaces,
|
|
60
|
+
fetchPackageJson: () => fetchPackageJson,
|
|
61
|
+
generateIframeBridgeScript: () => generateIframeBridgeScript,
|
|
62
|
+
generateImportMap: () => generateImportMap,
|
|
63
|
+
generateNamespaceGlobals: () => generateNamespaceGlobals,
|
|
64
|
+
getCdnBaseUrl: () => getCdnBaseUrl,
|
|
65
|
+
getImageRegistry: () => getImageRegistry,
|
|
66
|
+
injectNamespaceGlobals: () => injectNamespaceGlobals,
|
|
67
|
+
loadImage: () => loadImage,
|
|
68
|
+
mountEmbedded: () => mountEmbedded,
|
|
69
|
+
mountIframe: () => mountIframe,
|
|
70
|
+
parseImageConfig: () => parseImageConfig,
|
|
71
|
+
parseImageSpec: () => parseImageSpec,
|
|
72
|
+
parseManifest: () => parseManifest,
|
|
73
|
+
reloadEmbedded: () => reloadEmbedded,
|
|
74
|
+
reloadIframe: () => reloadIframe,
|
|
75
|
+
removeNamespaceGlobals: () => removeNamespaceGlobals,
|
|
76
|
+
resolveEntry: () => resolveEntry,
|
|
77
|
+
safeParseImageConfig: () => safeParseImageConfig,
|
|
78
|
+
safeParseManifest: () => safeParseManifest,
|
|
79
|
+
setCdnBaseUrl: () => setCdnBaseUrl,
|
|
80
|
+
vfsPlugin: () => vfsPlugin
|
|
81
|
+
});
|
|
82
|
+
module.exports = __toCommonJS(index_exports);
|
|
83
|
+
|
|
84
|
+
// ../../node_modules/.pnpm/tsup@8.5.1_jiti@1.21.7_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
|
|
85
|
+
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
86
|
+
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
87
|
+
|
|
88
|
+
// src/compiler.ts
|
|
89
|
+
var esbuild = __toESM(require("esbuild-wasm"), 1);
|
|
90
|
+
|
|
91
|
+
// src/vfs/project.ts
|
|
92
|
+
function createProjectFromFiles(files, id = crypto.randomUUID()) {
|
|
93
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
94
|
+
for (const file of files) {
|
|
95
|
+
fileMap.set(file.path, file);
|
|
96
|
+
}
|
|
97
|
+
return { id, entry: resolveEntry(fileMap), files: fileMap };
|
|
98
|
+
}
|
|
99
|
+
function resolveEntry(files) {
|
|
100
|
+
const paths = Array.from(files.keys());
|
|
101
|
+
const mainFile = paths.find((p) => /\bmain\.(tsx?|jsx?)$/.test(p));
|
|
102
|
+
if (mainFile) return mainFile;
|
|
103
|
+
const indexFile = paths.find((p) => /\bindex\.(tsx?|jsx?)$/.test(p));
|
|
104
|
+
if (indexFile) return indexFile;
|
|
105
|
+
const firstTsx = paths.find((p) => /\.(tsx|jsx)$/.test(p));
|
|
106
|
+
if (firstTsx) return firstTsx;
|
|
107
|
+
return paths[0] || "main.tsx";
|
|
108
|
+
}
|
|
109
|
+
function detectMainFile(language) {
|
|
110
|
+
switch (language) {
|
|
111
|
+
case "tsx":
|
|
112
|
+
case "typescript":
|
|
113
|
+
return "main.tsx";
|
|
114
|
+
case "jsx":
|
|
115
|
+
case "javascript":
|
|
116
|
+
return "main.jsx";
|
|
117
|
+
case "ts":
|
|
118
|
+
return "main.ts";
|
|
119
|
+
case "js":
|
|
120
|
+
return "main.js";
|
|
121
|
+
default:
|
|
122
|
+
return "main.tsx";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function createSingleFileProject(content, entry = "main.tsx", id = "inline") {
|
|
126
|
+
return {
|
|
127
|
+
id,
|
|
128
|
+
entry,
|
|
129
|
+
files: /* @__PURE__ */ new Map([[entry, { path: entry, content }]])
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/schemas.ts
|
|
134
|
+
var import_zod = require("zod");
|
|
135
|
+
var PlatformSchema = import_zod.z.enum(["browser", "cli"]);
|
|
136
|
+
var EsbuildConfigSchema = import_zod.z.object({
|
|
137
|
+
target: import_zod.z.string().optional(),
|
|
138
|
+
format: import_zod.z.enum(["esm", "cjs", "iife"]).optional(),
|
|
139
|
+
jsx: import_zod.z.enum(["automatic", "transform", "preserve"]).optional(),
|
|
140
|
+
jsxFactory: import_zod.z.string().optional(),
|
|
141
|
+
jsxFragment: import_zod.z.string().optional()
|
|
142
|
+
}).strict().optional();
|
|
143
|
+
var FrameworkConfigSchema = import_zod.z.object({
|
|
144
|
+
// Map of package names to window global names (e.g., { react: 'React' })
|
|
145
|
+
globals: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
|
|
146
|
+
// CDN URLs to preload before widget execution
|
|
147
|
+
preload: import_zod.z.array(import_zod.z.string()).optional(),
|
|
148
|
+
// Dependency version overrides for CDN packages (e.g., { react: '18' })
|
|
149
|
+
deps: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional()
|
|
150
|
+
}).strict().optional();
|
|
151
|
+
var AliasesSchema = import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional();
|
|
152
|
+
var DependenciesSchema = import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional();
|
|
153
|
+
var ImageConfigSchema = import_zod.z.object({
|
|
154
|
+
platform: PlatformSchema,
|
|
155
|
+
dependencies: DependenciesSchema,
|
|
156
|
+
esbuild: EsbuildConfigSchema,
|
|
157
|
+
framework: FrameworkConfigSchema,
|
|
158
|
+
aliases: AliasesSchema
|
|
159
|
+
}).strict();
|
|
160
|
+
var InputSpecSchema = import_zod.z.object({
|
|
161
|
+
type: import_zod.z.enum(["string", "number", "boolean", "object", "array"]),
|
|
162
|
+
default: import_zod.z.unknown().optional(),
|
|
163
|
+
required: import_zod.z.boolean().optional(),
|
|
164
|
+
description: import_zod.z.string().optional()
|
|
165
|
+
});
|
|
166
|
+
var ManifestSchema = import_zod.z.object({
|
|
167
|
+
name: import_zod.z.string(),
|
|
168
|
+
version: import_zod.z.string(),
|
|
169
|
+
description: import_zod.z.string().optional(),
|
|
170
|
+
platform: PlatformSchema,
|
|
171
|
+
image: import_zod.z.string(),
|
|
172
|
+
inputs: import_zod.z.record(import_zod.z.string(), InputSpecSchema).optional(),
|
|
173
|
+
services: import_zod.z.array(import_zod.z.string()).optional(),
|
|
174
|
+
packages: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional()
|
|
175
|
+
});
|
|
176
|
+
var CompileOptionsSchema = import_zod.z.object({
|
|
177
|
+
typescript: import_zod.z.boolean().optional()
|
|
178
|
+
}).strict().optional();
|
|
179
|
+
var MountModeSchema = import_zod.z.enum(["embedded", "iframe"]);
|
|
180
|
+
var MountOptionsSchema = import_zod.z.object({
|
|
181
|
+
target: import_zod.z.custom((v) => v instanceof HTMLElement, {
|
|
182
|
+
message: "Expected HTMLElement"
|
|
183
|
+
}),
|
|
184
|
+
mode: MountModeSchema,
|
|
185
|
+
sandbox: import_zod.z.array(import_zod.z.string()).optional(),
|
|
186
|
+
inputs: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
187
|
+
});
|
|
188
|
+
function parseImageConfig(data) {
|
|
189
|
+
return ImageConfigSchema.parse(data);
|
|
190
|
+
}
|
|
191
|
+
function safeParseImageConfig(data) {
|
|
192
|
+
const result = ImageConfigSchema.safeParse(data);
|
|
193
|
+
return result.success ? result.data : null;
|
|
194
|
+
}
|
|
195
|
+
function parseManifest(data) {
|
|
196
|
+
return ManifestSchema.parse(data);
|
|
197
|
+
}
|
|
198
|
+
function safeParseManifest(data) {
|
|
199
|
+
const result = ManifestSchema.safeParse(data);
|
|
200
|
+
return result.success ? result.data : null;
|
|
201
|
+
}
|
|
202
|
+
var DEFAULT_IMAGE_CONFIG = {
|
|
203
|
+
platform: "browser",
|
|
204
|
+
esbuild: {
|
|
205
|
+
target: "es2020",
|
|
206
|
+
format: "esm",
|
|
207
|
+
jsx: "automatic"
|
|
208
|
+
},
|
|
209
|
+
framework: {}
|
|
210
|
+
};
|
|
211
|
+
var DEFAULT_CLI_IMAGE_CONFIG = {
|
|
212
|
+
platform: "cli",
|
|
213
|
+
esbuild: {
|
|
214
|
+
target: "node20",
|
|
215
|
+
format: "esm",
|
|
216
|
+
jsx: "automatic"
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// src/images/loader.ts
|
|
221
|
+
var DEFAULT_CDN_BASE = "https://esm.sh";
|
|
222
|
+
var cdnBaseUrl = DEFAULT_CDN_BASE;
|
|
223
|
+
function setCdnBaseUrl(url) {
|
|
224
|
+
cdnBaseUrl = url;
|
|
225
|
+
}
|
|
226
|
+
function getCdnBaseUrl() {
|
|
227
|
+
return cdnBaseUrl;
|
|
228
|
+
}
|
|
229
|
+
function parseImageSpec(spec) {
|
|
230
|
+
if (spec.startsWith("@")) {
|
|
231
|
+
const parts = spec.split("/");
|
|
232
|
+
if (parts.length >= 2) {
|
|
233
|
+
const scope = parts[0];
|
|
234
|
+
const nameAndVersion = parts.slice(1).join("/");
|
|
235
|
+
const atIndex2 = nameAndVersion.lastIndexOf("@");
|
|
236
|
+
if (atIndex2 > 0) {
|
|
237
|
+
return {
|
|
238
|
+
name: `${scope}/${nameAndVersion.slice(0, atIndex2)}`,
|
|
239
|
+
version: nameAndVersion.slice(atIndex2 + 1)
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return { name: `${scope}/${nameAndVersion}` };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const atIndex = spec.lastIndexOf("@");
|
|
246
|
+
if (atIndex > 0) {
|
|
247
|
+
return {
|
|
248
|
+
name: spec.slice(0, atIndex),
|
|
249
|
+
version: spec.slice(atIndex + 1)
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return { name: spec };
|
|
253
|
+
}
|
|
254
|
+
async function fetchPackageJson(packageName, version) {
|
|
255
|
+
const versionSuffix = version ? `@${version}` : "";
|
|
256
|
+
const url = `${cdnBaseUrl}/${packageName}${versionSuffix}/package.json`;
|
|
257
|
+
const response = await fetch(url);
|
|
258
|
+
if (!response.ok) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Failed to fetch package.json for ${packageName}: ${response.statusText}`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
return response.json();
|
|
264
|
+
}
|
|
265
|
+
async function loadLocalImage(name) {
|
|
266
|
+
if (typeof globalThis.require === "undefined" && typeof process === "undefined") {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
const { createRequire } = await import("module");
|
|
271
|
+
const { readFile } = await import("fs/promises");
|
|
272
|
+
const { dirname: dirname3, join: join2 } = await import("path");
|
|
273
|
+
const require2 = createRequire(importMetaUrl);
|
|
274
|
+
let packageJsonPath;
|
|
275
|
+
try {
|
|
276
|
+
packageJsonPath = require2.resolve(`${name}/package.json`);
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
const packageJsonContent = await readFile(packageJsonPath, "utf-8");
|
|
281
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
282
|
+
const config = safeParseImageConfig(packageJson.patchwork) || DEFAULT_IMAGE_CONFIG;
|
|
283
|
+
let setup;
|
|
284
|
+
let mount;
|
|
285
|
+
const packageDir = dirname3(packageJsonPath);
|
|
286
|
+
if (packageJson.main) {
|
|
287
|
+
try {
|
|
288
|
+
const mainPath = join2(packageDir, packageJson.main);
|
|
289
|
+
const imageModule = await import(
|
|
290
|
+
/* webpackIgnore: true */
|
|
291
|
+
/* @vite-ignore */
|
|
292
|
+
mainPath
|
|
293
|
+
);
|
|
294
|
+
if (typeof imageModule.setup === "function") {
|
|
295
|
+
setup = imageModule.setup;
|
|
296
|
+
}
|
|
297
|
+
if (typeof imageModule.mount === "function") {
|
|
298
|
+
mount = imageModule.mount;
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
name: packageJson.name,
|
|
305
|
+
version: packageJson.version,
|
|
306
|
+
config,
|
|
307
|
+
dependencies: packageJson.dependencies || {},
|
|
308
|
+
setup,
|
|
309
|
+
mount
|
|
310
|
+
};
|
|
311
|
+
} catch {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async function loadImage(spec) {
|
|
316
|
+
const { name, version } = parseImageSpec(spec);
|
|
317
|
+
const localImage = await loadLocalImage(name);
|
|
318
|
+
if (localImage) {
|
|
319
|
+
return localImage;
|
|
320
|
+
}
|
|
321
|
+
const packageJson = await fetchPackageJson(name, version);
|
|
322
|
+
const config = safeParseImageConfig(packageJson.patchwork) || DEFAULT_IMAGE_CONFIG;
|
|
323
|
+
let setup;
|
|
324
|
+
let mount;
|
|
325
|
+
let moduleUrl;
|
|
326
|
+
if (packageJson.main && typeof window !== "undefined") {
|
|
327
|
+
try {
|
|
328
|
+
const versionSuffix = version ? `@${version}` : "";
|
|
329
|
+
const mainEntry = packageJson.main.startsWith("./") ? packageJson.main.slice(2) : packageJson.main;
|
|
330
|
+
const importUrl = `${cdnBaseUrl}/${name}${versionSuffix}/${mainEntry}`;
|
|
331
|
+
moduleUrl = importUrl;
|
|
332
|
+
const imageModule = await import(
|
|
333
|
+
/* @vite-ignore */
|
|
334
|
+
importUrl
|
|
335
|
+
);
|
|
336
|
+
if (typeof imageModule.setup === "function") {
|
|
337
|
+
setup = imageModule.setup;
|
|
338
|
+
}
|
|
339
|
+
if (typeof imageModule.mount === "function") {
|
|
340
|
+
mount = imageModule.mount;
|
|
341
|
+
}
|
|
342
|
+
} catch (err) {
|
|
343
|
+
console.error("[patchwork-compiler] Failed to load image module:", err);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
name: packageJson.name,
|
|
348
|
+
version: packageJson.version,
|
|
349
|
+
moduleUrl,
|
|
350
|
+
config,
|
|
351
|
+
dependencies: packageJson.dependencies || {},
|
|
352
|
+
setup,
|
|
353
|
+
mount
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/images/registry.ts
|
|
358
|
+
var ImageRegistry = class {
|
|
359
|
+
images = /* @__PURE__ */ new Map();
|
|
360
|
+
loading = /* @__PURE__ */ new Map();
|
|
361
|
+
/**
|
|
362
|
+
* Get a loaded image by spec
|
|
363
|
+
*/
|
|
364
|
+
get(spec) {
|
|
365
|
+
const { name } = parseImageSpec(spec);
|
|
366
|
+
return this.images.get(name);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Check if an image is loaded
|
|
370
|
+
*/
|
|
371
|
+
has(spec) {
|
|
372
|
+
const { name } = parseImageSpec(spec);
|
|
373
|
+
return this.images.has(name);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Load an image (or return cached)
|
|
377
|
+
*/
|
|
378
|
+
async load(spec) {
|
|
379
|
+
const { name } = parseImageSpec(spec);
|
|
380
|
+
const cached = this.images.get(name);
|
|
381
|
+
if (cached) {
|
|
382
|
+
return cached;
|
|
383
|
+
}
|
|
384
|
+
const inProgress = this.loading.get(name);
|
|
385
|
+
if (inProgress) {
|
|
386
|
+
return inProgress;
|
|
387
|
+
}
|
|
388
|
+
const loadPromise = loadImage(spec).then((image) => {
|
|
389
|
+
this.images.set(name, image);
|
|
390
|
+
this.loading.delete(name);
|
|
391
|
+
return image;
|
|
392
|
+
});
|
|
393
|
+
this.loading.set(name, loadPromise);
|
|
394
|
+
return loadPromise;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Preload an image
|
|
398
|
+
*/
|
|
399
|
+
async preload(spec) {
|
|
400
|
+
await this.load(spec);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Clear a specific image from cache
|
|
404
|
+
*/
|
|
405
|
+
clear(spec) {
|
|
406
|
+
const { name } = parseImageSpec(spec);
|
|
407
|
+
this.images.delete(name);
|
|
408
|
+
this.loading.delete(name);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Clear all cached images
|
|
412
|
+
*/
|
|
413
|
+
clearAll() {
|
|
414
|
+
this.images.clear();
|
|
415
|
+
this.loading.clear();
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Get all loaded image names
|
|
419
|
+
*/
|
|
420
|
+
getLoadedNames() {
|
|
421
|
+
return Array.from(this.images.keys());
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
var globalRegistry = null;
|
|
425
|
+
function getImageRegistry() {
|
|
426
|
+
if (!globalRegistry) {
|
|
427
|
+
globalRegistry = new ImageRegistry();
|
|
428
|
+
}
|
|
429
|
+
return globalRegistry;
|
|
430
|
+
}
|
|
431
|
+
function createImageRegistry() {
|
|
432
|
+
return new ImageRegistry();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/transforms/cdn.ts
|
|
436
|
+
var DEFAULT_CDN_BASE2 = "https://esm.sh";
|
|
437
|
+
var cdnBaseUrl2 = DEFAULT_CDN_BASE2;
|
|
438
|
+
function setCdnBaseUrl2(url) {
|
|
439
|
+
cdnBaseUrl2 = url;
|
|
440
|
+
}
|
|
441
|
+
var EXTERNAL_PACKAGES = /* @__PURE__ */ new Set(["react", "react-dom", "ink"]);
|
|
442
|
+
var NODE_BUILTINS = /* @__PURE__ */ new Set([
|
|
443
|
+
"assert",
|
|
444
|
+
"buffer",
|
|
445
|
+
"child_process",
|
|
446
|
+
"cluster",
|
|
447
|
+
"crypto",
|
|
448
|
+
"dgram",
|
|
449
|
+
"dns",
|
|
450
|
+
"events",
|
|
451
|
+
"fs",
|
|
452
|
+
"http",
|
|
453
|
+
"http2",
|
|
454
|
+
"https",
|
|
455
|
+
"net",
|
|
456
|
+
"os",
|
|
457
|
+
"path",
|
|
458
|
+
"perf_hooks",
|
|
459
|
+
"process",
|
|
460
|
+
"querystring",
|
|
461
|
+
"readline",
|
|
462
|
+
"stream",
|
|
463
|
+
"string_decoder",
|
|
464
|
+
"timers",
|
|
465
|
+
"tls",
|
|
466
|
+
"tty",
|
|
467
|
+
"url",
|
|
468
|
+
"util",
|
|
469
|
+
"v8",
|
|
470
|
+
"vm",
|
|
471
|
+
"worker_threads",
|
|
472
|
+
"zlib"
|
|
473
|
+
]);
|
|
474
|
+
function toEsmShUrl(packageName, version, subpath, deps) {
|
|
475
|
+
let url = `${cdnBaseUrl2}/${packageName}`;
|
|
476
|
+
if (version) {
|
|
477
|
+
url += `@${version}`;
|
|
478
|
+
}
|
|
479
|
+
if (subpath) {
|
|
480
|
+
url += `/${subpath}`;
|
|
481
|
+
}
|
|
482
|
+
if (deps && Object.keys(deps).length > 0) {
|
|
483
|
+
const depsStr = Object.entries(deps).map(([name, ver]) => `${name}@${ver}`).join(",");
|
|
484
|
+
url += `?deps=${depsStr}`;
|
|
485
|
+
}
|
|
486
|
+
return url;
|
|
487
|
+
}
|
|
488
|
+
function isBareImport(path) {
|
|
489
|
+
if (path.startsWith(".") || path.startsWith("/") || path.startsWith("http://") || path.startsWith("https://")) {
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
function parseImportPath(importPath) {
|
|
495
|
+
if (importPath.startsWith("@")) {
|
|
496
|
+
const parts2 = importPath.split("/");
|
|
497
|
+
if (parts2.length >= 2) {
|
|
498
|
+
const packageName2 = `${parts2[0]}/${parts2[1]}`;
|
|
499
|
+
const subpath2 = parts2.slice(2).join("/");
|
|
500
|
+
return { packageName: packageName2, subpath: subpath2 || void 0 };
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
const parts = importPath.split("/");
|
|
504
|
+
const packageName = parts[0];
|
|
505
|
+
const subpath = parts.slice(1).join("/");
|
|
506
|
+
return { packageName, subpath: subpath || void 0 };
|
|
507
|
+
}
|
|
508
|
+
function matchAlias(importPath, aliases) {
|
|
509
|
+
for (const [pattern, target] of Object.entries(aliases)) {
|
|
510
|
+
if (pattern.endsWith("/*")) {
|
|
511
|
+
const prefix = pattern.slice(0, -2);
|
|
512
|
+
if (importPath === prefix || importPath.startsWith(prefix + "/")) {
|
|
513
|
+
return target;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (importPath === pattern) {
|
|
517
|
+
return target;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
function cdnTransformPlugin(options = {}) {
|
|
523
|
+
const {
|
|
524
|
+
packages = {},
|
|
525
|
+
external = [],
|
|
526
|
+
bundle = false,
|
|
527
|
+
globals = {},
|
|
528
|
+
deps = {},
|
|
529
|
+
aliases = {}
|
|
530
|
+
} = options;
|
|
531
|
+
const externalSet = /* @__PURE__ */ new Set([...EXTERNAL_PACKAGES, ...external]);
|
|
532
|
+
const globalsSet = new Set(Object.keys(globals));
|
|
533
|
+
return {
|
|
534
|
+
name: "cdn-transform",
|
|
535
|
+
setup(build2) {
|
|
536
|
+
build2.onResolve({ filter: /.*/ }, (args) => {
|
|
537
|
+
const aliasTarget = matchAlias(args.path, aliases);
|
|
538
|
+
if (aliasTarget) {
|
|
539
|
+
const { packageName, subpath } = parseImportPath(aliasTarget);
|
|
540
|
+
if (globalsSet.has(packageName)) {
|
|
541
|
+
return {
|
|
542
|
+
path: aliasTarget,
|
|
543
|
+
namespace: "global-inject"
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
const version = packages[packageName];
|
|
547
|
+
let url = toEsmShUrl(
|
|
548
|
+
packageName,
|
|
549
|
+
version,
|
|
550
|
+
subpath,
|
|
551
|
+
Object.keys(deps).length > 0 ? deps : void 0
|
|
552
|
+
);
|
|
553
|
+
if (bundle) {
|
|
554
|
+
url += url.includes("?") ? "&bundle" : "?bundle";
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
path: url,
|
|
558
|
+
external: true
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
return null;
|
|
562
|
+
});
|
|
563
|
+
build2.onResolve({ filter: /.*/ }, (args) => {
|
|
564
|
+
if (!isBareImport(args.path)) {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
const { packageName } = parseImportPath(args.path);
|
|
568
|
+
if (globalsSet.has(packageName)) {
|
|
569
|
+
return {
|
|
570
|
+
path: args.path,
|
|
571
|
+
namespace: "global-inject"
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
return null;
|
|
575
|
+
});
|
|
576
|
+
build2.onLoad({ filter: /.*/, namespace: "global-inject" }, (args) => {
|
|
577
|
+
const { packageName, subpath } = parseImportPath(args.path);
|
|
578
|
+
const globalName = globals[packageName];
|
|
579
|
+
if (!globalName) return null;
|
|
580
|
+
if (subpath) {
|
|
581
|
+
return {
|
|
582
|
+
contents: `export * from '${packageName}'; export { default } from '${packageName}';`,
|
|
583
|
+
loader: "js"
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
const contents = `
|
|
587
|
+
const mod = window.${globalName};
|
|
588
|
+
export default mod;
|
|
589
|
+
// Re-export all properties as named exports
|
|
590
|
+
const { ${getCommonExports(packageName).join(", ")} } = mod;
|
|
591
|
+
export { ${getCommonExports(packageName).join(", ")} };
|
|
592
|
+
`;
|
|
593
|
+
return {
|
|
594
|
+
contents,
|
|
595
|
+
loader: "js"
|
|
596
|
+
};
|
|
597
|
+
});
|
|
598
|
+
build2.onResolve({ filter: /.*/ }, (args) => {
|
|
599
|
+
if (!isBareImport(args.path)) {
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
if (NODE_BUILTINS.has(args.path)) {
|
|
603
|
+
return { external: true };
|
|
604
|
+
}
|
|
605
|
+
const { packageName, subpath } = parseImportPath(args.path);
|
|
606
|
+
if (globalsSet.has(packageName)) {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
if (externalSet.has(packageName)) {
|
|
610
|
+
return { external: true };
|
|
611
|
+
}
|
|
612
|
+
const version = packages[packageName];
|
|
613
|
+
let url = toEsmShUrl(
|
|
614
|
+
packageName,
|
|
615
|
+
version,
|
|
616
|
+
subpath,
|
|
617
|
+
Object.keys(deps).length > 0 ? deps : void 0
|
|
618
|
+
);
|
|
619
|
+
if (bundle) {
|
|
620
|
+
url += url.includes("?") ? "&bundle" : "?bundle";
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
path: url,
|
|
624
|
+
external: true
|
|
625
|
+
};
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function getCommonExports(packageName) {
|
|
631
|
+
const exports2 = {
|
|
632
|
+
react: [
|
|
633
|
+
"useState",
|
|
634
|
+
"useEffect",
|
|
635
|
+
"useCallback",
|
|
636
|
+
"useMemo",
|
|
637
|
+
"useRef",
|
|
638
|
+
"useContext",
|
|
639
|
+
"useReducer",
|
|
640
|
+
"useLayoutEffect",
|
|
641
|
+
"useId",
|
|
642
|
+
"createContext",
|
|
643
|
+
"createElement",
|
|
644
|
+
"cloneElement",
|
|
645
|
+
"createRef",
|
|
646
|
+
"forwardRef",
|
|
647
|
+
"lazy",
|
|
648
|
+
"memo",
|
|
649
|
+
"Fragment",
|
|
650
|
+
"Suspense",
|
|
651
|
+
"StrictMode",
|
|
652
|
+
"Component",
|
|
653
|
+
"PureComponent",
|
|
654
|
+
"Children",
|
|
655
|
+
"isValidElement"
|
|
656
|
+
],
|
|
657
|
+
"react-dom": [
|
|
658
|
+
"createPortal",
|
|
659
|
+
"flushSync",
|
|
660
|
+
"render",
|
|
661
|
+
"hydrate",
|
|
662
|
+
"unmountComponentAtNode"
|
|
663
|
+
]
|
|
664
|
+
};
|
|
665
|
+
return exports2[packageName] || [];
|
|
666
|
+
}
|
|
667
|
+
function generateImportMap(packages) {
|
|
668
|
+
const imports = {};
|
|
669
|
+
for (const [name, version] of Object.entries(packages)) {
|
|
670
|
+
imports[name] = toEsmShUrl(name, version);
|
|
671
|
+
}
|
|
672
|
+
return imports;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// src/transforms/vfs.ts
|
|
676
|
+
function dirname(path) {
|
|
677
|
+
const idx = path.lastIndexOf("/");
|
|
678
|
+
return idx === -1 ? "." : path.slice(0, idx) || ".";
|
|
679
|
+
}
|
|
680
|
+
function normalizePath(path) {
|
|
681
|
+
const parts = [];
|
|
682
|
+
for (const segment of path.split("/")) {
|
|
683
|
+
if (segment === "..") parts.pop();
|
|
684
|
+
else if (segment && segment !== ".") parts.push(segment);
|
|
685
|
+
}
|
|
686
|
+
return parts.join("/");
|
|
687
|
+
}
|
|
688
|
+
function inferLoader(path, language) {
|
|
689
|
+
if (language) {
|
|
690
|
+
switch (language) {
|
|
691
|
+
case "typescript":
|
|
692
|
+
case "ts":
|
|
693
|
+
return "ts";
|
|
694
|
+
case "tsx":
|
|
695
|
+
return "tsx";
|
|
696
|
+
case "javascript":
|
|
697
|
+
case "js":
|
|
698
|
+
return "js";
|
|
699
|
+
case "jsx":
|
|
700
|
+
return "jsx";
|
|
701
|
+
case "json":
|
|
702
|
+
return "json";
|
|
703
|
+
case "css":
|
|
704
|
+
return "css";
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
const ext = path.split(".").pop();
|
|
708
|
+
switch (ext) {
|
|
709
|
+
case "ts":
|
|
710
|
+
return "ts";
|
|
711
|
+
case "tsx":
|
|
712
|
+
return "tsx";
|
|
713
|
+
case "js":
|
|
714
|
+
return "js";
|
|
715
|
+
case "jsx":
|
|
716
|
+
return "jsx";
|
|
717
|
+
case "json":
|
|
718
|
+
return "json";
|
|
719
|
+
case "css":
|
|
720
|
+
return "css";
|
|
721
|
+
default:
|
|
722
|
+
return "tsx";
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function normalizeVFSPath(path) {
|
|
726
|
+
if (path.startsWith("@/")) {
|
|
727
|
+
return path.slice(2);
|
|
728
|
+
}
|
|
729
|
+
return path;
|
|
730
|
+
}
|
|
731
|
+
function resolveRelativePath(importer, target) {
|
|
732
|
+
const importerDir = dirname(normalizeVFSPath(importer));
|
|
733
|
+
const combined = importerDir === "." ? target : `${importerDir}/${target}`;
|
|
734
|
+
return normalizePath(combined);
|
|
735
|
+
}
|
|
736
|
+
function matchAlias2(importPath, aliases) {
|
|
737
|
+
if (!aliases) return null;
|
|
738
|
+
for (const [pattern, target] of Object.entries(aliases)) {
|
|
739
|
+
if (pattern.endsWith("/*")) {
|
|
740
|
+
const prefix = pattern.slice(0, -2);
|
|
741
|
+
if (importPath === prefix || importPath.startsWith(prefix + "/")) {
|
|
742
|
+
return target;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if (importPath === pattern) {
|
|
746
|
+
return target;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return null;
|
|
750
|
+
}
|
|
751
|
+
function findFile(project, path) {
|
|
752
|
+
if (project.files.has(path)) return path;
|
|
753
|
+
const extensions = [".tsx", ".ts", ".jsx", ".js", ".json"];
|
|
754
|
+
for (const ext of extensions) {
|
|
755
|
+
if (project.files.has(path + ext)) return path + ext;
|
|
756
|
+
}
|
|
757
|
+
for (const ext of extensions) {
|
|
758
|
+
const indexPath = `${path}/index${ext}`;
|
|
759
|
+
if (project.files.has(indexPath)) return indexPath;
|
|
760
|
+
}
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
function vfsPlugin(project, options = {}) {
|
|
764
|
+
return {
|
|
765
|
+
name: "patchwork-vfs",
|
|
766
|
+
setup(build2) {
|
|
767
|
+
build2.onResolve({ filter: /^@\// }, (args) => {
|
|
768
|
+
const aliased = matchAlias2(args.path, options.aliases);
|
|
769
|
+
if (aliased) return null;
|
|
770
|
+
return { path: args.path, namespace: "vfs" };
|
|
771
|
+
});
|
|
772
|
+
build2.onResolve({ filter: /^\./ }, (args) => {
|
|
773
|
+
if (args.namespace !== "vfs") return null;
|
|
774
|
+
const resolved = resolveRelativePath(args.importer, args.path);
|
|
775
|
+
return { path: resolved, namespace: "vfs" };
|
|
776
|
+
});
|
|
777
|
+
build2.onLoad({ filter: /.*/, namespace: "vfs" }, (args) => {
|
|
778
|
+
const normalPath = normalizeVFSPath(args.path);
|
|
779
|
+
const filePath = findFile(project, normalPath);
|
|
780
|
+
if (!filePath) {
|
|
781
|
+
throw new Error(`File not found in VFS: ${args.path}`);
|
|
782
|
+
}
|
|
783
|
+
const file = project.files.get(filePath);
|
|
784
|
+
return {
|
|
785
|
+
contents: file.content,
|
|
786
|
+
loader: inferLoader(filePath, file.language)
|
|
787
|
+
};
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/mount/bridge.ts
|
|
794
|
+
function generateMessageId() {
|
|
795
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
796
|
+
}
|
|
797
|
+
function createHttpServiceProxy(proxyUrl) {
|
|
798
|
+
return {
|
|
799
|
+
async call(namespace, procedure, args) {
|
|
800
|
+
const url = `${proxyUrl}/${namespace}/${procedure}`;
|
|
801
|
+
const response = await fetch(url, {
|
|
802
|
+
method: "POST",
|
|
803
|
+
headers: { "Content-Type": "application/json" },
|
|
804
|
+
body: JSON.stringify({ args: args[0] ?? {} })
|
|
805
|
+
});
|
|
806
|
+
if (!response.ok) {
|
|
807
|
+
throw new Error(
|
|
808
|
+
`Service call failed: ${response.status} ${response.statusText}`
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
const result = await response.json();
|
|
812
|
+
return result;
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
function createFieldAccessProxy(namespace, handler) {
|
|
817
|
+
function createNestedProxy(path) {
|
|
818
|
+
const fn = (...args) => handler(namespace, path, ...args);
|
|
819
|
+
return new Proxy(fn, {
|
|
820
|
+
get(_, nestedName) {
|
|
821
|
+
if (typeof nestedName === "symbol") return void 0;
|
|
822
|
+
const newPath = path ? `${path}.${nestedName}` : nestedName;
|
|
823
|
+
return createNestedProxy(newPath);
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
return new Proxy(
|
|
828
|
+
{},
|
|
829
|
+
{
|
|
830
|
+
get(_, fieldName) {
|
|
831
|
+
if (typeof fieldName === "symbol") return void 0;
|
|
832
|
+
return createNestedProxy(fieldName);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
function generateNamespaceGlobals(services, proxy) {
|
|
838
|
+
const namespaces = {};
|
|
839
|
+
const uniqueNamespaces = extractNamespaces(services);
|
|
840
|
+
for (const namespace of uniqueNamespaces) {
|
|
841
|
+
namespaces[namespace] = createFieldAccessProxy(
|
|
842
|
+
namespace,
|
|
843
|
+
(ns, method, ...args) => proxy.call(ns, method, args)
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
return namespaces;
|
|
847
|
+
}
|
|
848
|
+
function injectNamespaceGlobals(target, namespaces) {
|
|
849
|
+
for (const [name, value] of Object.entries(namespaces)) {
|
|
850
|
+
target[name] = value;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
function removeNamespaceGlobals(target, namespaceNames) {
|
|
854
|
+
for (const name of namespaceNames) {
|
|
855
|
+
delete target[name];
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
function extractNamespaces(services) {
|
|
859
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
860
|
+
for (const service of services) {
|
|
861
|
+
const parts = service.split(".");
|
|
862
|
+
if (parts[0]) {
|
|
863
|
+
namespaces.add(parts[0]);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return Array.from(namespaces);
|
|
867
|
+
}
|
|
868
|
+
var ParentBridge = class {
|
|
869
|
+
proxy;
|
|
870
|
+
pendingCalls = /* @__PURE__ */ new Map();
|
|
871
|
+
iframes = /* @__PURE__ */ new Set();
|
|
872
|
+
messageHandler;
|
|
873
|
+
constructor(proxy) {
|
|
874
|
+
this.proxy = proxy;
|
|
875
|
+
this.messageHandler = this.handleMessage.bind(this);
|
|
876
|
+
if (typeof window !== "undefined") {
|
|
877
|
+
window.addEventListener("message", this.messageHandler);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Register an iframe to receive messages from
|
|
882
|
+
*/
|
|
883
|
+
registerIframe(iframe) {
|
|
884
|
+
this.iframes.add(iframe);
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Unregister an iframe
|
|
888
|
+
*/
|
|
889
|
+
unregisterIframe(iframe) {
|
|
890
|
+
this.iframes.delete(iframe);
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Handle incoming messages from iframes
|
|
894
|
+
*/
|
|
895
|
+
async handleMessage(event) {
|
|
896
|
+
const sourceIframe = Array.from(this.iframes).find(
|
|
897
|
+
(iframe) => iframe.contentWindow === event.source
|
|
898
|
+
);
|
|
899
|
+
if (!sourceIframe) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
const message = event.data;
|
|
903
|
+
if (!message || typeof message !== "object") return;
|
|
904
|
+
if (message.type === "service-call") {
|
|
905
|
+
const payload = message.payload;
|
|
906
|
+
try {
|
|
907
|
+
const result = await this.proxy.call(
|
|
908
|
+
payload.namespace,
|
|
909
|
+
payload.procedure,
|
|
910
|
+
payload.args
|
|
911
|
+
);
|
|
912
|
+
const response = {
|
|
913
|
+
type: "service-result",
|
|
914
|
+
id: message.id,
|
|
915
|
+
payload: { result }
|
|
916
|
+
};
|
|
917
|
+
sourceIframe.contentWindow?.postMessage(response, "*");
|
|
918
|
+
} catch (error) {
|
|
919
|
+
const response = {
|
|
920
|
+
type: "service-result",
|
|
921
|
+
id: message.id,
|
|
922
|
+
payload: {
|
|
923
|
+
error: error instanceof Error ? error.message : String(error)
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
sourceIframe.contentWindow?.postMessage(response, "*");
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Dispose the bridge
|
|
932
|
+
*/
|
|
933
|
+
dispose() {
|
|
934
|
+
if (typeof window !== "undefined") {
|
|
935
|
+
window.removeEventListener("message", this.messageHandler);
|
|
936
|
+
}
|
|
937
|
+
this.iframes.clear();
|
|
938
|
+
this.pendingCalls.clear();
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
function createIframeServiceProxy() {
|
|
942
|
+
const pendingCalls = /* @__PURE__ */ new Map();
|
|
943
|
+
if (typeof window !== "undefined") {
|
|
944
|
+
window.addEventListener("message", (event) => {
|
|
945
|
+
const message = event.data;
|
|
946
|
+
if (!message || typeof message !== "object") return;
|
|
947
|
+
if (message.type === "service-result") {
|
|
948
|
+
const pending = pendingCalls.get(message.id);
|
|
949
|
+
if (pending) {
|
|
950
|
+
pendingCalls.delete(message.id);
|
|
951
|
+
const payload = message.payload;
|
|
952
|
+
if (payload.error) {
|
|
953
|
+
pending.reject(new Error(payload.error));
|
|
954
|
+
} else {
|
|
955
|
+
pending.resolve(payload.result);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
return {
|
|
962
|
+
call(namespace, procedure, args) {
|
|
963
|
+
return new Promise((resolve, reject) => {
|
|
964
|
+
const id = generateMessageId();
|
|
965
|
+
pendingCalls.set(id, { resolve, reject });
|
|
966
|
+
const message = {
|
|
967
|
+
type: "service-call",
|
|
968
|
+
id,
|
|
969
|
+
payload: { namespace, procedure, args }
|
|
970
|
+
};
|
|
971
|
+
window.parent.postMessage(message, "*");
|
|
972
|
+
setTimeout(() => {
|
|
973
|
+
if (pendingCalls.has(id)) {
|
|
974
|
+
pendingCalls.delete(id);
|
|
975
|
+
reject(
|
|
976
|
+
new Error(`Service call timeout: ${namespace}.${procedure}`)
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
}, 3e4);
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
function generateIframeBridgeScript(services) {
|
|
985
|
+
const uniqueNamespaces = extractNamespaces(services);
|
|
986
|
+
const namespaceAssignments = uniqueNamespaces.map((ns) => `window.${ns} = createNamespaceProxy('${ns}');`).join("\n ");
|
|
987
|
+
return `
|
|
988
|
+
(function() {
|
|
989
|
+
const pendingCalls = new Map();
|
|
990
|
+
|
|
991
|
+
window.addEventListener('message', function(event) {
|
|
992
|
+
const message = event.data;
|
|
993
|
+
if (!message || typeof message !== 'object') return;
|
|
994
|
+
|
|
995
|
+
if (message.type === 'service-result') {
|
|
996
|
+
const pending = pendingCalls.get(message.id);
|
|
997
|
+
if (pending) {
|
|
998
|
+
pendingCalls.delete(message.id);
|
|
999
|
+
if (message.payload.error) {
|
|
1000
|
+
pending.reject(new Error(message.payload.error));
|
|
1001
|
+
} else {
|
|
1002
|
+
pending.resolve(message.payload.result);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
function proxyCall(namespace, procedure, args) {
|
|
1009
|
+
return new Promise(function(resolve, reject) {
|
|
1010
|
+
const id = Date.now() + '-' + Math.random().toString(36).slice(2, 11);
|
|
1011
|
+
pendingCalls.set(id, { resolve: resolve, reject: reject });
|
|
1012
|
+
|
|
1013
|
+
window.parent.postMessage({
|
|
1014
|
+
type: 'service-call',
|
|
1015
|
+
id: id,
|
|
1016
|
+
payload: { namespace: namespace, procedure: procedure, args: args }
|
|
1017
|
+
}, '*');
|
|
1018
|
+
|
|
1019
|
+
setTimeout(function() {
|
|
1020
|
+
if (pendingCalls.has(id)) {
|
|
1021
|
+
pendingCalls.delete(id);
|
|
1022
|
+
reject(new Error('Service call timeout: ' + namespace + '.' + procedure));
|
|
1023
|
+
}
|
|
1024
|
+
}, 30000);
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// Create a dynamic proxy for a namespace that supports arbitrary nested method calls
|
|
1029
|
+
function createNamespaceProxy(namespace) {
|
|
1030
|
+
function createNestedProxy(path) {
|
|
1031
|
+
var fn = function() {
|
|
1032
|
+
return proxyCall(namespace, path, Array.prototype.slice.call(arguments));
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
return new Proxy(fn, {
|
|
1036
|
+
get: function(_, nestedName) {
|
|
1037
|
+
if (typeof nestedName === 'symbol') return undefined;
|
|
1038
|
+
var newPath = path ? path + '.' + nestedName : nestedName;
|
|
1039
|
+
return createNestedProxy(newPath);
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
return new Proxy({}, {
|
|
1045
|
+
get: function(_, fieldName) {
|
|
1046
|
+
if (typeof fieldName === 'symbol') return undefined;
|
|
1047
|
+
return createNestedProxy(fieldName);
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
${namespaceAssignments}
|
|
1053
|
+
})();
|
|
1054
|
+
`;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// src/mount/embedded.ts
|
|
1058
|
+
var mountCounter = 0;
|
|
1059
|
+
var importMapInjected = false;
|
|
1060
|
+
function injectImportMap(globals, preloadUrls, deps) {
|
|
1061
|
+
if (importMapInjected) return;
|
|
1062
|
+
const existingMap = document.querySelector('script[type="importmap"]');
|
|
1063
|
+
if (existingMap) {
|
|
1064
|
+
importMapInjected = true;
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
const imports = {};
|
|
1068
|
+
const packageNames = Object.keys(globals);
|
|
1069
|
+
packageNames.forEach((pkgName, index) => {
|
|
1070
|
+
if (preloadUrls[index]) {
|
|
1071
|
+
imports[pkgName] = preloadUrls[index];
|
|
1072
|
+
} else if (deps?.[pkgName]) {
|
|
1073
|
+
imports[pkgName] = `https://esm.sh/${pkgName}@${deps[pkgName]}`;
|
|
1074
|
+
} else {
|
|
1075
|
+
imports[pkgName] = `https://esm.sh/${pkgName}`;
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
if (imports["react-dom"]) {
|
|
1079
|
+
imports["react-dom/client"] = imports["react-dom"];
|
|
1080
|
+
}
|
|
1081
|
+
const script = document.createElement("script");
|
|
1082
|
+
script.type = "importmap";
|
|
1083
|
+
script.textContent = JSON.stringify({ imports }, null, 2);
|
|
1084
|
+
document.head.insertBefore(script, document.head.firstChild);
|
|
1085
|
+
importMapInjected = true;
|
|
1086
|
+
}
|
|
1087
|
+
function generateMountId() {
|
|
1088
|
+
return `pw-mount-${Date.now()}-${++mountCounter}`;
|
|
1089
|
+
}
|
|
1090
|
+
function pickCreateElement(globals) {
|
|
1091
|
+
for (const obj of globals) {
|
|
1092
|
+
const ce = obj?.createElement;
|
|
1093
|
+
if (typeof ce === "function") return ce;
|
|
1094
|
+
const def = obj?.default;
|
|
1095
|
+
if (def && typeof def.createElement === "function") {
|
|
1096
|
+
return def.createElement;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
function pickRenderer(globals) {
|
|
1102
|
+
for (const obj of globals) {
|
|
1103
|
+
if (obj && typeof obj.createRoot === "function") {
|
|
1104
|
+
return { kind: "root", createRoot: obj.createRoot };
|
|
1105
|
+
}
|
|
1106
|
+
if (obj && typeof obj.render === "function") {
|
|
1107
|
+
return { kind: "render", render: obj.render };
|
|
1108
|
+
}
|
|
1109
|
+
const def = obj?.default;
|
|
1110
|
+
if (def && typeof def.createRoot === "function") {
|
|
1111
|
+
return { kind: "root", createRoot: def.createRoot };
|
|
1112
|
+
}
|
|
1113
|
+
if (def && typeof def.render === "function") {
|
|
1114
|
+
return { kind: "render", render: def.render };
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return null;
|
|
1118
|
+
}
|
|
1119
|
+
async function mountEmbedded(widget, options, image, proxy) {
|
|
1120
|
+
const { target, inputs = {} } = options;
|
|
1121
|
+
const mountId = generateMountId();
|
|
1122
|
+
const container = document.createElement("div");
|
|
1123
|
+
container.id = mountId;
|
|
1124
|
+
container.className = "patchwork-widget patchwork-embedded";
|
|
1125
|
+
target.appendChild(container);
|
|
1126
|
+
if (image?.setup) {
|
|
1127
|
+
await image.setup(container);
|
|
1128
|
+
}
|
|
1129
|
+
if (image?.css) {
|
|
1130
|
+
const style = document.createElement("style");
|
|
1131
|
+
style.id = `${mountId}-style`;
|
|
1132
|
+
style.textContent = image.css;
|
|
1133
|
+
document.head.appendChild(style);
|
|
1134
|
+
}
|
|
1135
|
+
const services = widget.manifest.services || [];
|
|
1136
|
+
const namespaceNames = extractNamespaces(services);
|
|
1137
|
+
const namespaces = generateNamespaceGlobals(services, proxy);
|
|
1138
|
+
injectNamespaceGlobals(window, namespaces);
|
|
1139
|
+
const frameworkConfig = image?.config?.framework || {};
|
|
1140
|
+
const preloadUrls = frameworkConfig.preload || [];
|
|
1141
|
+
const globalMapping = frameworkConfig.globals || {};
|
|
1142
|
+
const deps = frameworkConfig.deps || {};
|
|
1143
|
+
injectImportMap(globalMapping, preloadUrls, deps);
|
|
1144
|
+
const preloadedModules = await Promise.all(
|
|
1145
|
+
preloadUrls.map(
|
|
1146
|
+
(url) => import(
|
|
1147
|
+
/* webpackIgnore: true */
|
|
1148
|
+
/* @vite-ignore */
|
|
1149
|
+
url
|
|
1150
|
+
)
|
|
1151
|
+
)
|
|
1152
|
+
);
|
|
1153
|
+
const win = window;
|
|
1154
|
+
const globalNames = Object.values(globalMapping);
|
|
1155
|
+
preloadedModules.forEach((mod, index) => {
|
|
1156
|
+
if (globalNames[index]) {
|
|
1157
|
+
const name = globalNames[index];
|
|
1158
|
+
win[name] = mod;
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
1161
|
+
const blob = new Blob([widget.code], { type: "application/javascript" });
|
|
1162
|
+
const scriptUrl = URL.createObjectURL(blob);
|
|
1163
|
+
let moduleCleanup;
|
|
1164
|
+
const globalObjects = globalNames.map((n) => win[n]).filter(Boolean);
|
|
1165
|
+
try {
|
|
1166
|
+
const module2 = await import(
|
|
1167
|
+
/* webpackIgnore: true */
|
|
1168
|
+
scriptUrl
|
|
1169
|
+
);
|
|
1170
|
+
if (image?.mount) {
|
|
1171
|
+
const result = await image.mount(module2, container, inputs);
|
|
1172
|
+
if (typeof result === "function") {
|
|
1173
|
+
moduleCleanup = result;
|
|
1174
|
+
}
|
|
1175
|
+
} else if (typeof module2.mount === "function") {
|
|
1176
|
+
const result = await module2.mount(container, inputs);
|
|
1177
|
+
if (typeof result === "function") {
|
|
1178
|
+
moduleCleanup = result;
|
|
1179
|
+
}
|
|
1180
|
+
} else if (typeof module2.render === "function") {
|
|
1181
|
+
const result = await module2.render(container, inputs);
|
|
1182
|
+
if (typeof result === "function") {
|
|
1183
|
+
moduleCleanup = result;
|
|
1184
|
+
}
|
|
1185
|
+
} else if (typeof module2.default === "function") {
|
|
1186
|
+
const Component = module2.default;
|
|
1187
|
+
const createElement = pickCreateElement(globalObjects);
|
|
1188
|
+
const renderer = pickRenderer(globalObjects);
|
|
1189
|
+
if (createElement && renderer?.kind === "root") {
|
|
1190
|
+
const root = renderer.createRoot(container);
|
|
1191
|
+
root.render(createElement(Component, inputs));
|
|
1192
|
+
if (typeof root.unmount === "function") {
|
|
1193
|
+
moduleCleanup = () => root.unmount();
|
|
1194
|
+
}
|
|
1195
|
+
} else if (createElement && renderer?.kind === "render") {
|
|
1196
|
+
renderer.render(createElement(Component, inputs), container);
|
|
1197
|
+
} else {
|
|
1198
|
+
const result = Component(inputs);
|
|
1199
|
+
if (result instanceof HTMLElement) {
|
|
1200
|
+
container.appendChild(result);
|
|
1201
|
+
} else if (typeof result === "string") {
|
|
1202
|
+
container.innerHTML = result;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
} finally {
|
|
1207
|
+
URL.revokeObjectURL(scriptUrl);
|
|
1208
|
+
}
|
|
1209
|
+
const unmount = () => {
|
|
1210
|
+
if (moduleCleanup) {
|
|
1211
|
+
moduleCleanup();
|
|
1212
|
+
}
|
|
1213
|
+
removeNamespaceGlobals(window, namespaceNames);
|
|
1214
|
+
const style = document.getElementById(`${mountId}-style`);
|
|
1215
|
+
if (style) {
|
|
1216
|
+
style.remove();
|
|
1217
|
+
}
|
|
1218
|
+
container.remove();
|
|
1219
|
+
};
|
|
1220
|
+
return {
|
|
1221
|
+
id: mountId,
|
|
1222
|
+
widget,
|
|
1223
|
+
mode: "embedded",
|
|
1224
|
+
target,
|
|
1225
|
+
inputs,
|
|
1226
|
+
unmount
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
async function reloadEmbedded(mounted, widget, image, proxy) {
|
|
1230
|
+
mounted.unmount();
|
|
1231
|
+
return mountEmbedded(
|
|
1232
|
+
widget,
|
|
1233
|
+
{ target: mounted.target, mode: "embedded", inputs: mounted.inputs },
|
|
1234
|
+
image,
|
|
1235
|
+
proxy
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// src/mount/iframe.ts
|
|
1240
|
+
var mountCounter2 = 0;
|
|
1241
|
+
var sharedBridge = null;
|
|
1242
|
+
function getParentBridge(proxy) {
|
|
1243
|
+
if (!sharedBridge) {
|
|
1244
|
+
sharedBridge = new ParentBridge(proxy);
|
|
1245
|
+
}
|
|
1246
|
+
return sharedBridge;
|
|
1247
|
+
}
|
|
1248
|
+
function generateMountId2() {
|
|
1249
|
+
return `pw-iframe-${Date.now()}-${++mountCounter2}`;
|
|
1250
|
+
}
|
|
1251
|
+
var DEFAULT_SANDBOX = ["allow-scripts"];
|
|
1252
|
+
var DEV_SANDBOX = ["allow-scripts", "allow-same-origin"];
|
|
1253
|
+
function generateIframeContent(image, inputs, services, baseUrl) {
|
|
1254
|
+
const bridgeScript = generateIframeBridgeScript(services);
|
|
1255
|
+
const packages = {
|
|
1256
|
+
...image?.dependencies || {}
|
|
1257
|
+
};
|
|
1258
|
+
const importMap = generateImportMap(packages);
|
|
1259
|
+
const css = image?.css || "";
|
|
1260
|
+
const frameworkConfig = image?.config?.framework || {};
|
|
1261
|
+
const preloadUrls = frameworkConfig.preload || [];
|
|
1262
|
+
const globals = frameworkConfig.globals || {};
|
|
1263
|
+
const globalNames = Object.values(globals);
|
|
1264
|
+
const imageModuleUrl = image?.moduleUrl || "";
|
|
1265
|
+
const mountScript = `
|
|
1266
|
+
// Run image setup inside the iframe (styling/runtime)
|
|
1267
|
+
const imageModuleUrl = ${JSON.stringify(imageModuleUrl)};
|
|
1268
|
+
|
|
1269
|
+
// Preload framework modules declared by the image (if any)
|
|
1270
|
+
const preloadUrls = ${JSON.stringify(preloadUrls)};
|
|
1271
|
+
const globalNames = ${JSON.stringify(globalNames)};
|
|
1272
|
+
for (let i = 0; i < preloadUrls.length; i++) {
|
|
1273
|
+
const url = preloadUrls[i];
|
|
1274
|
+
const name = globalNames[i];
|
|
1275
|
+
if (!url || !name) continue;
|
|
1276
|
+
try {
|
|
1277
|
+
const mod = await import(/* webpackIgnore: true */ url);
|
|
1278
|
+
window[name] = mod;
|
|
1279
|
+
} catch (e) {
|
|
1280
|
+
console.error('[patchwork-iframe] Failed to preload:', url, e);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
const root = document.getElementById('root');
|
|
1285
|
+
const inputs = window.__PATCHWORK_INPUTS__ || {};
|
|
1286
|
+
|
|
1287
|
+
if (imageModuleUrl && root) {
|
|
1288
|
+
try {
|
|
1289
|
+
const img = await import(/* webpackIgnore: true */ imageModuleUrl);
|
|
1290
|
+
if (typeof img?.setup === 'function') {
|
|
1291
|
+
await img.setup(root);
|
|
1292
|
+
}
|
|
1293
|
+
} catch (e) {
|
|
1294
|
+
console.error('[patchwork-iframe] Failed to run image setup:', e);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function pickCreateElement(globals) {
|
|
1299
|
+
for (const obj of globals) {
|
|
1300
|
+
if (obj && typeof obj.createElement === 'function') return obj.createElement.bind(obj);
|
|
1301
|
+
if (obj?.default && typeof obj.default.createElement === 'function') return obj.default.createElement.bind(obj.default);
|
|
1302
|
+
}
|
|
1303
|
+
return null;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
function pickRenderer(globals) {
|
|
1307
|
+
for (const obj of globals) {
|
|
1308
|
+
if (obj && typeof obj.createRoot === 'function') {
|
|
1309
|
+
return {
|
|
1310
|
+
kind: 'root',
|
|
1311
|
+
createRoot: obj.createRoot.bind(obj),
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
if (obj && typeof obj.render === 'function') {
|
|
1315
|
+
return {
|
|
1316
|
+
kind: 'render',
|
|
1317
|
+
render: obj.render.bind(obj),
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
if (obj?.default && typeof obj.default.createRoot === 'function') {
|
|
1321
|
+
return {
|
|
1322
|
+
kind: 'root',
|
|
1323
|
+
createRoot: obj.default.createRoot.bind(obj.default),
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
if (obj?.default && typeof obj.default.render === 'function') {
|
|
1327
|
+
return {
|
|
1328
|
+
kind: 'render',
|
|
1329
|
+
render: obj.default.render.bind(obj.default),
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
function getGlobalsFromConfig() {
|
|
1337
|
+
const names = ${JSON.stringify(globalNames)};
|
|
1338
|
+
return names.map((n) => window[n]).filter(Boolean);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
async function mountModule(mod) {
|
|
1342
|
+
if (!root) throw new Error('No #root element');
|
|
1343
|
+
|
|
1344
|
+
if (typeof mod?.mount === 'function') {
|
|
1345
|
+
const cleanup = await mod.mount(root, inputs);
|
|
1346
|
+
if (typeof cleanup === 'function') window.__PATCHWORK_CLEANUP__ = cleanup;
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
if (typeof mod?.render === 'function') {
|
|
1351
|
+
const cleanup = await mod.render(root, inputs);
|
|
1352
|
+
if (typeof cleanup === 'function') window.__PATCHWORK_CLEANUP__ = cleanup;
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
const Component = mod?.default;
|
|
1357
|
+
if (typeof Component !== 'function') {
|
|
1358
|
+
root.textContent = 'Widget did not export a default component.';
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
const globals = getGlobalsFromConfig();
|
|
1363
|
+
const createElement = pickCreateElement(globals);
|
|
1364
|
+
const renderer = pickRenderer(globals);
|
|
1365
|
+
|
|
1366
|
+
if (createElement && renderer?.kind === 'root') {
|
|
1367
|
+
const r = renderer.createRoot(root);
|
|
1368
|
+
r.render(createElement(Component, inputs));
|
|
1369
|
+
if (typeof r.unmount === 'function') window.__PATCHWORK_CLEANUP__ = () => r.unmount();
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
if (createElement && renderer?.kind === 'render') {
|
|
1374
|
+
renderer.render(createElement(Component, inputs), root);
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
const result = Component(inputs);
|
|
1379
|
+
if (result instanceof HTMLElement) {
|
|
1380
|
+
root.appendChild(result);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
if (typeof result === 'string') {
|
|
1384
|
+
root.innerHTML = result;
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
root.textContent = 'No framework renderer available for this widget.';
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// Wait for widget code via postMessage (more efficient than inline in srcdoc)
|
|
1392
|
+
// We convert relative URLs to absolute so they work inside blob URL context
|
|
1393
|
+
window.addEventListener('message', async function handleWidgetCode(event) {
|
|
1394
|
+
if (!event.data || event.data.type !== 'widget-code') return;
|
|
1395
|
+
window.removeEventListener('message', handleWidgetCode);
|
|
1396
|
+
|
|
1397
|
+
const widgetCode = event.data.code;
|
|
1398
|
+
const origin = event.data.origin || ''; // Parent sends the origin
|
|
1399
|
+
|
|
1400
|
+
// Convert relative URLs (starting with /) to absolute URLs
|
|
1401
|
+
// This is necessary because blob: URLs can't resolve relative imports
|
|
1402
|
+
// and srcdoc iframes have null origin
|
|
1403
|
+
const absoluteCode = widgetCode.replace(
|
|
1404
|
+
/from\\s*["'](\\/[^"']+)["']/g,
|
|
1405
|
+
(_, path) => 'from "' + origin + path + '"'
|
|
1406
|
+
).replace(
|
|
1407
|
+
/import\\s*["'](\\/[^"']+)["']/g,
|
|
1408
|
+
(_, path) => 'import "' + origin + path + '"'
|
|
1409
|
+
);
|
|
1410
|
+
|
|
1411
|
+
const blob = new Blob([absoluteCode], { type: 'application/javascript' });
|
|
1412
|
+
const url = URL.createObjectURL(blob);
|
|
1413
|
+
|
|
1414
|
+
try {
|
|
1415
|
+
const mod = await import(/* webpackIgnore: true */ url);
|
|
1416
|
+
await mountModule(mod);
|
|
1417
|
+
window.parent.postMessage({ type: 'widget-mounted' }, '*');
|
|
1418
|
+
} catch (e) {
|
|
1419
|
+
console.error('[patchwork-iframe] Failed to mount widget:', e);
|
|
1420
|
+
window.parent.postMessage({ type: 'widget-error', error: e.message }, '*');
|
|
1421
|
+
} finally {
|
|
1422
|
+
URL.revokeObjectURL(url);
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1426
|
+
// Signal ready to receive widget code
|
|
1427
|
+
window.parent.postMessage({ type: 'widget-ready' }, '*');
|
|
1428
|
+
|
|
1429
|
+
// Set up ResizeObserver to report body size changes to parent
|
|
1430
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
1431
|
+
for (const entry of entries) {
|
|
1432
|
+
const { width, height } = entry.contentRect;
|
|
1433
|
+
window.parent.postMessage({
|
|
1434
|
+
type: 'widget-resize',
|
|
1435
|
+
width: Math.ceil(width),
|
|
1436
|
+
height: Math.ceil(height)
|
|
1437
|
+
}, '*');
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
resizeObserver.observe(document.body);
|
|
1441
|
+
`;
|
|
1442
|
+
return `<!DOCTYPE html>
|
|
1443
|
+
<html>
|
|
1444
|
+
<head>
|
|
1445
|
+
<base href="${baseUrl}">
|
|
1446
|
+
<meta charset="UTF-8">
|
|
1447
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1448
|
+
<style>
|
|
1449
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1450
|
+
body { font-family: system-ui, -apple-system, sans-serif; }
|
|
1451
|
+
${css}
|
|
1452
|
+
</style>
|
|
1453
|
+
<script type="importmap">
|
|
1454
|
+
${JSON.stringify({ imports: importMap }, null, 2)}
|
|
1455
|
+
</script>
|
|
1456
|
+
</head>
|
|
1457
|
+
<body>
|
|
1458
|
+
<div id="root"></div>
|
|
1459
|
+
|
|
1460
|
+
<!-- Service Bridge -->
|
|
1461
|
+
<script>
|
|
1462
|
+
${bridgeScript}
|
|
1463
|
+
</script>
|
|
1464
|
+
|
|
1465
|
+
<!-- Widget Inputs -->
|
|
1466
|
+
<script>
|
|
1467
|
+
window.__PATCHWORK_INPUTS__ = ${JSON.stringify(inputs)};
|
|
1468
|
+
</script>
|
|
1469
|
+
|
|
1470
|
+
<script type="module">
|
|
1471
|
+
${mountScript}
|
|
1472
|
+
</script>
|
|
1473
|
+
</body>
|
|
1474
|
+
</html>`;
|
|
1475
|
+
}
|
|
1476
|
+
async function mountIframe(widget, options, image, proxy) {
|
|
1477
|
+
const { target, sandbox = DEFAULT_SANDBOX, inputs = {} } = options;
|
|
1478
|
+
const mountId = generateMountId2();
|
|
1479
|
+
const iframe = document.createElement("iframe");
|
|
1480
|
+
iframe.id = mountId;
|
|
1481
|
+
iframe.className = "patchwork-widget patchwork-iframe";
|
|
1482
|
+
iframe.style.cssText = "width: 100%; border: none; overflow: hidden;";
|
|
1483
|
+
iframe.sandbox.add(...sandbox);
|
|
1484
|
+
const bridge = getParentBridge(proxy);
|
|
1485
|
+
bridge.registerIframe(iframe);
|
|
1486
|
+
const services = widget.manifest.services || [];
|
|
1487
|
+
const baseUrl = typeof window !== "undefined" ? window.location.origin : "";
|
|
1488
|
+
const content = generateIframeContent(image, inputs, services, baseUrl);
|
|
1489
|
+
iframe.srcdoc = content;
|
|
1490
|
+
target.appendChild(iframe);
|
|
1491
|
+
const handleResize = (event) => {
|
|
1492
|
+
if (event.source !== iframe.contentWindow) return;
|
|
1493
|
+
if (event.data?.type === "widget-resize") {
|
|
1494
|
+
const { height } = event.data;
|
|
1495
|
+
if (typeof height === "number" && height > 0) {
|
|
1496
|
+
iframe.style.height = `${height}px`;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
window.addEventListener("message", handleResize);
|
|
1501
|
+
await new Promise((resolve, reject) => {
|
|
1502
|
+
const timeout = setTimeout(() => {
|
|
1503
|
+
cleanup();
|
|
1504
|
+
reject(new Error("Iframe mount timeout"));
|
|
1505
|
+
}, 3e4);
|
|
1506
|
+
const handleMessage = (event) => {
|
|
1507
|
+
if (event.source !== iframe.contentWindow) return;
|
|
1508
|
+
if (event.data?.type === "widget-ready") {
|
|
1509
|
+
iframe.contentWindow?.postMessage(
|
|
1510
|
+
{ type: "widget-code", code: widget.code, origin: baseUrl },
|
|
1511
|
+
"*"
|
|
1512
|
+
);
|
|
1513
|
+
} else if (event.data?.type === "widget-mounted") {
|
|
1514
|
+
cleanup();
|
|
1515
|
+
resolve();
|
|
1516
|
+
} else if (event.data?.type === "widget-error") {
|
|
1517
|
+
cleanup();
|
|
1518
|
+
reject(new Error(event.data.error || "Widget mount failed"));
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
const cleanup = () => {
|
|
1522
|
+
clearTimeout(timeout);
|
|
1523
|
+
window.removeEventListener("message", handleMessage);
|
|
1524
|
+
};
|
|
1525
|
+
window.addEventListener("message", handleMessage);
|
|
1526
|
+
});
|
|
1527
|
+
const unmount = () => {
|
|
1528
|
+
window.removeEventListener("message", handleResize);
|
|
1529
|
+
bridge.unregisterIframe(iframe);
|
|
1530
|
+
iframe.remove();
|
|
1531
|
+
};
|
|
1532
|
+
return {
|
|
1533
|
+
id: mountId,
|
|
1534
|
+
widget,
|
|
1535
|
+
mode: "iframe",
|
|
1536
|
+
target,
|
|
1537
|
+
iframe,
|
|
1538
|
+
inputs,
|
|
1539
|
+
sandbox,
|
|
1540
|
+
unmount
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
async function reloadIframe(mounted, widget, image, proxy) {
|
|
1544
|
+
mounted.unmount();
|
|
1545
|
+
return mountIframe(
|
|
1546
|
+
widget,
|
|
1547
|
+
{
|
|
1548
|
+
target: mounted.target,
|
|
1549
|
+
mode: "iframe",
|
|
1550
|
+
sandbox: mounted.sandbox,
|
|
1551
|
+
inputs: mounted.inputs
|
|
1552
|
+
},
|
|
1553
|
+
image,
|
|
1554
|
+
proxy
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
function disposeIframeBridge() {
|
|
1558
|
+
if (sharedBridge) {
|
|
1559
|
+
sharedBridge.dispose();
|
|
1560
|
+
sharedBridge = null;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// src/compiler.ts
|
|
1565
|
+
var esbuildInitialized = false;
|
|
1566
|
+
var esbuildInitPromise = null;
|
|
1567
|
+
async function initEsbuild() {
|
|
1568
|
+
if (esbuildInitialized) return;
|
|
1569
|
+
if (esbuildInitPromise) return esbuildInitPromise;
|
|
1570
|
+
esbuildInitPromise = (async () => {
|
|
1571
|
+
try {
|
|
1572
|
+
await esbuild.initialize({
|
|
1573
|
+
wasmURL: "https://unpkg.com/esbuild-wasm/esbuild.wasm"
|
|
1574
|
+
});
|
|
1575
|
+
esbuildInitialized = true;
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
if (error instanceof Error && error.message.includes("initialized")) {
|
|
1578
|
+
esbuildInitialized = true;
|
|
1579
|
+
} else {
|
|
1580
|
+
throw error;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
})();
|
|
1584
|
+
return esbuildInitPromise;
|
|
1585
|
+
}
|
|
1586
|
+
function hashContent(content) {
|
|
1587
|
+
const encoder = new TextEncoder();
|
|
1588
|
+
const data = encoder.encode(content);
|
|
1589
|
+
let hash = 0;
|
|
1590
|
+
for (let i = 0; i < data.length; i++) {
|
|
1591
|
+
hash = (hash << 5) - hash + (data[i] ?? 0) | 0;
|
|
1592
|
+
}
|
|
1593
|
+
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
1594
|
+
}
|
|
1595
|
+
async function createCompiler(options) {
|
|
1596
|
+
await initEsbuild();
|
|
1597
|
+
const { image: imageSpec, proxyUrl, cdnBaseUrl: cdnBaseUrl3, widgetCdnBaseUrl } = options;
|
|
1598
|
+
if (cdnBaseUrl3) {
|
|
1599
|
+
setCdnBaseUrl(cdnBaseUrl3);
|
|
1600
|
+
}
|
|
1601
|
+
if (widgetCdnBaseUrl) {
|
|
1602
|
+
setCdnBaseUrl2(widgetCdnBaseUrl);
|
|
1603
|
+
} else if (cdnBaseUrl3) {
|
|
1604
|
+
setCdnBaseUrl2(cdnBaseUrl3);
|
|
1605
|
+
}
|
|
1606
|
+
const registry = getImageRegistry();
|
|
1607
|
+
await registry.preload(imageSpec);
|
|
1608
|
+
const proxy = createHttpServiceProxy(proxyUrl);
|
|
1609
|
+
return new PatchworkCompiler(proxy, registry);
|
|
1610
|
+
}
|
|
1611
|
+
var PatchworkCompiler = class {
|
|
1612
|
+
proxy;
|
|
1613
|
+
registry;
|
|
1614
|
+
constructor(proxy, registry) {
|
|
1615
|
+
this.proxy = proxy;
|
|
1616
|
+
this.registry = registry;
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Pre-load an image package
|
|
1620
|
+
*/
|
|
1621
|
+
async preloadImage(spec) {
|
|
1622
|
+
await this.registry.preload(spec);
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* Check if an image is loaded
|
|
1626
|
+
*/
|
|
1627
|
+
isImageLoaded(spec) {
|
|
1628
|
+
return this.registry.has(spec);
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Compile widget source to ESM
|
|
1632
|
+
*/
|
|
1633
|
+
async compile(source, manifest, _options = {}) {
|
|
1634
|
+
const project = typeof source === "string" ? createSingleFileProject(source) : source;
|
|
1635
|
+
const entryExt = project.entry.split(".").pop();
|
|
1636
|
+
const loader = entryExt === "ts" || entryExt === "tsx" ? "tsx" : "jsx";
|
|
1637
|
+
const image = this.registry.get(manifest.image) || null;
|
|
1638
|
+
const esbuildConfig = image?.config.esbuild || {};
|
|
1639
|
+
const frameworkConfig = image?.config.framework || {};
|
|
1640
|
+
const target = esbuildConfig.target || "es2020";
|
|
1641
|
+
const format = esbuildConfig.format || "esm";
|
|
1642
|
+
const jsx = esbuildConfig.jsx ?? "automatic";
|
|
1643
|
+
const packages = {
|
|
1644
|
+
...image?.dependencies || {},
|
|
1645
|
+
...manifest.packages || {}
|
|
1646
|
+
};
|
|
1647
|
+
const globals = frameworkConfig.globals || {};
|
|
1648
|
+
const deps = frameworkConfig.deps || {};
|
|
1649
|
+
const aliases = image?.config.aliases || {};
|
|
1650
|
+
const entryFile = project.files.get(project.entry);
|
|
1651
|
+
if (!entryFile) {
|
|
1652
|
+
throw new Error(`Entry file not found: ${project.entry}`);
|
|
1653
|
+
}
|
|
1654
|
+
const result = await esbuild.build({
|
|
1655
|
+
stdin: {
|
|
1656
|
+
contents: entryFile.content,
|
|
1657
|
+
loader,
|
|
1658
|
+
sourcefile: project.entry
|
|
1659
|
+
},
|
|
1660
|
+
bundle: true,
|
|
1661
|
+
format,
|
|
1662
|
+
target,
|
|
1663
|
+
platform: manifest.platform === "cli" ? "node" : "browser",
|
|
1664
|
+
jsx,
|
|
1665
|
+
...esbuildConfig.jsxFactory ? { jsxFactory: esbuildConfig.jsxFactory } : {},
|
|
1666
|
+
...esbuildConfig.jsxFragment ? { jsxFragment: esbuildConfig.jsxFragment } : {},
|
|
1667
|
+
write: false,
|
|
1668
|
+
sourcemap: "inline",
|
|
1669
|
+
plugins: [
|
|
1670
|
+
vfsPlugin(project, { aliases }),
|
|
1671
|
+
cdnTransformPlugin({
|
|
1672
|
+
packages,
|
|
1673
|
+
globals,
|
|
1674
|
+
deps,
|
|
1675
|
+
aliases
|
|
1676
|
+
})
|
|
1677
|
+
]
|
|
1678
|
+
});
|
|
1679
|
+
const code = result.outputFiles?.[0]?.text || "";
|
|
1680
|
+
const hash = hashContent(code);
|
|
1681
|
+
return {
|
|
1682
|
+
code,
|
|
1683
|
+
hash,
|
|
1684
|
+
manifest
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Mount a compiled widget to the DOM
|
|
1689
|
+
*/
|
|
1690
|
+
async mount(widget, options) {
|
|
1691
|
+
const image = this.registry.get(widget.manifest.image) || null;
|
|
1692
|
+
if (options.mode === "iframe") {
|
|
1693
|
+
return mountIframe(widget, options, image, this.proxy);
|
|
1694
|
+
}
|
|
1695
|
+
return mountEmbedded(widget, options, image, this.proxy);
|
|
1696
|
+
}
|
|
1697
|
+
/**
|
|
1698
|
+
* Unmount a mounted widget
|
|
1699
|
+
*/
|
|
1700
|
+
unmount(mounted) {
|
|
1701
|
+
mounted.unmount();
|
|
1702
|
+
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Hot reload a mounted widget
|
|
1705
|
+
*/
|
|
1706
|
+
async reload(mounted, source, manifest) {
|
|
1707
|
+
const widget = await this.compile(source, manifest);
|
|
1708
|
+
const image = this.registry.get(widget.manifest.image) || null;
|
|
1709
|
+
if (mounted.mode === "iframe") {
|
|
1710
|
+
await reloadIframe(mounted, widget, image, this.proxy);
|
|
1711
|
+
} else {
|
|
1712
|
+
await reloadEmbedded(mounted, widget, image, this.proxy);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
};
|
|
1716
|
+
|
|
1717
|
+
// src/vfs/core/utils.ts
|
|
1718
|
+
function createFileStats(size, mtime, isDir = false) {
|
|
1719
|
+
return {
|
|
1720
|
+
size,
|
|
1721
|
+
mtime,
|
|
1722
|
+
isFile: () => !isDir,
|
|
1723
|
+
isDirectory: () => isDir
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
function createDirEntry(name, isDir) {
|
|
1727
|
+
return {
|
|
1728
|
+
name,
|
|
1729
|
+
isFile: () => !isDir,
|
|
1730
|
+
isDirectory: () => isDir
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
function normalizePath2(path) {
|
|
1734
|
+
return path.replace(/\/+/g, "/").replace(/^\/|\/$/g, "");
|
|
1735
|
+
}
|
|
1736
|
+
function dirname2(path) {
|
|
1737
|
+
const normalized = normalizePath2(path);
|
|
1738
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
1739
|
+
return lastSlash === -1 ? "" : normalized.slice(0, lastSlash);
|
|
1740
|
+
}
|
|
1741
|
+
function join(...parts) {
|
|
1742
|
+
return normalizePath2(parts.filter(Boolean).join("/"));
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
// src/vfs/backends/memory.ts
|
|
1746
|
+
var MemoryBackend = class {
|
|
1747
|
+
files = /* @__PURE__ */ new Map();
|
|
1748
|
+
dirs = /* @__PURE__ */ new Set([""]);
|
|
1749
|
+
watchers = /* @__PURE__ */ new Map();
|
|
1750
|
+
async readFile(path) {
|
|
1751
|
+
const entry = this.files.get(normalizePath2(path));
|
|
1752
|
+
if (!entry) throw new Error(`ENOENT: ${path}`);
|
|
1753
|
+
return entry.content;
|
|
1754
|
+
}
|
|
1755
|
+
async writeFile(path, content) {
|
|
1756
|
+
const normalized = normalizePath2(path);
|
|
1757
|
+
const dir = dirname2(normalized);
|
|
1758
|
+
if (dir && !this.dirs.has(dir)) {
|
|
1759
|
+
throw new Error(`ENOENT: ${dir}`);
|
|
1760
|
+
}
|
|
1761
|
+
const isNew = !this.files.has(normalized);
|
|
1762
|
+
this.files.set(normalized, { content, mtime: /* @__PURE__ */ new Date() });
|
|
1763
|
+
this.emit(isNew ? "create" : "update", normalized);
|
|
1764
|
+
}
|
|
1765
|
+
async unlink(path) {
|
|
1766
|
+
const normalized = normalizePath2(path);
|
|
1767
|
+
if (!this.files.delete(normalized)) {
|
|
1768
|
+
throw new Error(`ENOENT: ${path}`);
|
|
1769
|
+
}
|
|
1770
|
+
this.emit("delete", normalized);
|
|
1771
|
+
}
|
|
1772
|
+
async stat(path) {
|
|
1773
|
+
const normalized = normalizePath2(path);
|
|
1774
|
+
const entry = this.files.get(normalized);
|
|
1775
|
+
if (entry) {
|
|
1776
|
+
return createFileStats(entry.content.length, entry.mtime, false);
|
|
1777
|
+
}
|
|
1778
|
+
if (this.dirs.has(normalized)) {
|
|
1779
|
+
return createFileStats(0, /* @__PURE__ */ new Date(), true);
|
|
1780
|
+
}
|
|
1781
|
+
throw new Error(`ENOENT: ${path}`);
|
|
1782
|
+
}
|
|
1783
|
+
async mkdir(path, options) {
|
|
1784
|
+
const normalized = normalizePath2(path);
|
|
1785
|
+
if (this.dirs.has(normalized)) return;
|
|
1786
|
+
const parent = dirname2(normalized);
|
|
1787
|
+
if (parent && !this.dirs.has(parent)) {
|
|
1788
|
+
if (options?.recursive) {
|
|
1789
|
+
await this.mkdir(parent, options);
|
|
1790
|
+
} else {
|
|
1791
|
+
throw new Error(`ENOENT: ${parent}`);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
this.dirs.add(normalized);
|
|
1795
|
+
}
|
|
1796
|
+
async readdir(path) {
|
|
1797
|
+
const normalized = normalizePath2(path);
|
|
1798
|
+
if (!this.dirs.has(normalized)) {
|
|
1799
|
+
throw new Error(`ENOENT: ${path}`);
|
|
1800
|
+
}
|
|
1801
|
+
const prefix = normalized ? `${normalized}/` : "";
|
|
1802
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1803
|
+
for (const filePath of this.files.keys()) {
|
|
1804
|
+
if (filePath.startsWith(prefix)) {
|
|
1805
|
+
const rest = filePath.slice(prefix.length);
|
|
1806
|
+
const name = rest.split("/")[0];
|
|
1807
|
+
if (name) entries.set(name, false);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
for (const dirPath of this.dirs) {
|
|
1811
|
+
if (dirPath.startsWith(prefix) && dirPath !== normalized) {
|
|
1812
|
+
const rest = dirPath.slice(prefix.length);
|
|
1813
|
+
const name = rest.split("/")[0];
|
|
1814
|
+
if (name) entries.set(name, true);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
return Array.from(entries).map(
|
|
1818
|
+
([name, isDir]) => createDirEntry(name, isDir)
|
|
1819
|
+
);
|
|
1820
|
+
}
|
|
1821
|
+
async rmdir(path, options) {
|
|
1822
|
+
const normalized = normalizePath2(path);
|
|
1823
|
+
if (!this.dirs.has(normalized)) {
|
|
1824
|
+
throw new Error(`ENOENT: ${path}`);
|
|
1825
|
+
}
|
|
1826
|
+
const prefix = `${normalized}/`;
|
|
1827
|
+
const hasChildren = [...this.files.keys()].some((p) => p.startsWith(prefix)) || [...this.dirs].some((d) => d.startsWith(prefix));
|
|
1828
|
+
if (hasChildren && !options?.recursive) {
|
|
1829
|
+
throw new Error(`ENOTEMPTY: ${path}`);
|
|
1830
|
+
}
|
|
1831
|
+
if (options?.recursive) {
|
|
1832
|
+
for (const filePath of this.files.keys()) {
|
|
1833
|
+
if (filePath.startsWith(prefix)) {
|
|
1834
|
+
this.files.delete(filePath);
|
|
1835
|
+
this.emit("delete", filePath);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
for (const dirPath of this.dirs) {
|
|
1839
|
+
if (dirPath.startsWith(prefix)) {
|
|
1840
|
+
this.dirs.delete(dirPath);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
this.dirs.delete(normalized);
|
|
1845
|
+
}
|
|
1846
|
+
async exists(path) {
|
|
1847
|
+
const normalized = normalizePath2(path);
|
|
1848
|
+
return this.files.has(normalized) || this.dirs.has(normalized);
|
|
1849
|
+
}
|
|
1850
|
+
watch(path, callback) {
|
|
1851
|
+
const normalized = normalizePath2(path);
|
|
1852
|
+
let callbacks = this.watchers.get(normalized);
|
|
1853
|
+
if (!callbacks) {
|
|
1854
|
+
callbacks = /* @__PURE__ */ new Set();
|
|
1855
|
+
this.watchers.set(normalized, callbacks);
|
|
1856
|
+
}
|
|
1857
|
+
callbacks.add(callback);
|
|
1858
|
+
return () => callbacks.delete(callback);
|
|
1859
|
+
}
|
|
1860
|
+
emit(event, path) {
|
|
1861
|
+
let current = path;
|
|
1862
|
+
while (true) {
|
|
1863
|
+
const callbacks = this.watchers.get(current);
|
|
1864
|
+
if (callbacks) {
|
|
1865
|
+
for (const cb of callbacks) cb(event, path);
|
|
1866
|
+
}
|
|
1867
|
+
if (!current) break;
|
|
1868
|
+
current = dirname2(current);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
};
|
|
1872
|
+
|
|
1873
|
+
// src/vfs/core/virtual-fs.ts
|
|
1874
|
+
var VirtualFS = class {
|
|
1875
|
+
changes = /* @__PURE__ */ new Map();
|
|
1876
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1877
|
+
backend;
|
|
1878
|
+
constructor(backend) {
|
|
1879
|
+
this.backend = backend ?? new MemoryBackend();
|
|
1880
|
+
}
|
|
1881
|
+
async readFile(path, encoding) {
|
|
1882
|
+
return this.backend.readFile(path, encoding);
|
|
1883
|
+
}
|
|
1884
|
+
async writeFile(path, content) {
|
|
1885
|
+
const existed = await this.backend.exists(path);
|
|
1886
|
+
await this.backend.writeFile(path, content);
|
|
1887
|
+
this.recordChange(path, existed ? "update" : "create");
|
|
1888
|
+
}
|
|
1889
|
+
async applyRemoteFile(path, content) {
|
|
1890
|
+
await this.backend.writeFile(path, content);
|
|
1891
|
+
}
|
|
1892
|
+
async applyRemoteDelete(path) {
|
|
1893
|
+
try {
|
|
1894
|
+
if (await this.backend.exists(path)) {
|
|
1895
|
+
await this.backend.unlink(path);
|
|
1896
|
+
}
|
|
1897
|
+
} catch {
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
async unlink(path) {
|
|
1902
|
+
await this.backend.unlink(path);
|
|
1903
|
+
this.recordChange(path, "delete");
|
|
1904
|
+
}
|
|
1905
|
+
async stat(path) {
|
|
1906
|
+
return this.backend.stat(path);
|
|
1907
|
+
}
|
|
1908
|
+
async mkdir(path, options) {
|
|
1909
|
+
return this.backend.mkdir(path, options);
|
|
1910
|
+
}
|
|
1911
|
+
async readdir(path) {
|
|
1912
|
+
return this.backend.readdir(path);
|
|
1913
|
+
}
|
|
1914
|
+
async rmdir(path, options) {
|
|
1915
|
+
return this.backend.rmdir(path, options);
|
|
1916
|
+
}
|
|
1917
|
+
async exists(path) {
|
|
1918
|
+
return this.backend.exists(path);
|
|
1919
|
+
}
|
|
1920
|
+
watch(path, callback) {
|
|
1921
|
+
if (this.backend.watch) {
|
|
1922
|
+
return this.backend.watch(path, callback);
|
|
1923
|
+
}
|
|
1924
|
+
return () => {
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
/**
|
|
1928
|
+
* Get all pending changes since last sync
|
|
1929
|
+
*/
|
|
1930
|
+
getChanges() {
|
|
1931
|
+
return Array.from(this.changes.values());
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Clear change tracking (after successful sync)
|
|
1935
|
+
*/
|
|
1936
|
+
clearChanges() {
|
|
1937
|
+
this.changes.clear();
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Mark specific paths as synced
|
|
1941
|
+
*/
|
|
1942
|
+
markSynced(paths) {
|
|
1943
|
+
for (const path of paths) {
|
|
1944
|
+
this.changes.delete(path);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
/**
|
|
1948
|
+
* Subscribe to change events
|
|
1949
|
+
*/
|
|
1950
|
+
onChange(listener) {
|
|
1951
|
+
this.listeners.add(listener);
|
|
1952
|
+
return () => this.listeners.delete(listener);
|
|
1953
|
+
}
|
|
1954
|
+
recordChange(path, type) {
|
|
1955
|
+
const record = { path, type, mtime: /* @__PURE__ */ new Date() };
|
|
1956
|
+
this.changes.set(path, record);
|
|
1957
|
+
for (const listener of this.listeners) {
|
|
1958
|
+
listener(record);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
};
|
|
1962
|
+
|
|
1963
|
+
// src/vfs/sync/differ.ts
|
|
1964
|
+
function hashContent2(content) {
|
|
1965
|
+
let hash = 2166136261;
|
|
1966
|
+
for (let i = 0; i < content.length; i += 1) {
|
|
1967
|
+
hash ^= content.charCodeAt(i);
|
|
1968
|
+
hash = hash + (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24) >>> 0;
|
|
1969
|
+
}
|
|
1970
|
+
return hash.toString(16).padStart(8, "0");
|
|
1971
|
+
}
|
|
1972
|
+
async function readChecksum(provider, path) {
|
|
1973
|
+
try {
|
|
1974
|
+
const content = await provider.readFile(path);
|
|
1975
|
+
return hashContent2(content);
|
|
1976
|
+
} catch {
|
|
1977
|
+
return void 0;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
async function readChecksums(local, localPath, remote, remotePath) {
|
|
1981
|
+
const [localChecksum, remoteChecksum] = await Promise.all([
|
|
1982
|
+
readChecksum(local, localPath),
|
|
1983
|
+
readChecksum(remote, remotePath)
|
|
1984
|
+
]);
|
|
1985
|
+
return { local: localChecksum, remote: remoteChecksum };
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
// src/vfs/sync/resolver.ts
|
|
1989
|
+
function resolveConflict(input) {
|
|
1990
|
+
if (input.remoteMtime <= input.changeMtime) return null;
|
|
1991
|
+
if (input.localChecksum && input.remoteChecksum && input.localChecksum === input.remoteChecksum) {
|
|
1992
|
+
return null;
|
|
1993
|
+
}
|
|
1994
|
+
const conflict = {
|
|
1995
|
+
path: input.path,
|
|
1996
|
+
localMtime: input.changeMtime,
|
|
1997
|
+
remoteMtime: input.remoteMtime
|
|
1998
|
+
};
|
|
1999
|
+
switch (input.strategy) {
|
|
2000
|
+
case "local-wins":
|
|
2001
|
+
conflict.resolved = "local";
|
|
2002
|
+
break;
|
|
2003
|
+
case "remote-wins":
|
|
2004
|
+
conflict.resolved = "remote";
|
|
2005
|
+
break;
|
|
2006
|
+
case "newest-wins":
|
|
2007
|
+
conflict.resolved = input.remoteMtime > input.changeMtime ? "remote" : "local";
|
|
2008
|
+
break;
|
|
2009
|
+
case "manual":
|
|
2010
|
+
break;
|
|
2011
|
+
}
|
|
2012
|
+
return conflict;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
// src/vfs/sync/engine.ts
|
|
2016
|
+
var SyncEngineImpl = class {
|
|
2017
|
+
constructor(local, remote, config = {}) {
|
|
2018
|
+
this.local = local;
|
|
2019
|
+
this.remote = remote;
|
|
2020
|
+
this.conflictStrategy = config.conflictStrategy ?? "local-wins";
|
|
2021
|
+
this.basePath = config.basePath ?? "";
|
|
2022
|
+
this.startRemoteWatch();
|
|
2023
|
+
}
|
|
2024
|
+
status = "idle";
|
|
2025
|
+
intervalId;
|
|
2026
|
+
listeners = /* @__PURE__ */ new Map();
|
|
2027
|
+
conflictStrategy;
|
|
2028
|
+
basePath;
|
|
2029
|
+
async sync() {
|
|
2030
|
+
if (this.status === "syncing") {
|
|
2031
|
+
return { pushed: 0, pulled: 0, conflicts: [] };
|
|
2032
|
+
}
|
|
2033
|
+
this.setStatus("syncing");
|
|
2034
|
+
const result = { pushed: 0, pulled: 0, conflicts: [] };
|
|
2035
|
+
try {
|
|
2036
|
+
const localChanges = this.local.getChanges();
|
|
2037
|
+
const localChangeMap = new Map(
|
|
2038
|
+
localChanges.map((change) => [change.path, change])
|
|
2039
|
+
);
|
|
2040
|
+
const syncedPaths = [];
|
|
2041
|
+
const remoteFiles = await this.listFiles(this.remote, this.basePath);
|
|
2042
|
+
const localFiles = await this.listFiles(this.local, "");
|
|
2043
|
+
const remoteLocalPaths = new Set(
|
|
2044
|
+
remoteFiles.map((path) => this.localPath(path))
|
|
2045
|
+
);
|
|
2046
|
+
for (const remotePath of remoteFiles) {
|
|
2047
|
+
const localPath = this.localPath(remotePath);
|
|
2048
|
+
const localChange = localChangeMap.get(localPath);
|
|
2049
|
+
if (localChange) {
|
|
2050
|
+
const conflict = await this.checkConflict(localChange, remotePath);
|
|
2051
|
+
if (conflict) {
|
|
2052
|
+
result.conflicts.push(conflict);
|
|
2053
|
+
this.emit("conflict", conflict);
|
|
2054
|
+
if (conflict.resolved === "remote") {
|
|
2055
|
+
if (await this.pullRemoteFile(localPath, remotePath)) {
|
|
2056
|
+
result.pulled++;
|
|
2057
|
+
this.emit("change", {
|
|
2058
|
+
path: localPath,
|
|
2059
|
+
type: "update",
|
|
2060
|
+
mtime: /* @__PURE__ */ new Date()
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2063
|
+
syncedPaths.push(localPath);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
continue;
|
|
2067
|
+
}
|
|
2068
|
+
if (await this.pullRemoteFile(localPath, remotePath)) {
|
|
2069
|
+
result.pulled++;
|
|
2070
|
+
this.emit("change", {
|
|
2071
|
+
path: localPath,
|
|
2072
|
+
type: "update",
|
|
2073
|
+
mtime: /* @__PURE__ */ new Date()
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
for (const localPath of localFiles) {
|
|
2078
|
+
if (remoteLocalPaths.has(localPath)) continue;
|
|
2079
|
+
if (localChangeMap.has(localPath)) continue;
|
|
2080
|
+
await this.local.applyRemoteDelete(localPath);
|
|
2081
|
+
result.pulled++;
|
|
2082
|
+
this.emit("change", {
|
|
2083
|
+
path: localPath,
|
|
2084
|
+
type: "delete",
|
|
2085
|
+
mtime: /* @__PURE__ */ new Date()
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
for (const change of localChanges) {
|
|
2089
|
+
if (syncedPaths.includes(change.path)) continue;
|
|
2090
|
+
const remotePath = this.remotePath(change.path);
|
|
2091
|
+
try {
|
|
2092
|
+
const conflict = await this.checkConflict(change, remotePath);
|
|
2093
|
+
if (conflict) {
|
|
2094
|
+
result.conflicts.push(conflict);
|
|
2095
|
+
this.emit("conflict", conflict);
|
|
2096
|
+
if (conflict.resolved === "remote") {
|
|
2097
|
+
if (await this.pullRemoteFile(change.path, remotePath)) {
|
|
2098
|
+
result.pulled++;
|
|
2099
|
+
this.emit("change", {
|
|
2100
|
+
path: change.path,
|
|
2101
|
+
type: "update",
|
|
2102
|
+
mtime: /* @__PURE__ */ new Date()
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
syncedPaths.push(change.path);
|
|
2106
|
+
}
|
|
2107
|
+
if (conflict.resolved !== "local") continue;
|
|
2108
|
+
}
|
|
2109
|
+
if (change.type === "delete") {
|
|
2110
|
+
if (await this.remote.exists(remotePath)) {
|
|
2111
|
+
await this.remote.unlink(remotePath);
|
|
2112
|
+
}
|
|
2113
|
+
result.pushed++;
|
|
2114
|
+
syncedPaths.push(change.path);
|
|
2115
|
+
this.emit("change", change);
|
|
2116
|
+
continue;
|
|
2117
|
+
}
|
|
2118
|
+
const content = await this.local.readFile(change.path);
|
|
2119
|
+
await this.remote.writeFile(remotePath, content);
|
|
2120
|
+
result.pushed++;
|
|
2121
|
+
syncedPaths.push(change.path);
|
|
2122
|
+
this.emit("change", change);
|
|
2123
|
+
} catch (err) {
|
|
2124
|
+
this.emit(
|
|
2125
|
+
"error",
|
|
2126
|
+
err instanceof Error ? err : new Error(String(err))
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
if (syncedPaths.length > 0) {
|
|
2131
|
+
this.local.markSynced(syncedPaths);
|
|
2132
|
+
}
|
|
2133
|
+
this.setStatus("idle");
|
|
2134
|
+
} catch (err) {
|
|
2135
|
+
this.setStatus("error");
|
|
2136
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
2137
|
+
}
|
|
2138
|
+
return result;
|
|
2139
|
+
}
|
|
2140
|
+
startAutoSync(intervalMs) {
|
|
2141
|
+
this.stopAutoSync();
|
|
2142
|
+
this.intervalId = setInterval(() => this.sync(), intervalMs);
|
|
2143
|
+
}
|
|
2144
|
+
stopAutoSync() {
|
|
2145
|
+
if (this.intervalId) {
|
|
2146
|
+
clearInterval(this.intervalId);
|
|
2147
|
+
this.intervalId = void 0;
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
on(event, callback) {
|
|
2151
|
+
let set = this.listeners.get(event);
|
|
2152
|
+
if (!set) {
|
|
2153
|
+
set = /* @__PURE__ */ new Set();
|
|
2154
|
+
this.listeners.set(event, set);
|
|
2155
|
+
}
|
|
2156
|
+
set.add(callback);
|
|
2157
|
+
return () => set.delete(callback);
|
|
2158
|
+
}
|
|
2159
|
+
emit(event, data) {
|
|
2160
|
+
const set = this.listeners.get(event);
|
|
2161
|
+
if (set) {
|
|
2162
|
+
for (const cb of set) cb(data);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
setStatus(status) {
|
|
2166
|
+
this.status = status;
|
|
2167
|
+
this.emit("status", status);
|
|
2168
|
+
}
|
|
2169
|
+
remotePath(localPath) {
|
|
2170
|
+
return this.basePath ? join(this.basePath, localPath) : localPath;
|
|
2171
|
+
}
|
|
2172
|
+
localPath(remotePath) {
|
|
2173
|
+
if (!this.basePath) return normalizePath2(remotePath);
|
|
2174
|
+
const normalized = normalizePath2(remotePath);
|
|
2175
|
+
const base = normalizePath2(this.basePath);
|
|
2176
|
+
if (normalized === base) return "";
|
|
2177
|
+
if (normalized.startsWith(`${base}/`)) {
|
|
2178
|
+
return normalized.slice(base.length + 1);
|
|
2179
|
+
}
|
|
2180
|
+
return normalized;
|
|
2181
|
+
}
|
|
2182
|
+
async listFiles(provider, basePath) {
|
|
2183
|
+
const normalized = normalizePath2(basePath);
|
|
2184
|
+
let entries = [];
|
|
2185
|
+
try {
|
|
2186
|
+
entries = await provider.readdir(normalized);
|
|
2187
|
+
} catch {
|
|
2188
|
+
return [];
|
|
2189
|
+
}
|
|
2190
|
+
const results = [];
|
|
2191
|
+
for (const entry of entries) {
|
|
2192
|
+
const entryPath = normalized ? `${normalized}/${entry.name}` : entry.name;
|
|
2193
|
+
if (entry.isDirectory()) {
|
|
2194
|
+
results.push(...await this.listFiles(provider, entryPath));
|
|
2195
|
+
} else {
|
|
2196
|
+
results.push(entryPath);
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
return results;
|
|
2200
|
+
}
|
|
2201
|
+
async pullRemoteFile(localPath, remotePath) {
|
|
2202
|
+
let localContent = null;
|
|
2203
|
+
try {
|
|
2204
|
+
if (await this.local.exists(localPath)) {
|
|
2205
|
+
localContent = await this.local.readFile(localPath);
|
|
2206
|
+
}
|
|
2207
|
+
} catch {
|
|
2208
|
+
localContent = null;
|
|
2209
|
+
}
|
|
2210
|
+
const remoteContent = await this.remote.readFile(remotePath);
|
|
2211
|
+
if (localContent === remoteContent) return false;
|
|
2212
|
+
await this.local.applyRemoteFile(localPath, remoteContent);
|
|
2213
|
+
return true;
|
|
2214
|
+
}
|
|
2215
|
+
startRemoteWatch() {
|
|
2216
|
+
if (!this.remote.watch) return;
|
|
2217
|
+
this.remote.watch(this.basePath, (event, path) => {
|
|
2218
|
+
void this.handleRemoteEvent(event, path);
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2221
|
+
async handleRemoteEvent(event, remotePath) {
|
|
2222
|
+
const localPath = this.localPath(remotePath);
|
|
2223
|
+
const localChange = this.local.getChanges().find((change) => change.path === localPath);
|
|
2224
|
+
if (localChange) {
|
|
2225
|
+
const conflict = await this.checkRemoteEventConflict(
|
|
2226
|
+
localChange,
|
|
2227
|
+
remotePath,
|
|
2228
|
+
event
|
|
2229
|
+
);
|
|
2230
|
+
if (conflict) {
|
|
2231
|
+
this.emit("conflict", conflict);
|
|
2232
|
+
if (conflict.resolved === "remote") {
|
|
2233
|
+
await this.applyRemoteEvent(event, localPath, remotePath);
|
|
2234
|
+
this.local.markSynced([localPath]);
|
|
2235
|
+
this.emit("change", {
|
|
2236
|
+
path: localPath,
|
|
2237
|
+
type: event,
|
|
2238
|
+
mtime: /* @__PURE__ */ new Date()
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
try {
|
|
2246
|
+
await this.applyRemoteEvent(event, localPath, remotePath);
|
|
2247
|
+
this.emit("change", {
|
|
2248
|
+
path: localPath,
|
|
2249
|
+
type: event,
|
|
2250
|
+
mtime: /* @__PURE__ */ new Date()
|
|
2251
|
+
});
|
|
2252
|
+
} catch (err) {
|
|
2253
|
+
this.emit("error", err instanceof Error ? err : new Error(String(err)));
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
async checkConflict(change, remotePath) {
|
|
2257
|
+
try {
|
|
2258
|
+
const remoteStat = await this.remote.stat(remotePath);
|
|
2259
|
+
if (remoteStat.mtime <= change.mtime) return null;
|
|
2260
|
+
const checksums = await readChecksums(
|
|
2261
|
+
this.local,
|
|
2262
|
+
change.path,
|
|
2263
|
+
this.remote,
|
|
2264
|
+
remotePath
|
|
2265
|
+
);
|
|
2266
|
+
return resolveConflict({
|
|
2267
|
+
path: change.path,
|
|
2268
|
+
changeMtime: change.mtime,
|
|
2269
|
+
remoteMtime: remoteStat.mtime,
|
|
2270
|
+
localChecksum: checksums.local,
|
|
2271
|
+
remoteChecksum: checksums.remote,
|
|
2272
|
+
strategy: this.conflictStrategy
|
|
2273
|
+
});
|
|
2274
|
+
} catch {
|
|
2275
|
+
return null;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
async checkRemoteEventConflict(change, remotePath, event) {
|
|
2279
|
+
if (event === "delete") {
|
|
2280
|
+
if (change.type === "delete") return null;
|
|
2281
|
+
return resolveConflict({
|
|
2282
|
+
path: change.path,
|
|
2283
|
+
changeMtime: change.mtime,
|
|
2284
|
+
remoteMtime: /* @__PURE__ */ new Date(),
|
|
2285
|
+
strategy: this.conflictStrategy
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
try {
|
|
2289
|
+
const remoteStat = await this.remote.stat(remotePath);
|
|
2290
|
+
if (remoteStat.mtime <= change.mtime) return null;
|
|
2291
|
+
const checksums = await readChecksums(
|
|
2292
|
+
this.local,
|
|
2293
|
+
change.path,
|
|
2294
|
+
this.remote,
|
|
2295
|
+
remotePath
|
|
2296
|
+
);
|
|
2297
|
+
return resolveConflict({
|
|
2298
|
+
path: change.path,
|
|
2299
|
+
changeMtime: change.mtime,
|
|
2300
|
+
remoteMtime: remoteStat.mtime,
|
|
2301
|
+
localChecksum: checksums.local,
|
|
2302
|
+
remoteChecksum: checksums.remote,
|
|
2303
|
+
strategy: this.conflictStrategy
|
|
2304
|
+
});
|
|
2305
|
+
} catch {
|
|
2306
|
+
return null;
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
async applyRemoteEvent(event, localPath, remotePath) {
|
|
2310
|
+
if (event === "delete") {
|
|
2311
|
+
await this.local.applyRemoteDelete(localPath);
|
|
2312
|
+
return;
|
|
2313
|
+
}
|
|
2314
|
+
const content = await this.remote.readFile(remotePath);
|
|
2315
|
+
await this.local.applyRemoteFile(localPath, content);
|
|
2316
|
+
}
|
|
2317
|
+
};
|
|
2318
|
+
|
|
2319
|
+
// src/vfs/backends/indexeddb.ts
|
|
2320
|
+
var DB_NAME = "patchwork-vfs";
|
|
2321
|
+
var DB_VERSION = 2;
|
|
2322
|
+
var FILES_STORE = "files";
|
|
2323
|
+
var DIRS_STORE = "dirs";
|
|
2324
|
+
function openDB() {
|
|
2325
|
+
return new Promise((resolve, reject) => {
|
|
2326
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
2327
|
+
request.onerror = () => reject(request.error);
|
|
2328
|
+
request.onsuccess = () => resolve(request.result);
|
|
2329
|
+
request.onupgradeneeded = (event) => {
|
|
2330
|
+
const db = request.result;
|
|
2331
|
+
if (!db.objectStoreNames.contains(FILES_STORE)) {
|
|
2332
|
+
db.createObjectStore(FILES_STORE);
|
|
2333
|
+
}
|
|
2334
|
+
if (!db.objectStoreNames.contains(DIRS_STORE)) {
|
|
2335
|
+
db.createObjectStore(DIRS_STORE);
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
function withStore(storeName, mode, fn) {
|
|
2341
|
+
return openDB().then(
|
|
2342
|
+
(db) => new Promise((resolve, reject) => {
|
|
2343
|
+
const tx = db.transaction(storeName, mode);
|
|
2344
|
+
const store = tx.objectStore(storeName);
|
|
2345
|
+
const request = fn(store);
|
|
2346
|
+
request.onerror = () => reject(request.error);
|
|
2347
|
+
request.onsuccess = () => resolve(request.result);
|
|
2348
|
+
})
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
var IndexedDBBackend = class {
|
|
2352
|
+
constructor(prefix = "vfs") {
|
|
2353
|
+
this.prefix = prefix;
|
|
2354
|
+
}
|
|
2355
|
+
key(path) {
|
|
2356
|
+
return `${this.prefix}:${normalizePath2(path)}`;
|
|
2357
|
+
}
|
|
2358
|
+
async readFile(path) {
|
|
2359
|
+
const record = await withStore(
|
|
2360
|
+
FILES_STORE,
|
|
2361
|
+
"readonly",
|
|
2362
|
+
(store) => store.get(this.key(path))
|
|
2363
|
+
);
|
|
2364
|
+
if (!record) throw new Error(`ENOENT: ${path}`);
|
|
2365
|
+
return record.content;
|
|
2366
|
+
}
|
|
2367
|
+
async writeFile(path, content) {
|
|
2368
|
+
const dir = dirname2(normalizePath2(path));
|
|
2369
|
+
if (dir && !await this.dirExists(dir)) {
|
|
2370
|
+
throw new Error(`ENOENT: ${dir}`);
|
|
2371
|
+
}
|
|
2372
|
+
const record = { content, mtime: Date.now() };
|
|
2373
|
+
await withStore(
|
|
2374
|
+
FILES_STORE,
|
|
2375
|
+
"readwrite",
|
|
2376
|
+
(store) => store.put(record, this.key(path))
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
async unlink(path) {
|
|
2380
|
+
await withStore(
|
|
2381
|
+
FILES_STORE,
|
|
2382
|
+
"readwrite",
|
|
2383
|
+
(store) => store.delete(this.key(path))
|
|
2384
|
+
);
|
|
2385
|
+
}
|
|
2386
|
+
async stat(path) {
|
|
2387
|
+
const normalized = normalizePath2(path);
|
|
2388
|
+
const record = await withStore(
|
|
2389
|
+
FILES_STORE,
|
|
2390
|
+
"readonly",
|
|
2391
|
+
(store) => store.get(this.key(normalized))
|
|
2392
|
+
);
|
|
2393
|
+
if (record) {
|
|
2394
|
+
return createFileStats(
|
|
2395
|
+
record.content.length,
|
|
2396
|
+
new Date(record.mtime),
|
|
2397
|
+
false
|
|
2398
|
+
);
|
|
2399
|
+
}
|
|
2400
|
+
if (await this.dirExists(normalized)) {
|
|
2401
|
+
return createFileStats(0, /* @__PURE__ */ new Date(), true);
|
|
2402
|
+
}
|
|
2403
|
+
throw new Error(`ENOENT: ${path}`);
|
|
2404
|
+
}
|
|
2405
|
+
async mkdir(path, options) {
|
|
2406
|
+
const normalized = normalizePath2(path);
|
|
2407
|
+
if (await this.dirExists(normalized)) return;
|
|
2408
|
+
const parent = dirname2(normalized);
|
|
2409
|
+
if (parent && !await this.dirExists(parent)) {
|
|
2410
|
+
if (options?.recursive) {
|
|
2411
|
+
await this.mkdir(parent, options);
|
|
2412
|
+
} else {
|
|
2413
|
+
throw new Error(`ENOENT: ${parent}`);
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
await withStore(
|
|
2417
|
+
DIRS_STORE,
|
|
2418
|
+
"readwrite",
|
|
2419
|
+
(store) => store.put(Date.now(), this.key(normalized))
|
|
2420
|
+
);
|
|
2421
|
+
}
|
|
2422
|
+
async readdir(path) {
|
|
2423
|
+
const normalized = normalizePath2(path);
|
|
2424
|
+
if (normalized && !await this.dirExists(normalized)) {
|
|
2425
|
+
throw new Error(`ENOENT: ${path}`);
|
|
2426
|
+
}
|
|
2427
|
+
const prefix = normalized ? `${this.key(normalized)}/` : `${this.prefix}:`;
|
|
2428
|
+
const entries = /* @__PURE__ */ new Map();
|
|
2429
|
+
const fileKeys = await withStore(
|
|
2430
|
+
FILES_STORE,
|
|
2431
|
+
"readonly",
|
|
2432
|
+
(store) => store.getAllKeys()
|
|
2433
|
+
);
|
|
2434
|
+
for (const key of fileKeys) {
|
|
2435
|
+
if (key.startsWith(prefix)) {
|
|
2436
|
+
const rest = key.slice(prefix.length);
|
|
2437
|
+
const name = rest.split("/")[0];
|
|
2438
|
+
if (name && !rest.includes("/")) entries.set(name, false);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
const dirKeys = await withStore(
|
|
2442
|
+
DIRS_STORE,
|
|
2443
|
+
"readonly",
|
|
2444
|
+
(store) => store.getAllKeys()
|
|
2445
|
+
);
|
|
2446
|
+
for (const key of dirKeys) {
|
|
2447
|
+
if (key.startsWith(prefix)) {
|
|
2448
|
+
const rest = key.slice(prefix.length);
|
|
2449
|
+
const name = rest.split("/")[0];
|
|
2450
|
+
if (name && !rest.includes("/")) entries.set(name, true);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
return Array.from(entries).map(
|
|
2454
|
+
([name, isDir]) => createDirEntry(name, isDir)
|
|
2455
|
+
);
|
|
2456
|
+
}
|
|
2457
|
+
async rmdir(path, options) {
|
|
2458
|
+
const normalized = normalizePath2(path);
|
|
2459
|
+
if (!await this.dirExists(normalized)) {
|
|
2460
|
+
throw new Error(`ENOENT: ${path}`);
|
|
2461
|
+
}
|
|
2462
|
+
const prefix = `${this.key(normalized)}/`;
|
|
2463
|
+
if (options?.recursive) {
|
|
2464
|
+
const fileKeys = await withStore(
|
|
2465
|
+
FILES_STORE,
|
|
2466
|
+
"readonly",
|
|
2467
|
+
(store) => store.getAllKeys()
|
|
2468
|
+
);
|
|
2469
|
+
for (const key of fileKeys) {
|
|
2470
|
+
if (key.startsWith(prefix)) {
|
|
2471
|
+
await withStore(
|
|
2472
|
+
FILES_STORE,
|
|
2473
|
+
"readwrite",
|
|
2474
|
+
(store) => store.delete(key)
|
|
2475
|
+
);
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
const dirKeys = await withStore(
|
|
2479
|
+
DIRS_STORE,
|
|
2480
|
+
"readonly",
|
|
2481
|
+
(store) => store.getAllKeys()
|
|
2482
|
+
);
|
|
2483
|
+
for (const key of dirKeys) {
|
|
2484
|
+
if (key.startsWith(prefix)) {
|
|
2485
|
+
await withStore(
|
|
2486
|
+
DIRS_STORE,
|
|
2487
|
+
"readwrite",
|
|
2488
|
+
(store) => store.delete(key)
|
|
2489
|
+
);
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
await withStore(
|
|
2494
|
+
DIRS_STORE,
|
|
2495
|
+
"readwrite",
|
|
2496
|
+
(store) => store.delete(this.key(normalized))
|
|
2497
|
+
);
|
|
2498
|
+
}
|
|
2499
|
+
async exists(path) {
|
|
2500
|
+
const normalized = normalizePath2(path);
|
|
2501
|
+
const record = await withStore(
|
|
2502
|
+
FILES_STORE,
|
|
2503
|
+
"readonly",
|
|
2504
|
+
(store) => store.get(this.key(normalized))
|
|
2505
|
+
);
|
|
2506
|
+
if (record) return true;
|
|
2507
|
+
return this.dirExists(normalized);
|
|
2508
|
+
}
|
|
2509
|
+
async dirExists(path) {
|
|
2510
|
+
if (!path) return true;
|
|
2511
|
+
const result = await withStore(
|
|
2512
|
+
DIRS_STORE,
|
|
2513
|
+
"readonly",
|
|
2514
|
+
(store) => store.get(this.key(path))
|
|
2515
|
+
);
|
|
2516
|
+
return result !== void 0;
|
|
2517
|
+
}
|
|
2518
|
+
};
|
|
2519
|
+
|
|
2520
|
+
// src/vfs/backends/http.ts
|
|
2521
|
+
var HttpBackend = class {
|
|
2522
|
+
constructor(config) {
|
|
2523
|
+
this.config = config;
|
|
2524
|
+
}
|
|
2525
|
+
async readFile(path) {
|
|
2526
|
+
const res = await fetch(this.url(path));
|
|
2527
|
+
if (!res.ok) throw new Error(`ENOENT: ${path}`);
|
|
2528
|
+
return res.text();
|
|
2529
|
+
}
|
|
2530
|
+
async writeFile(path, content) {
|
|
2531
|
+
const res = await fetch(this.url(path), {
|
|
2532
|
+
method: "PUT",
|
|
2533
|
+
body: content,
|
|
2534
|
+
headers: { "Content-Type": "text/plain" }
|
|
2535
|
+
});
|
|
2536
|
+
if (!res.ok) throw new Error(`Failed to write: ${path}`);
|
|
2537
|
+
}
|
|
2538
|
+
async unlink(path) {
|
|
2539
|
+
const res = await fetch(this.url(path), { method: "DELETE" });
|
|
2540
|
+
if (!res.ok) throw new Error(`Failed to delete: ${path}`);
|
|
2541
|
+
}
|
|
2542
|
+
async stat(path) {
|
|
2543
|
+
const res = await fetch(this.url(path, { stat: "true" }));
|
|
2544
|
+
if (!res.ok) throw new Error(`ENOENT: ${path}`);
|
|
2545
|
+
const data = await res.json();
|
|
2546
|
+
return createFileStats(data.size, new Date(data.mtime), data.isDirectory);
|
|
2547
|
+
}
|
|
2548
|
+
async mkdir(path, options) {
|
|
2549
|
+
const params = { mkdir: "true" };
|
|
2550
|
+
if (options?.recursive) params.recursive = "true";
|
|
2551
|
+
const res = await fetch(this.url(path, params), { method: "POST" });
|
|
2552
|
+
if (!res.ok) throw new Error(`Failed to mkdir: ${path}`);
|
|
2553
|
+
}
|
|
2554
|
+
async readdir(path) {
|
|
2555
|
+
const res = await fetch(this.url(path, { readdir: "true" }));
|
|
2556
|
+
if (!res.ok) throw new Error(`ENOENT: ${path}`);
|
|
2557
|
+
const entries = await res.json();
|
|
2558
|
+
return entries.map((e) => createDirEntry(e.name, e.isDirectory));
|
|
2559
|
+
}
|
|
2560
|
+
async rmdir(path, options) {
|
|
2561
|
+
const params = {};
|
|
2562
|
+
if (options?.recursive) params.recursive = "true";
|
|
2563
|
+
const res = await fetch(this.url(path, params), { method: "DELETE" });
|
|
2564
|
+
if (!res.ok) throw new Error(`Failed to rmdir: ${path}`);
|
|
2565
|
+
}
|
|
2566
|
+
async exists(path) {
|
|
2567
|
+
const res = await fetch(this.url(path), { method: "HEAD" });
|
|
2568
|
+
return res.ok;
|
|
2569
|
+
}
|
|
2570
|
+
watch(path, callback) {
|
|
2571
|
+
const controller = new AbortController();
|
|
2572
|
+
this.startWatch(path, callback, controller.signal);
|
|
2573
|
+
return () => controller.abort();
|
|
2574
|
+
}
|
|
2575
|
+
async startWatch(path, callback, signal) {
|
|
2576
|
+
try {
|
|
2577
|
+
const res = await fetch(this.url("", { watch: path }), { signal });
|
|
2578
|
+
if (!res.ok) return;
|
|
2579
|
+
const reader = res.body?.getReader();
|
|
2580
|
+
if (!reader) return;
|
|
2581
|
+
const decoder = new TextDecoder();
|
|
2582
|
+
let buffer = "";
|
|
2583
|
+
while (!signal.aborted) {
|
|
2584
|
+
const { done, value } = await reader.read();
|
|
2585
|
+
if (done) break;
|
|
2586
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2587
|
+
const lines = buffer.split("\n");
|
|
2588
|
+
buffer = lines.pop() ?? "";
|
|
2589
|
+
for (const line of lines) {
|
|
2590
|
+
if (line.startsWith("data: ")) {
|
|
2591
|
+
try {
|
|
2592
|
+
const event = JSON.parse(line.slice(6));
|
|
2593
|
+
callback(event.type, event.path);
|
|
2594
|
+
} catch {
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
} catch {
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
url(path, params) {
|
|
2603
|
+
const baseUrl = this.config.baseUrl.replace(/\/+$/, "");
|
|
2604
|
+
const cleanPath = path.replace(/^\/+/, "");
|
|
2605
|
+
const base = cleanPath ? `${baseUrl}/${cleanPath}` : baseUrl;
|
|
2606
|
+
if (!params) return base;
|
|
2607
|
+
const query = new URLSearchParams(params).toString();
|
|
2608
|
+
return `${base}?${query}`;
|
|
2609
|
+
}
|
|
2610
|
+
};
|
|
2611
|
+
|
|
2612
|
+
// src/vfs/store.ts
|
|
2613
|
+
var VFSStore = class {
|
|
2614
|
+
constructor(provider, options = {}) {
|
|
2615
|
+
this.provider = provider;
|
|
2616
|
+
this.root = options.root ?? "";
|
|
2617
|
+
if (options.sync) {
|
|
2618
|
+
this.local = new VirtualFS();
|
|
2619
|
+
this.syncEngine = new SyncEngineImpl(this.local, this.provider, {
|
|
2620
|
+
conflictStrategy: options.conflictStrategy,
|
|
2621
|
+
basePath: this.root
|
|
2622
|
+
});
|
|
2623
|
+
if (options.autoSyncIntervalMs) {
|
|
2624
|
+
this.syncEngine.startAutoSync(options.autoSyncIntervalMs);
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
local;
|
|
2629
|
+
syncEngine;
|
|
2630
|
+
root;
|
|
2631
|
+
async readFile(path, encoding) {
|
|
2632
|
+
if (this.local) {
|
|
2633
|
+
try {
|
|
2634
|
+
return await this.local.readFile(path, encoding);
|
|
2635
|
+
} catch {
|
|
2636
|
+
const content = await this.provider.readFile(
|
|
2637
|
+
this.remotePath(path),
|
|
2638
|
+
encoding
|
|
2639
|
+
);
|
|
2640
|
+
await this.local.applyRemoteFile(path, content);
|
|
2641
|
+
return content;
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
return this.provider.readFile(this.remotePath(path), encoding);
|
|
2645
|
+
}
|
|
2646
|
+
async writeFile(path, content) {
|
|
2647
|
+
if (this.local) {
|
|
2648
|
+
await this.local.writeFile(path, content);
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
await this.provider.writeFile(this.remotePath(path), content);
|
|
2652
|
+
}
|
|
2653
|
+
async unlink(path) {
|
|
2654
|
+
if (this.local) {
|
|
2655
|
+
await this.local.unlink(path);
|
|
2656
|
+
return;
|
|
2657
|
+
}
|
|
2658
|
+
await this.provider.unlink(this.remotePath(path));
|
|
2659
|
+
}
|
|
2660
|
+
async stat(path) {
|
|
2661
|
+
if (this.local) {
|
|
2662
|
+
try {
|
|
2663
|
+
return await this.local.stat(path);
|
|
2664
|
+
} catch {
|
|
2665
|
+
return this.provider.stat(this.remotePath(path));
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
return this.provider.stat(this.remotePath(path));
|
|
2669
|
+
}
|
|
2670
|
+
async mkdir(path, options) {
|
|
2671
|
+
if (this.local) {
|
|
2672
|
+
await this.local.mkdir(path, options);
|
|
2673
|
+
}
|
|
2674
|
+
await this.provider.mkdir(this.remotePath(path), options);
|
|
2675
|
+
}
|
|
2676
|
+
async readdir(path) {
|
|
2677
|
+
if (this.local) {
|
|
2678
|
+
try {
|
|
2679
|
+
return await this.local.readdir(path);
|
|
2680
|
+
} catch {
|
|
2681
|
+
return this.provider.readdir(this.remotePath(path));
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
return this.provider.readdir(this.remotePath(path));
|
|
2685
|
+
}
|
|
2686
|
+
async rmdir(path, options) {
|
|
2687
|
+
if (this.local) {
|
|
2688
|
+
await this.local.rmdir(path, options);
|
|
2689
|
+
}
|
|
2690
|
+
await this.provider.rmdir(this.remotePath(path), options);
|
|
2691
|
+
}
|
|
2692
|
+
async exists(path) {
|
|
2693
|
+
if (this.local) {
|
|
2694
|
+
if (await this.local.exists(path)) return true;
|
|
2695
|
+
return this.provider.exists(this.remotePath(path));
|
|
2696
|
+
}
|
|
2697
|
+
return this.provider.exists(this.remotePath(path));
|
|
2698
|
+
}
|
|
2699
|
+
async listFiles(prefix = "") {
|
|
2700
|
+
return this.walkFiles(prefix);
|
|
2701
|
+
}
|
|
2702
|
+
async loadProject(id) {
|
|
2703
|
+
const paths = await this.listFiles(id);
|
|
2704
|
+
if (paths.length === 0) return null;
|
|
2705
|
+
const files = /* @__PURE__ */ new Map();
|
|
2706
|
+
await Promise.all(
|
|
2707
|
+
paths.map(async (path) => {
|
|
2708
|
+
const content = await this.provider.readFile(this.remotePath(path));
|
|
2709
|
+
const relative = path.slice(id.length + 1);
|
|
2710
|
+
files.set(relative, { path: relative, content });
|
|
2711
|
+
if (this.local) {
|
|
2712
|
+
await this.local.applyRemoteFile(path, content);
|
|
2713
|
+
}
|
|
2714
|
+
})
|
|
2715
|
+
);
|
|
2716
|
+
return { id, entry: resolveEntry(files), files };
|
|
2717
|
+
}
|
|
2718
|
+
async saveProject(project) {
|
|
2719
|
+
if (this.local) {
|
|
2720
|
+
await Promise.all(
|
|
2721
|
+
Array.from(project.files.values()).map(
|
|
2722
|
+
(file) => this.local.writeFile(`${project.id}/${file.path}`, file.content)
|
|
2723
|
+
)
|
|
2724
|
+
);
|
|
2725
|
+
await this.sync();
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2728
|
+
await Promise.all(
|
|
2729
|
+
Array.from(project.files.values()).map(
|
|
2730
|
+
(file) => this.provider.writeFile(
|
|
2731
|
+
this.remotePath(`${project.id}/${file.path}`),
|
|
2732
|
+
file.content
|
|
2733
|
+
)
|
|
2734
|
+
)
|
|
2735
|
+
);
|
|
2736
|
+
}
|
|
2737
|
+
async sync() {
|
|
2738
|
+
if (!this.syncEngine) {
|
|
2739
|
+
return { pushed: 0, pulled: 0, conflicts: [] };
|
|
2740
|
+
}
|
|
2741
|
+
return this.syncEngine.sync();
|
|
2742
|
+
}
|
|
2743
|
+
on(event, callback) {
|
|
2744
|
+
if (!this.syncEngine) return () => {
|
|
2745
|
+
};
|
|
2746
|
+
return this.syncEngine.on(event, callback);
|
|
2747
|
+
}
|
|
2748
|
+
remotePath(path) {
|
|
2749
|
+
return this.root ? join(this.root, path) : path;
|
|
2750
|
+
}
|
|
2751
|
+
async walkFiles(prefix) {
|
|
2752
|
+
const results = [];
|
|
2753
|
+
const normalized = prefix ? prefix.replace(/^\/+/g, "") : "";
|
|
2754
|
+
let entries = [];
|
|
2755
|
+
try {
|
|
2756
|
+
entries = await this.provider.readdir(this.remotePath(normalized));
|
|
2757
|
+
} catch {
|
|
2758
|
+
return results;
|
|
2759
|
+
}
|
|
2760
|
+
for (const entry of entries) {
|
|
2761
|
+
const entryPath = normalized ? `${normalized}/${entry.name}` : entry.name;
|
|
2762
|
+
if (entry.isDirectory()) {
|
|
2763
|
+
results.push(...await this.walkFiles(entryPath));
|
|
2764
|
+
} else {
|
|
2765
|
+
results.push(entryPath);
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
return results;
|
|
2769
|
+
}
|
|
2770
|
+
};
|
|
2771
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2772
|
+
0 && (module.exports = {
|
|
2773
|
+
CompileOptionsSchema,
|
|
2774
|
+
DEFAULT_CLI_IMAGE_CONFIG,
|
|
2775
|
+
DEFAULT_IMAGE_CONFIG,
|
|
2776
|
+
DEV_SANDBOX,
|
|
2777
|
+
EsbuildConfigSchema,
|
|
2778
|
+
HttpBackend,
|
|
2779
|
+
ImageConfigSchema,
|
|
2780
|
+
ImageRegistry,
|
|
2781
|
+
IndexedDBBackend,
|
|
2782
|
+
InputSpecSchema,
|
|
2783
|
+
ManifestSchema,
|
|
2784
|
+
MountModeSchema,
|
|
2785
|
+
MountOptionsSchema,
|
|
2786
|
+
ParentBridge,
|
|
2787
|
+
PlatformSchema,
|
|
2788
|
+
VFSStore,
|
|
2789
|
+
cdnTransformPlugin,
|
|
2790
|
+
createCompiler,
|
|
2791
|
+
createFieldAccessProxy,
|
|
2792
|
+
createHttpServiceProxy,
|
|
2793
|
+
createIframeServiceProxy,
|
|
2794
|
+
createImageRegistry,
|
|
2795
|
+
createProjectFromFiles,
|
|
2796
|
+
createSingleFileProject,
|
|
2797
|
+
detectMainFile,
|
|
2798
|
+
disposeIframeBridge,
|
|
2799
|
+
extractNamespaces,
|
|
2800
|
+
fetchPackageJson,
|
|
2801
|
+
generateIframeBridgeScript,
|
|
2802
|
+
generateImportMap,
|
|
2803
|
+
generateNamespaceGlobals,
|
|
2804
|
+
getCdnBaseUrl,
|
|
2805
|
+
getImageRegistry,
|
|
2806
|
+
injectNamespaceGlobals,
|
|
2807
|
+
loadImage,
|
|
2808
|
+
mountEmbedded,
|
|
2809
|
+
mountIframe,
|
|
2810
|
+
parseImageConfig,
|
|
2811
|
+
parseImageSpec,
|
|
2812
|
+
parseManifest,
|
|
2813
|
+
reloadEmbedded,
|
|
2814
|
+
reloadIframe,
|
|
2815
|
+
removeNamespaceGlobals,
|
|
2816
|
+
resolveEntry,
|
|
2817
|
+
safeParseImageConfig,
|
|
2818
|
+
safeParseManifest,
|
|
2819
|
+
setCdnBaseUrl,
|
|
2820
|
+
vfsPlugin
|
|
2821
|
+
});
|
|
2822
|
+
//# sourceMappingURL=index.cjs.map
|