6
6
#include < string>
7
7
#include " Caches.h"
8
8
#include " Helpers.h"
9
+ #include " ModuleInternalCallbacks.h" // for ResolveModuleCallback
9
10
#include " NativeScriptException.h"
10
11
#include " RuntimeConfig.h"
11
12
115
116
116
117
const char * path1 = [fullPath fileSystemRepresentation ];
117
118
const char * path2 =
118
- [[fullPath stringByAppendingPathExtension: @" js " ] fileSystemRepresentation ];
119
+ [[fullPath stringByAppendingPathExtension: @" mjs " ] fileSystemRepresentation ];
119
120
120
121
if (!tns::Exists (path1) && !tns::Exists (path2)) {
121
122
fullPath = [tnsModulesPath stringByAppendingPathComponent: @" tns-core-modules" ];
183
184
return it2->second ->Get (isolate);
184
185
}
185
186
186
- if ([extension isEqualToString: @" js " ]) {
187
+ if ([extension isEqualToString: @" mjs " ]) {
187
188
moduleObj = this ->LoadModule (isolate, path, cacheKey);
188
189
} else if ([extension isEqualToString: @" json" ]) {
189
190
moduleObj = this ->LoadData (isolate, path);
217
218
std::make_shared<Persistent<Object>>(isolate, moduleObj);
218
219
TempModule tempModule (this , modulePath, cacheKey, poModuleObj);
219
220
220
- Local<Script> script = LoadScript (isolate, modulePath);
221
+ Local<Value> scriptValue = LoadScript (isolate, modulePath);
222
+
223
+ if (!scriptValue->IsFunction ()) {
224
+ throw NativeScriptException (isolate,
225
+ " Expected module factory to be a function for " + modulePath);
226
+ }
227
+ v8::Local<v8::Function> moduleFunc = scriptValue.As <v8::Function>();
221
228
222
- Local<v8::Function> moduleFunc;
223
229
{
224
230
TryCatch tc (isolate);
225
- moduleFunc = script->Run (context).ToLocalChecked ().As <v8::Function>();
231
+ // moduleFunc = script->Run(context).ToLocalChecked().As<v8::Function>();
226
232
if (tc.HasCaught ()) {
227
233
throw NativeScriptException (isolate, tc, " Error running script " + modulePath);
228
234
}
282
288
return json;
283
289
}
284
290
285
- Local<Script> ModuleInternal::LoadScript (Isolate* isolate, const std::string& path) {
286
- Local<Context> context = isolate->GetCurrentContext ();
287
- std::string baseOrigin = tns::ReplaceAll (path, RuntimeConfig.BaseDir , " " );
288
- std::string fullRequiredModulePathWithSchema = " file://" + baseOrigin;
289
- ScriptOrigin origin (isolate, tns::ToV8String (isolate, fullRequiredModulePathWithSchema));
290
- Local<v8::String> scriptText = WrapModuleContent (isolate, path);
291
- ScriptCompiler::CachedData* cacheData = LoadScriptCache (path);
292
- ScriptCompiler::Source source (scriptText, origin, cacheData);
293
-
294
- ScriptCompiler::CompileOptions options = ScriptCompiler::kNoCompileOptions ;
295
-
296
- if (cacheData != nullptr ) {
297
- options = ScriptCompiler::kConsumeCodeCache ;
291
+ Local<Value> ModuleInternal::LoadScript (Isolate* isolate, const std::string& path) {
292
+ // Simple dispatch on extension:
293
+ if (path.size () >= 4 && path.compare (path.size () - 4 , 4 , " .mjs" ) == 0 ) {
294
+ return ModuleInternal::LoadESModule (isolate, path);
295
+ } else {
296
+ Local<Script> script = ModuleInternal::LoadClassicScript (isolate, path);
297
+ // run it and return the value
298
+ return script->Run (isolate->GetCurrentContext ()).ToLocalChecked ();
298
299
}
300
+ }
301
+
302
+ Local<Script> ModuleInternal::LoadClassicScript (Isolate* isolate, const std::string& path) {
303
+ auto context = isolate->GetCurrentContext ();
304
+ // build URL
305
+ std::string base = ReplaceAll (path, RuntimeConfig.BaseDir , " " );
306
+ std::string url = " file://" + base;
307
+
308
+ // wrap & cache lookup
309
+ Local<v8::String> sourceText = ModuleInternal::WrapModuleContent (isolate, path);
310
+ auto * cacheData = ModuleInternal::LoadScriptCache (path);
311
+
312
+ // note: is_module=false here
313
+ ScriptOrigin origin (
314
+ isolate,
315
+ v8::String::NewFromUtf8 (isolate, url.c_str (), NewStringType::kNormal ).ToLocalChecked (),
316
+ 0 , // line offset
317
+ 0 , // column offset
318
+ false , // shared_cross_origin
319
+ -1 , // script_id
320
+ Local<Value>(),
321
+ false , // is_opaque
322
+ false , // is_wasm
323
+ false // is_module
324
+ );
325
+ ScriptCompiler::Source source (sourceText, origin, cacheData);
326
+
327
+ auto opts = cacheData ? ScriptCompiler::kConsumeCodeCache : ScriptCompiler::kNoCompileOptions ;
299
328
300
- Local<Script> script;
301
329
TryCatch tc (isolate);
302
- bool success = ScriptCompiler::Compile (context, &source, options). ToLocal (& script) ;
303
- if (!success || tc.HasCaught ()) {
304
- throw NativeScriptException (isolate, tc, " Cannot compile " + path);
330
+ Local<Script> script;
331
+ if (!ScriptCompiler::Compile (context, &source, opts). ToLocal (&script) || tc.HasCaught ()) {
332
+ throw NativeScriptException (isolate, tc, " Cannot compile script " + path);
305
333
}
306
334
307
335
if (cacheData == nullptr ) {
308
- SaveScriptCache (script, path);
336
+ ModuleInternal:: SaveScriptCache (script, path);
309
337
}
310
338
311
339
return script;
312
340
}
313
341
342
+ Local<Value> ModuleInternal::LoadESModule (Isolate* isolate, const std::string& path) {
343
+ auto context = isolate->GetCurrentContext ();
344
+
345
+ // 1) Prepare URL & source
346
+ std::string base = ReplaceAll (path, RuntimeConfig.BaseDir , " " );
347
+ std::string url = " file://" + base;
348
+ v8::Local<v8::String> sourceText = ModuleInternal::WrapModuleContent (isolate, path);
349
+ auto * cacheData = ModuleInternal::LoadScriptCache (path);
350
+
351
+ ScriptOrigin origin (
352
+ isolate,
353
+ v8::String::NewFromUtf8 (isolate, url.c_str (), NewStringType::kNormal ).ToLocalChecked (), 0 , 0 ,
354
+ false , -1 , Local<Value>(), false , false ,
355
+ true // ← is_module
356
+ );
357
+ ScriptCompiler::Source source (sourceText, origin, cacheData);
358
+
359
+ // 2) Compile with its own TryCatch
360
+ Local<Module> module ;
361
+ {
362
+ TryCatch tcCompile (isolate);
363
+ MaybeLocal<Module> maybeMod = ScriptCompiler::CompileModule (
364
+ isolate, &source,
365
+ cacheData ? ScriptCompiler::kConsumeCodeCache : ScriptCompiler::kNoCompileOptions );
366
+
367
+ if (!maybeMod.ToLocal (&module )) {
368
+ // V8 threw a syntax error or similar
369
+ throw NativeScriptException (isolate, tcCompile, " Cannot compile ES module " + path);
370
+ }
371
+ }
372
+
373
+ // 3) Register for resolution callback
374
+ extern std::unordered_map<std::string, Global<Module>> g_moduleRegistry;
375
+ g_moduleRegistry[path].Reset (isolate, module );
376
+
377
+ // 4) Save cache if first time
378
+ if (cacheData == nullptr ) {
379
+ Local<UnboundModuleScript> unbound = module ->GetUnboundModuleScript ();
380
+ auto * generatedCache = ScriptCompiler::CreateCodeCache (unbound);
381
+ ModuleInternal::SaveScriptCache (generatedCache, path);
382
+ }
383
+
384
+ // 5) Instantiate (link) with its own TryCatch
385
+ {
386
+ TryCatch tcLink (isolate);
387
+ bool linked = module ->InstantiateModule (context, &ResolveModuleCallback).FromMaybe (false );
388
+
389
+ if (!linked) {
390
+ if (tcLink.HasCaught ()) {
391
+ throw NativeScriptException (isolate, tcLink, " Cannot instantiate module " + path);
392
+ } else {
393
+ // V8 gave no exception object—throw plain text
394
+ throw NativeScriptException (isolate, " Cannot instantiate module " + path);
395
+ }
396
+ }
397
+ }
398
+
399
+ // 6) Evaluate with its own TryCatch
400
+ Local<Value> result;
401
+ {
402
+ TryCatch tcEval (isolate);
403
+ if (!module ->Evaluate (context).ToLocal (&result)) {
404
+ throw NativeScriptException (isolate, tcEval, " Cannot evaluate module " + path);
405
+ }
406
+ }
407
+
408
+ // 7) Return the namespace
409
+ return module ->GetModuleNamespace ();
410
+ }
411
+
314
412
MaybeLocal<Value> ModuleInternal::RunScriptString (Isolate* isolate, Local<Context> context,
315
413
const std::string scriptString) {
316
414
ScriptCompiler::CompileOptions options = ScriptCompiler::kNoCompileOptions ;
332
430
this ->RunScriptString (isolate, context, script);
333
431
}
334
432
335
- Local<v8::String> ModuleInternal::WrapModuleContent (Isolate* isolate, const std::string& path) {
433
+ v8::Local<v8::String> ModuleInternal::WrapModuleContent (v8::Isolate* isolate,
434
+ const std::string& path) {
435
+ // For classical scripts we wrap the source into the CommonJS factory function
436
+ // but for ES modules (".mjs") we must leave the source intact so that the
437
+ // V8 parser can recognise the "export"/"import" syntax. Wrapping an ES module
438
+ // in a function expression would turn those top-level keywords into syntax
439
+ // errors (e.g. `export *` → "Unexpected token '*'").
440
+
441
+ if (path.size () >= 4 && path.compare (path.size () - 4 , 4 , " .mjs" ) == 0 ) {
442
+ // Read raw text without wrapping.
443
+ std::string sourceText = tns::ReadText (path);
444
+ return tns::ToV8String (isolate, sourceText);
445
+ }
446
+
336
447
return tns::ReadModule (isolate, path);
337
448
}
338
449
348
459
BOOL exists = [fileManager fileExistsAtPath: fullPath isDirectory: &isDirectory];
349
460
350
461
if (exists == YES && isDirectory == YES ) {
351
- NSString * jsFile = [fullPath stringByAppendingPathExtension: @" js " ];
462
+ NSString * jsFile = [fullPath stringByAppendingPathExtension: @" mjs " ];
352
463
BOOL isDir;
353
464
if ([fileManager fileExistsAtPath: jsFile isDirectory: &isDir] && isDir == NO ) {
354
465
return [jsFile UTF8String ];
355
466
}
356
467
}
357
468
358
469
if (exists == NO ) {
359
- fullPath = [fullPath stringByAppendingPathExtension: @" js " ];
470
+ fullPath = [fullPath stringByAppendingPathExtension: @" mjs " ];
360
471
exists = [fileManager fileExistsAtPath: fullPath isDirectory: &isDirectory];
361
472
}
362
473
@@ -387,9 +498,9 @@ throw NativeScriptException("Unable to locate main entry in " +
387
498
}
388
499
389
500
if (exists == NO ) {
390
- fullPath = [fullPath stringByAppendingPathExtension: @" js " ];
501
+ fullPath = [fullPath stringByAppendingPathExtension: @" mjs " ];
391
502
} else {
392
- fullPath = [fullPath stringByAppendingPathComponent: @" index.js " ];
503
+ fullPath = [fullPath stringByAppendingPathComponent: @" index.mjs " ];
393
504
}
394
505
395
506
exists = [fileManager fileExistsAtPath: fullPath isDirectory: &isDirectory];
@@ -448,7 +559,7 @@ throw NativeScriptException("Unable to locate main entry in " +
448
559
}
449
560
450
561
long length = 0 ;
451
- std::string cachePath = GetCacheFileName (path + " .cache" );
562
+ std::string cachePath = ModuleInternal:: GetCacheFileName (path + " .cache" );
452
563
453
564
struct stat result;
454
565
if (stat (cachePath.c_str (), &result) == 0 ) {
@@ -474,6 +585,33 @@ throw NativeScriptException("Unable to locate main entry in " +
474
585
isNew ? ScriptCompiler::CachedData::BufferOwned : ScriptCompiler::CachedData::BufferNotOwned);
475
586
}
476
587
588
+ void ModuleInternal::SaveScriptCache (const ScriptCompiler::CachedData* cache,
589
+ const std::string& path) {
590
+ std::string cachePath = ModuleInternal::GetCacheFileName (path + " .cache" );
591
+
592
+ // std::ofstream ofs(cachePath, std::ios::binary);
593
+ // if (!ofs) return; // or throw
594
+
595
+ // ofs.write(reinterpret_cast<const char*>(cache->data),
596
+ // cache->length);
597
+ // ofs.close();
598
+
599
+ int length = cache->length ;
600
+ tns::WriteBinary (cachePath, cache->data , length);
601
+ delete cache;
602
+
603
+ // make sure cache and js file have the same modification date
604
+ struct stat result;
605
+ struct utimbuf new_times;
606
+ new_times.actime = time (nullptr );
607
+ new_times.modtime = time (nullptr );
608
+ if (stat (path.c_str (), &result) == 0 ) {
609
+ auto jsLastModifiedTime = result.st_mtime ;
610
+ new_times.modtime = jsLastModifiedTime;
611
+ }
612
+ utime (cachePath.c_str (), &new_times);
613
+ }
614
+
477
615
void ModuleInternal::SaveScriptCache (const Local<Script> script, const std::string& path) {
478
616
if (RuntimeConfig.IsDebug ) {
479
617
return ;
@@ -484,7 +622,7 @@ throw NativeScriptException("Unable to locate main entry in " +
484
622
ScriptCompiler::CachedData* cachedData = ScriptCompiler::CreateCodeCache (unboundScript);
485
623
486
624
int length = cachedData->length ;
487
- std::string cachePath = GetCacheFileName (path + " .cache" );
625
+ std::string cachePath = ModuleInternal:: GetCacheFileName (path + " .cache" );
488
626
tns::WriteBinary (cachePath, cachedData->data , length);
489
627
delete cachedData;
490
628
0 commit comments