@probat/react 0.1.3 → 0.1.5
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 +147 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +147 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/utils/api.ts +211 -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,41 @@ 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
|
+
// Skip for Next.js - Turbopack statically analyzes import() calls and fails
|
|
354
|
+
const isBrowser = typeof window !== "undefined";
|
|
355
|
+
const isNextJS = isBrowser && (
|
|
356
|
+
(window as any).__NEXT_DATA__ !== undefined ||
|
|
357
|
+
(window as any).__NEXT_LOADED_PAGES__ !== undefined ||
|
|
358
|
+
typeof (globalThis as any).__NEXT_DATA__ !== "undefined"
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
// Only use dynamic import for non-Next.js environments (Vite, etc.)
|
|
362
|
+
if (isBrowser && !isNextJS) {
|
|
363
|
+
try {
|
|
364
|
+
const variantUrl = `/probat/${filePath}`;
|
|
365
|
+
// Use Function constructor to prevent Next.js/Turbopack from statically analyzing
|
|
366
|
+
// This makes the import completely dynamic and invisible to static analysis
|
|
367
|
+
const dynamicImportFunc = new Function('url', 'return import(url)');
|
|
368
|
+
const mod = await dynamicImportFunc(variantUrl);
|
|
369
|
+
const VariantComponent = (mod as any)?.default || mod;
|
|
370
|
+
if (VariantComponent && typeof VariantComponent === "function") {
|
|
371
|
+
console.log(`[PROBAT] ✅ Loaded variant via dynamic import (CSR): ${variantUrl}`);
|
|
372
|
+
return VariantComponent as React.ComponentType<any>;
|
|
373
|
+
}
|
|
374
|
+
} catch (dynamicImportError) {
|
|
375
|
+
// Dynamic import failed, fall through to fetch+babel
|
|
376
|
+
console.debug(`[PROBAT] Dynamic import failed, using fetch+babel:`, dynamicImportError);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Fallback: fetch + babel compilation (works for Next.js SSR and when dynamic import fails)
|
|
230
381
|
const localUrl = `/probat/${filePath}`;
|
|
231
382
|
try {
|
|
232
383
|
const localRes = await fetch(localUrl, {
|
|
@@ -316,6 +467,23 @@ export async function loadVariantComponent(
|
|
|
316
467
|
rawCode = rawCode.replace(/^import\s+.*\/@vite\/client.*$/gm, "");
|
|
317
468
|
rawCode = rawCode.replace(/import\.meta\.hot(?:\.[\w$]+)*/g, "undefined");
|
|
318
469
|
|
|
470
|
+
// Preprocess relative imports: convert to absolute paths that can be fetched
|
|
471
|
+
// This allows the require shim to fetch them later
|
|
472
|
+
const relativeImportMap = new Map<string, string>();
|
|
473
|
+
rawCode = rawCode.replace(
|
|
474
|
+
/^import\s+(\w+)\s+from\s+['"](\.\.?\/[^'"]+)['"];?\s*$/gm,
|
|
475
|
+
(match, importName, relativePath) => {
|
|
476
|
+
// Resolve relative path to absolute
|
|
477
|
+
const baseDir = filePath.substring(0, filePath.lastIndexOf('/'));
|
|
478
|
+
const resolvedPath = resolveRelativePath(relativePath, baseDir);
|
|
479
|
+
// Try to find the file with extension
|
|
480
|
+
const absolutePath = resolvedPath.startsWith('/') ? resolvedPath : '/' + resolvedPath;
|
|
481
|
+
relativeImportMap.set(importName, absolutePath);
|
|
482
|
+
// Replace with absolute path import - Babel will compile this to require()
|
|
483
|
+
return `import ${importName} from "${absolutePath}";`;
|
|
484
|
+
}
|
|
485
|
+
);
|
|
486
|
+
|
|
319
487
|
const compiled = Babel.transform(rawCode, {
|
|
320
488
|
presets: [
|
|
321
489
|
["react", { runtime: "classic" }],
|
|
@@ -326,8 +494,25 @@ export async function loadVariantComponent(
|
|
|
326
494
|
filename: filePath,
|
|
327
495
|
}).code;
|
|
328
496
|
|
|
497
|
+
// Pre-load relative imports if any
|
|
498
|
+
const relativeModules: Record<string, any> = {};
|
|
499
|
+
if (relativeImportMap.size > 0) {
|
|
500
|
+
for (const [importName, absolutePath] of relativeImportMap.entries()) {
|
|
501
|
+
try {
|
|
502
|
+
const moduleExports = await loadRelativeModule(absolutePath, filePath, repoFullName, baseRef);
|
|
503
|
+
relativeModules[absolutePath] = moduleExports.default || moduleExports;
|
|
504
|
+
} catch (err) {
|
|
505
|
+
console.warn(`[PROBAT] Failed to load relative import ${absolutePath}:`, err);
|
|
506
|
+
relativeModules[absolutePath] = null;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Build require shim that can resolve relative imports
|
|
512
|
+
const relativeModulesJson = JSON.stringify(relativeModules);
|
|
329
513
|
code = `
|
|
330
514
|
var __probatVariant = (function() {
|
|
515
|
+
var relativeModules = ${relativeModulesJson};
|
|
331
516
|
var require = function(name) {
|
|
332
517
|
if (name === "react" || name === "react/jsx-runtime") {
|
|
333
518
|
return window.React || globalThis.React;
|
|
@@ -338,6 +523,14 @@ export async function loadVariantComponent(
|
|
|
338
523
|
if (name === "react/jsx-runtime.js") {
|
|
339
524
|
return window.React || globalThis.React;
|
|
340
525
|
}
|
|
526
|
+
// Handle relative imports (now converted to absolute paths)
|
|
527
|
+
if (name.startsWith("/") && relativeModules.hasOwnProperty(name)) {
|
|
528
|
+
var mod = relativeModules[name];
|
|
529
|
+
if (mod === null) {
|
|
530
|
+
throw new Error("Failed to load module: " + name);
|
|
531
|
+
}
|
|
532
|
+
return mod;
|
|
533
|
+
}
|
|
341
534
|
throw new Error("Unsupported module: " + name);
|
|
342
535
|
};
|
|
343
536
|
var module = { exports: {} };
|
|
@@ -353,12 +546,12 @@ export async function loadVariantComponent(
|
|
|
353
546
|
}
|
|
354
547
|
|
|
355
548
|
if (!rawCodeFetched || code === "") {
|
|
356
|
-
|
|
549
|
+
const variantUrl = `${baseUrl.replace(/\/$/, "")}/variants/${filePath}`;
|
|
357
550
|
const serverRes = await fetch(variantUrl, {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
551
|
+
method: "GET",
|
|
552
|
+
headers: { Accept: "text/javascript" },
|
|
553
|
+
credentials: "include",
|
|
554
|
+
});
|
|
362
555
|
if (!serverRes.ok) {
|
|
363
556
|
throw new Error(`HTTP ${serverRes.status}`);
|
|
364
557
|
}
|