@probat/react 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +149 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +149 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/utils/api.ts +205 -18
package/src/utils/api.ts
CHANGED
|
@@ -178,6 +178,7 @@ export async function fetchComponentExperimentConfig(
|
|
|
178
178
|
|
|
179
179
|
// Cache for variant component loads
|
|
180
180
|
const variantComponentCache = new Map<string, Promise<React.ComponentType<any> | null>>();
|
|
181
|
+
const moduleCache = new Map<string, any>(); // Cache for loaded modules (relative imports)
|
|
181
182
|
|
|
182
183
|
// Make React available globally for variant components
|
|
183
184
|
if (typeof window !== "undefined") {
|
|
@@ -185,6 +186,139 @@ if (typeof window !== "undefined") {
|
|
|
185
186
|
(window as any).React = (window as any).React || React;
|
|
186
187
|
}
|
|
187
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Resolve relative import path to absolute path
|
|
191
|
+
*/
|
|
192
|
+
function resolveRelativePath(relativePath: string, basePath: string): string {
|
|
193
|
+
// Remove file extension if present
|
|
194
|
+
const baseDir = basePath.substring(0, basePath.lastIndexOf('/'));
|
|
195
|
+
const parts = baseDir.split('/').filter(Boolean);
|
|
196
|
+
const relativeParts = relativePath.split('/').filter(Boolean);
|
|
197
|
+
|
|
198
|
+
for (const part of relativeParts) {
|
|
199
|
+
if (part === '..') {
|
|
200
|
+
parts.pop();
|
|
201
|
+
} else if (part !== '.') {
|
|
202
|
+
parts.push(part);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return '/' + parts.join('/');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Fetch and compile a module from absolute path
|
|
211
|
+
*/
|
|
212
|
+
async function loadRelativeModule(
|
|
213
|
+
absolutePath: string,
|
|
214
|
+
baseFilePath: string,
|
|
215
|
+
repoFullName?: string,
|
|
216
|
+
baseRef?: string
|
|
217
|
+
): Promise<any> {
|
|
218
|
+
const cacheKey = `${baseFilePath}:${absolutePath}`;
|
|
219
|
+
if (moduleCache.has(cacheKey)) {
|
|
220
|
+
return moduleCache.get(cacheKey);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Try to determine file extension
|
|
224
|
+
const extensions = ['.jsx', '.tsx', '.js', '.ts'];
|
|
225
|
+
let moduleCode: string | null = null;
|
|
226
|
+
let modulePath: string | null = null;
|
|
227
|
+
|
|
228
|
+
// Try each extension
|
|
229
|
+
for (const ext of extensions) {
|
|
230
|
+
const testPath = absolutePath + (absolutePath.includes('.') ? '' : ext);
|
|
231
|
+
|
|
232
|
+
// Try local first
|
|
233
|
+
try {
|
|
234
|
+
const localRes = await fetch(testPath, {
|
|
235
|
+
method: "GET",
|
|
236
|
+
headers: { Accept: "text/plain" },
|
|
237
|
+
});
|
|
238
|
+
if (localRes.ok) {
|
|
239
|
+
moduleCode = await localRes.text();
|
|
240
|
+
modulePath = testPath;
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
} catch {
|
|
244
|
+
// Continue to next extension or GitHub
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Try GitHub if local failed
|
|
248
|
+
if (!moduleCode && repoFullName) {
|
|
249
|
+
try {
|
|
250
|
+
const githubPath = testPath.startsWith('/') ? testPath.substring(1) : testPath;
|
|
251
|
+
const githubUrl = `https://raw.githubusercontent.com/${repoFullName}/${baseRef || 'main'}/${githubPath}`;
|
|
252
|
+
const res = await fetch(githubUrl, { method: "GET", headers: { Accept: "text/plain" } });
|
|
253
|
+
if (res.ok) {
|
|
254
|
+
moduleCode = await res.text();
|
|
255
|
+
modulePath = testPath;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
} catch {
|
|
259
|
+
// Continue
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (!moduleCode || !modulePath) {
|
|
265
|
+
throw new Error(`Could not resolve module: ${absolutePath} from ${baseFilePath}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Compile the module
|
|
269
|
+
let Babel: any;
|
|
270
|
+
if (typeof window !== "undefined" && (window as any).Babel) {
|
|
271
|
+
Babel = (window as any).Babel;
|
|
272
|
+
} else {
|
|
273
|
+
try {
|
|
274
|
+
// @ts-ignore
|
|
275
|
+
const babelModule = await import("@babel/standalone");
|
|
276
|
+
Babel = babelModule.default || babelModule;
|
|
277
|
+
} catch {
|
|
278
|
+
throw new Error("Babel not available for compiling relative import");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Preprocess module code
|
|
283
|
+
let processedCode = moduleCode;
|
|
284
|
+
processedCode = processedCode.replace(/^import\s+['"].*\.css['"];?\s*$/gm, "");
|
|
285
|
+
processedCode = processedCode.replace(/^import\s+React\s+from\s+['"]react['"];?\s*$/m, "const React = window.React || globalThis.React;");
|
|
286
|
+
processedCode = processedCode.replace(/import\.meta\.env\.[\w$]+/g, "undefined");
|
|
287
|
+
processedCode = processedCode.replace(/\bimport\.meta\b/g, "({})");
|
|
288
|
+
|
|
289
|
+
const isTSX = modulePath.endsWith(".tsx");
|
|
290
|
+
const compiled = Babel.transform(processedCode, {
|
|
291
|
+
presets: [
|
|
292
|
+
["react", { runtime: "classic" }],
|
|
293
|
+
["typescript", { allExtensions: true, isTSX }],
|
|
294
|
+
],
|
|
295
|
+
plugins: [["transform-modules-commonjs", { allowTopLevelThis: true }]],
|
|
296
|
+
sourceType: "module",
|
|
297
|
+
filename: modulePath,
|
|
298
|
+
}).code;
|
|
299
|
+
|
|
300
|
+
// Execute compiled module
|
|
301
|
+
const moduleCodeWrapper = `
|
|
302
|
+
var require = function(name) {
|
|
303
|
+
if (name === "react" || name === "react/jsx-runtime") {
|
|
304
|
+
return window.React || globalThis.React;
|
|
305
|
+
}
|
|
306
|
+
if (name.startsWith("/@vite") || name.includes("@vite/client")) {
|
|
307
|
+
return {};
|
|
308
|
+
}
|
|
309
|
+
throw new Error("Unsupported module in relative import: " + name);
|
|
310
|
+
};
|
|
311
|
+
var module = { exports: {} };
|
|
312
|
+
var exports = module.exports;
|
|
313
|
+
${compiled}
|
|
314
|
+
return module.exports;
|
|
315
|
+
`;
|
|
316
|
+
|
|
317
|
+
const moduleExports = new Function(moduleCodeWrapper)();
|
|
318
|
+
moduleCache.set(cacheKey, moduleExports);
|
|
319
|
+
return moduleExports;
|
|
320
|
+
}
|
|
321
|
+
|
|
188
322
|
export async function loadVariantComponent(
|
|
189
323
|
baseUrl: string,
|
|
190
324
|
proposalId: string,
|
|
@@ -209,24 +343,35 @@ export async function loadVariantComponent(
|
|
|
209
343
|
const loadPromise = (async () => {
|
|
210
344
|
try {
|
|
211
345
|
// ============================================
|
|
212
|
-
//
|
|
346
|
+
// Hybrid approach: Dynamic import for CSR (Vite), fetch+babel for SSR (Next.js)
|
|
213
347
|
// ============================================
|
|
214
|
-
try {
|
|
215
|
-
const variantUrl = `/probat/${filePath}`;
|
|
216
|
-
const mod = await import(/* @vite-ignore */ variantUrl);
|
|
217
|
-
const VariantComponent = (mod as any)?.default || mod;
|
|
218
|
-
if (VariantComponent && typeof VariantComponent === "function") {
|
|
219
|
-
return VariantComponent as React.ComponentType<any>;
|
|
220
|
-
}
|
|
221
|
-
} catch {
|
|
222
|
-
// Fall through to legacy path
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Legacy path (fetch + babel + eval) retained for non-Vite envs
|
|
226
348
|
let code: string = "";
|
|
227
349
|
let rawCode: string = "";
|
|
228
350
|
let rawCodeFetched = false;
|
|
229
351
|
|
|
352
|
+
// Try dynamic import first (works great for Vite/CSR, resolves relative imports automatically)
|
|
353
|
+
// Only attempt in browser and not during Next.js server-side compilation
|
|
354
|
+
const isBrowser = typeof window !== "undefined";
|
|
355
|
+
const isNextJSServer = typeof window === "undefined" ||
|
|
356
|
+
(typeof (globalThis as any).process !== "undefined" && (globalThis as any).process.env?.NEXT_RUNTIME === "nodejs");
|
|
357
|
+
|
|
358
|
+
if (isBrowser && !isNextJSServer) {
|
|
359
|
+
try {
|
|
360
|
+
const variantUrl = `/probat/${filePath}`;
|
|
361
|
+
// Use dynamic import - Vite can resolve relative imports automatically
|
|
362
|
+
const mod = await import(/* @vite-ignore */ variantUrl);
|
|
363
|
+
const VariantComponent = (mod as any)?.default || mod;
|
|
364
|
+
if (VariantComponent && typeof VariantComponent === "function") {
|
|
365
|
+
console.log(`[PROBAT] ✅ Loaded variant via dynamic import (CSR): ${variantUrl}`);
|
|
366
|
+
return VariantComponent as React.ComponentType<any>;
|
|
367
|
+
}
|
|
368
|
+
} catch (dynamicImportError) {
|
|
369
|
+
// Dynamic import failed (might be Next.js or other issue), fall through to fetch+babel
|
|
370
|
+
console.debug(`[PROBAT] Dynamic import failed, using fetch+babel:`, dynamicImportError);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Fallback: fetch + babel compilation (works for Next.js SSR and when dynamic import fails)
|
|
230
375
|
const localUrl = `/probat/${filePath}`;
|
|
231
376
|
try {
|
|
232
377
|
const localRes = await fetch(localUrl, {
|
|
@@ -316,6 +461,23 @@ export async function loadVariantComponent(
|
|
|
316
461
|
rawCode = rawCode.replace(/^import\s+.*\/@vite\/client.*$/gm, "");
|
|
317
462
|
rawCode = rawCode.replace(/import\.meta\.hot(?:\.[\w$]+)*/g, "undefined");
|
|
318
463
|
|
|
464
|
+
// Preprocess relative imports: convert to absolute paths that can be fetched
|
|
465
|
+
// This allows the require shim to fetch them later
|
|
466
|
+
const relativeImportMap = new Map<string, string>();
|
|
467
|
+
rawCode = rawCode.replace(
|
|
468
|
+
/^import\s+(\w+)\s+from\s+['"](\.\.?\/[^'"]+)['"];?\s*$/gm,
|
|
469
|
+
(match, importName, relativePath) => {
|
|
470
|
+
// Resolve relative path to absolute
|
|
471
|
+
const baseDir = filePath.substring(0, filePath.lastIndexOf('/'));
|
|
472
|
+
const resolvedPath = resolveRelativePath(relativePath, baseDir);
|
|
473
|
+
// Try to find the file with extension
|
|
474
|
+
const absolutePath = resolvedPath.startsWith('/') ? resolvedPath : '/' + resolvedPath;
|
|
475
|
+
relativeImportMap.set(importName, absolutePath);
|
|
476
|
+
// Replace with absolute path import - Babel will compile this to require()
|
|
477
|
+
return `import ${importName} from "${absolutePath}";`;
|
|
478
|
+
}
|
|
479
|
+
);
|
|
480
|
+
|
|
319
481
|
const compiled = Babel.transform(rawCode, {
|
|
320
482
|
presets: [
|
|
321
483
|
["react", { runtime: "classic" }],
|
|
@@ -326,8 +488,25 @@ export async function loadVariantComponent(
|
|
|
326
488
|
filename: filePath,
|
|
327
489
|
}).code;
|
|
328
490
|
|
|
491
|
+
// Pre-load relative imports if any
|
|
492
|
+
const relativeModules: Record<string, any> = {};
|
|
493
|
+
if (relativeImportMap.size > 0) {
|
|
494
|
+
for (const [importName, absolutePath] of relativeImportMap.entries()) {
|
|
495
|
+
try {
|
|
496
|
+
const moduleExports = await loadRelativeModule(absolutePath, filePath, repoFullName, baseRef);
|
|
497
|
+
relativeModules[absolutePath] = moduleExports.default || moduleExports;
|
|
498
|
+
} catch (err) {
|
|
499
|
+
console.warn(`[PROBAT] Failed to load relative import ${absolutePath}:`, err);
|
|
500
|
+
relativeModules[absolutePath] = null;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Build require shim that can resolve relative imports
|
|
506
|
+
const relativeModulesJson = JSON.stringify(relativeModules);
|
|
329
507
|
code = `
|
|
330
508
|
var __probatVariant = (function() {
|
|
509
|
+
var relativeModules = ${relativeModulesJson};
|
|
331
510
|
var require = function(name) {
|
|
332
511
|
if (name === "react" || name === "react/jsx-runtime") {
|
|
333
512
|
return window.React || globalThis.React;
|
|
@@ -338,6 +517,14 @@ export async function loadVariantComponent(
|
|
|
338
517
|
if (name === "react/jsx-runtime.js") {
|
|
339
518
|
return window.React || globalThis.React;
|
|
340
519
|
}
|
|
520
|
+
// Handle relative imports (now converted to absolute paths)
|
|
521
|
+
if (name.startsWith("/") && relativeModules.hasOwnProperty(name)) {
|
|
522
|
+
var mod = relativeModules[name];
|
|
523
|
+
if (mod === null) {
|
|
524
|
+
throw new Error("Failed to load module: " + name);
|
|
525
|
+
}
|
|
526
|
+
return mod;
|
|
527
|
+
}
|
|
341
528
|
throw new Error("Unsupported module: " + name);
|
|
342
529
|
};
|
|
343
530
|
var module = { exports: {} };
|
|
@@ -353,12 +540,12 @@ export async function loadVariantComponent(
|
|
|
353
540
|
}
|
|
354
541
|
|
|
355
542
|
if (!rawCodeFetched || code === "") {
|
|
356
|
-
|
|
543
|
+
const variantUrl = `${baseUrl.replace(/\/$/, "")}/variants/${filePath}`;
|
|
357
544
|
const serverRes = await fetch(variantUrl, {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
545
|
+
method: "GET",
|
|
546
|
+
headers: { Accept: "text/javascript" },
|
|
547
|
+
credentials: "include",
|
|
548
|
+
});
|
|
362
549
|
if (!serverRes.ok) {
|
|
363
550
|
throw new Error(`HTTP ${serverRes.status}`);
|
|
364
551
|
}
|