@@ -274,7 +274,7 @@ bool IsLikelyOptionalModule(const std::string& moduleName) {
274
274
return it2->second ->Get (isolate);
275
275
}
276
276
277
- if ([extension isEqualToString: @" mjs" ]) {
277
+ if ([extension isEqualToString: @" mjs" ] || [extension isEqualToString: @" js " ] ) {
278
278
moduleObj = this ->LoadModule (isolate, path, cacheKey);
279
279
} else if ([extension isEqualToString: @" json" ]) {
280
280
moduleObj = this ->LoadData (isolate, path);
@@ -311,15 +311,17 @@ bool IsLikelyOptionalModule(const std::string& moduleName) {
311
311
// Compile/load the JavaScript/ESM source
312
312
Local<Value> scriptValue = LoadScript (isolate, modulePath);
313
313
314
+ // Check if this is an ES module
314
315
bool isESM = modulePath.size () >= 4 && modulePath.compare (modulePath.size () - 4 , 4 , " .mjs" ) == 0 ;
316
+ std::shared_ptr<Caches> cache = Caches::Get (isolate);
315
317
316
- // Debug: Log ES module detection
318
+ // Debug: Log module type detection
317
319
printf (" LoadModule: Module path: %s, isESM: %s\n " , modulePath.c_str (), isESM ? " true" : " false" );
318
320
319
321
if (isESM) {
320
- // For ES modules the returned value is the module namespace object, not a
321
- // factory function. Wire it as the exports and skip CommonJS invocation.
322
+ // For ES modules, the returned value is the namespace object
322
323
printf (" LoadModule: Processing as ES module\n " );
324
+
323
325
if (!scriptValue->IsObject ()) {
324
326
printf (" LoadModule: ES module failed - scriptValue is not an object\n " );
325
327
throw NativeScriptException (isolate, " Failed to load ES module " + modulePath);
@@ -352,7 +354,15 @@ bool IsLikelyOptionalModule(const std::string& moduleName) {
352
354
}
353
355
}
354
356
355
- exportsObj = scriptValue.As <Object>();
357
+ // Handle exports differently for ES modules vs worker scripts
358
+ if (isESM) {
359
+ exportsObj = scriptValue.As <Object>();
360
+ } else {
361
+ // For worker scripts, create an empty exports object since they don't export anything
362
+ // They work through global scope (self.onmessage, etc.)
363
+ exportsObj = Object::New (isolate);
364
+ }
365
+
356
366
bool succ =
357
367
moduleObj->Set (context, tns::ToV8String (isolate, " exports" ), exportsObj).FromMaybe (false );
358
368
tns::Assert (succ, isolate);
@@ -522,6 +532,15 @@ ScriptOrigin origin(
522
532
523
533
// 3) Register for resolution callback
524
534
extern std::unordered_map<std::string, Global<Module>> g_moduleRegistry;
535
+
536
+ // Safe Global handle management: Clear any existing entry first
537
+ auto it = g_moduleRegistry.find (path);
538
+ if (it != g_moduleRegistry.end ()) {
539
+ // Clear the existing Global handle before replacing it
540
+ it->second .Reset ();
541
+ }
542
+
543
+ // Now safely set the new module handle
525
544
g_moduleRegistry[path].Reset (isolate, module );
526
545
527
546
// 4) Save cache if first time
@@ -559,30 +578,49 @@ ScriptOrigin origin(
559
578
560
579
// Handle the case where evaluation returns a Promise (for top-level await)
561
580
if (result->IsPromise ()) {
562
- printf (" LoadESModule: Module evaluation returned a Promise, waiting for resolution\n " );
581
+ printf (" LoadESModule: Module evaluation returned a Promise, processing...\n " );
582
+
583
+ // Use TryCatch to safely handle Promise operations
584
+ TryCatch promiseTc (isolate);
563
585
Local<Promise> promise = result.As <Promise>();
564
586
565
- // For worker context, we need to wait for the promise to resolve
566
- // This is important for modules that use top-level await or have async initialization
567
- std::shared_ptr<Caches> cache = Caches::Get (isolate);
568
- if (cache->isWorker ) {
569
- printf (" LoadESModule: In worker context, processing promise resolution\n " );
570
-
571
- // Run the microtask queue to allow the promise to resolve
572
- while (promise->State () == Promise::kPending ) {
573
- isolate->PerformMicrotaskCheckpoint ();
574
- // Add a small delay to prevent busy waiting
575
- usleep (1000 ); // 1ms
587
+ // Process microtasks to allow Promise resolution (for both worker and main contexts)
588
+ printf (" LoadESModule: Processing microtasks for Promise resolution\n " );
589
+
590
+ // Limited attempts to resolve the promise to avoid infinite loops
591
+ int maxAttempts = 100 ;
592
+ int attempts = 0 ;
593
+
594
+ while (attempts < maxAttempts && !promiseTc.HasCaught ()) {
595
+ isolate->PerformMicrotaskCheckpoint ();
596
+
597
+ // Check promise state safely
598
+ if (promiseTc.HasCaught ()) {
599
+ printf (" LoadESModule: Exception during Promise processing, breaking\n " );
600
+ break ;
576
601
}
577
602
578
- if (promise->State () == Promise::kRejected ) {
579
- printf (" LoadESModule: Promise was rejected\n " );
580
- Local<Value> reason = promise->Result ();
581
- isolate->ThrowException (reason);
582
- throw NativeScriptException (isolate, tcEval, " Module evaluation promise rejected" );
603
+ Promise::PromiseState state = promise->State ();
604
+
605
+ if (state != Promise::kPending ) {
606
+ if (state == Promise::kRejected ) {
607
+ printf (" LoadESModule: Promise was rejected\n " );
608
+ if (!promiseTc.HasCaught ()) {
609
+ Local<Value> reason = promise->Result ();
610
+ isolate->ThrowException (reason);
611
+ }
612
+ throw NativeScriptException (isolate, promiseTc, " Module evaluation promise rejected" );
613
+ }
614
+ printf (" LoadESModule: Promise resolved successfully\n " );
615
+ break ;
583
616
}
584
617
585
- printf (" LoadESModule: Promise resolved successfully\n " );
618
+ attempts++;
619
+ usleep (100 ); // 0.1ms delay
620
+ }
621
+
622
+ if (attempts >= maxAttempts) {
623
+ printf (" LoadESModule: Promise resolution timeout, continuing anyway\n " );
586
624
}
587
625
}
588
626
}
@@ -620,14 +658,17 @@ ScriptOrigin origin(
620
658
// in a function expression would turn those top-level keywords into syntax
621
659
// errors (e.g. `export *` → "Unexpected token '*'").
622
660
661
+ // Check if we're in a worker context
662
+ std::shared_ptr<Caches> cache = Caches::Get (isolate);
663
+ bool isWorkerContext = cache && cache->isWorker ;
664
+
623
665
if (path.size () >= 4 && path.compare (path.size () - 4 , 4 , " .mjs" ) == 0 ) {
624
666
// Read raw text without wrapping.
625
667
std::string sourceText = tns::ReadText (path);
626
668
627
669
// For ES modules in worker context, we need to provide access to global objects
628
670
// since ES modules run in their own scope
629
- std::shared_ptr<Caches> cache = Caches::Get (isolate);
630
- if (cache && cache->isWorker ) {
671
+ if (isWorkerContext) {
631
672
// Prepend global declarations to make worker globals available in ES module scope
632
673
std::string globalDeclarations = " const self = globalThis.self || globalThis;\n "
633
674
" const postMessage = globalThis.postMessage;\n "
@@ -642,6 +683,9 @@ ScriptOrigin origin(
642
683
return tns::ToV8String (isolate, sourceText);
643
684
}
644
685
686
+ // Worker .js files should use CommonJS wrapping like regular .js files
687
+ // This ensures proper runtime context and global object setup
688
+
645
689
return tns::ReadModule (isolate, path);
646
690
}
647
691
0 commit comments