Skip to content

Commit 5add9e3

Browse files
committed
feat: worker improvements
1 parent 828d022 commit 5add9e3

File tree

1 file changed

+69
-25
lines changed

1 file changed

+69
-25
lines changed

NativeScript/runtime/ModuleInternal.mm

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ bool IsLikelyOptionalModule(const std::string& moduleName) {
274274
return it2->second->Get(isolate);
275275
}
276276

277-
if ([extension isEqualToString:@"mjs"]) {
277+
if ([extension isEqualToString:@"mjs"] || [extension isEqualToString:@"js"]) {
278278
moduleObj = this->LoadModule(isolate, path, cacheKey);
279279
} else if ([extension isEqualToString:@"json"]) {
280280
moduleObj = this->LoadData(isolate, path);
@@ -311,15 +311,17 @@ bool IsLikelyOptionalModule(const std::string& moduleName) {
311311
// Compile/load the JavaScript/ESM source
312312
Local<Value> scriptValue = LoadScript(isolate, modulePath);
313313

314+
// Check if this is an ES module
314315
bool isESM = modulePath.size() >= 4 && modulePath.compare(modulePath.size() - 4, 4, ".mjs") == 0;
316+
std::shared_ptr<Caches> cache = Caches::Get(isolate);
315317

316-
// Debug: Log ES module detection
318+
// Debug: Log module type detection
317319
printf("LoadModule: Module path: %s, isESM: %s\n", modulePath.c_str(), isESM ? "true" : "false");
318320

319321
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
322323
printf("LoadModule: Processing as ES module\n");
324+
323325
if (!scriptValue->IsObject()) {
324326
printf("LoadModule: ES module failed - scriptValue is not an object\n");
325327
throw NativeScriptException(isolate, "Failed to load ES module " + modulePath);
@@ -352,7 +354,15 @@ bool IsLikelyOptionalModule(const std::string& moduleName) {
352354
}
353355
}
354356

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+
356366
bool succ =
357367
moduleObj->Set(context, tns::ToV8String(isolate, "exports"), exportsObj).FromMaybe(false);
358368
tns::Assert(succ, isolate);
@@ -522,6 +532,15 @@ ScriptOrigin origin(
522532

523533
// 3) Register for resolution callback
524534
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
525544
g_moduleRegistry[path].Reset(isolate, module);
526545

527546
// 4) Save cache if first time
@@ -559,30 +578,49 @@ ScriptOrigin origin(
559578

560579
// Handle the case where evaluation returns a Promise (for top-level await)
561580
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);
563585
Local<Promise> promise = result.As<Promise>();
564586

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;
576601
}
577602

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;
583616
}
584617

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");
586624
}
587625
}
588626
}
@@ -620,14 +658,17 @@ ScriptOrigin origin(
620658
// in a function expression would turn those top-level keywords into syntax
621659
// errors (e.g. `export *` → "Unexpected token '*'").
622660

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+
623665
if (path.size() >= 4 && path.compare(path.size() - 4, 4, ".mjs") == 0) {
624666
// Read raw text without wrapping.
625667
std::string sourceText = tns::ReadText(path);
626668

627669
// For ES modules in worker context, we need to provide access to global objects
628670
// 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) {
631672
// Prepend global declarations to make worker globals available in ES module scope
632673
std::string globalDeclarations = "const self = globalThis.self || globalThis;\n"
633674
"const postMessage = globalThis.postMessage;\n"
@@ -642,6 +683,9 @@ ScriptOrigin origin(
642683
return tns::ToV8String(isolate, sourceText);
643684
}
644685

686+
// Worker .js files should use CommonJS wrapping like regular .js files
687+
// This ensures proper runtime context and global object setup
688+
645689
return tns::ReadModule(isolate, path);
646690
}
647691

0 commit comments

Comments
 (0)