Skip to content

Commit e72977a

Browse files
authored
feat: ES modules (ESM) support with conditional esm or commonjs consumption + better error handling (#276)
1 parent d289232 commit e72977a

32 files changed

+5139
-1935
lines changed

NativeScript/NativeScript.mm

Lines changed: 86 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1-
#include <Foundation/Foundation.h>
21
#include "NativeScript.h"
2+
#include <Foundation/Foundation.h>
33
#include "inspector/JsV8InspectorClient.h"
44
#include "runtime/Console.h"
5-
#include "runtime/RuntimeConfig.h"
65
#include "runtime/Helpers.h"
76
#include "runtime/Runtime.h"
7+
#include "runtime/RuntimeConfig.h"
88
#include "runtime/Tasks.h"
99

1010
using namespace v8;
1111
using namespace tns;
1212

13+
namespace tns {
14+
// External flag from Runtime.mm to track JavaScript errors
15+
extern bool jsErrorOccurred;
16+
}
17+
1318
@implementation Config
1419

1520
@synthesize BaseDir;
@@ -23,99 +28,110 @@ @implementation NativeScript
2328

2429
extern char defaultStartOfMetadataSection __asm("section$start$__DATA$__TNSMetadata");
2530

26-
- (void)runScriptString: (NSString*) script runLoop: (BOOL) runLoop {
31+
- (void)runScriptString:(NSString*)script runLoop:(BOOL)runLoop {
32+
std::string cppString = std::string([script UTF8String]);
33+
runtime_->RunScript(cppString);
2734

28-
std::string cppString = std::string([script UTF8String]);
29-
runtime_->RunScript(cppString);
30-
31-
if (runLoop) {
32-
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
33-
}
34-
35-
36-
tns::Tasks::Drain();
35+
if (runLoop) {
36+
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
37+
}
3738

39+
tns::Tasks::Drain();
3840
}
3941

4042
std::unique_ptr<Runtime> runtime_;
4143

4244
- (void)runMainApplication {
43-
runtime_->RunMainScript();
45+
runtime_->RunMainScript();
4446

45-
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
46-
tns::Tasks::Drain();
47+
// In debug mode, if JavaScript errors occurred, keep the app alive indefinitely
48+
// This prevents iOS from terminating the app and allows hot-reload to work
49+
if (RuntimeConfig.IsDebug && jsErrorOccurred) {
50+
// Log(@"Debug mode - JavaScript errors detected, entering infinite run loop to prevent app termination");
51+
52+
while (true) {
53+
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true);
54+
tns::Tasks::Drain();
55+
}
56+
// Note: This line is never reached in debug mode with errors
57+
}
58+
59+
// Normal execution path (no errors or release mode)
60+
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
61+
tns::Tasks::Drain();
4762
}
4863

4964
- (bool)liveSync {
50-
if (runtime_ == nullptr) {
51-
return false;
52-
}
65+
if (runtime_ == nullptr) {
66+
return false;
67+
}
5368

54-
Isolate* isolate = runtime_->GetIsolate();
55-
return tns::LiveSync(isolate);
69+
Isolate* isolate = runtime_->GetIsolate();
70+
return tns::LiveSync(isolate);
5671
}
5772

5873
- (void)shutdownRuntime {
59-
if (RuntimeConfig.IsDebug) {
60-
Console::DetachInspectorClient();
61-
}
62-
tns::Tasks::ClearTasks();
63-
if (runtime_ != nullptr) {
64-
runtime_ = nullptr;
65-
}
74+
if (RuntimeConfig.IsDebug) {
75+
Console::DetachInspectorClient();
76+
}
77+
tns::Tasks::ClearTasks();
78+
if (runtime_ != nullptr) {
79+
runtime_ = nullptr;
80+
}
6681
}
6782

6883
- (instancetype)initializeWithConfig:(Config*)config {
69-
if (self = [super init]) {
70-
RuntimeConfig.BaseDir = [config.BaseDir UTF8String];
71-
if (config.ApplicationPath != nil) {
72-
RuntimeConfig.ApplicationPath = [[config.BaseDir stringByAppendingPathComponent:config.ApplicationPath] UTF8String];
73-
} else {
74-
RuntimeConfig.ApplicationPath = [[config.BaseDir stringByAppendingPathComponent:@"app"] UTF8String];
75-
}
76-
if (config.MetadataPtr != nil) {
77-
RuntimeConfig.MetadataPtr = [config MetadataPtr];
78-
} else {
79-
RuntimeConfig.MetadataPtr = &defaultStartOfMetadataSection;
80-
}
81-
RuntimeConfig.IsDebug = [config IsDebug];
82-
RuntimeConfig.LogToSystemConsole = [config LogToSystemConsole];
83-
84-
Runtime::Initialize();
85-
runtime_ = nullptr;
86-
runtime_ = std::make_unique<Runtime>();
87-
88-
std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
89-
Isolate* isolate = runtime_->CreateIsolate();
90-
v8::Locker l(isolate);
91-
runtime_->Init(isolate);
92-
std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
93-
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
94-
printf("Runtime initialization took %llims (version %s, V8 version %s)\n", duration, NATIVESCRIPT_VERSION, V8::GetVersion());
95-
96-
if (config.IsDebug) {
97-
Isolate::Scope isolate_scope(isolate);
98-
HandleScope handle_scope(isolate);
99-
v8_inspector::JsV8InspectorClient* inspectorClient = new v8_inspector::JsV8InspectorClient(runtime_.get());
100-
inspectorClient->init();
101-
inspectorClient->registerModules();
102-
inspectorClient->connect([config ArgumentsCount], [config Arguments]);
103-
Console::AttachInspectorClient(inspectorClient);
104-
}
84+
if (self = [super init]) {
85+
RuntimeConfig.BaseDir = [config.BaseDir UTF8String];
86+
if (config.ApplicationPath != nil) {
87+
RuntimeConfig.ApplicationPath =
88+
[[config.BaseDir stringByAppendingPathComponent:config.ApplicationPath] UTF8String];
89+
} else {
90+
RuntimeConfig.ApplicationPath =
91+
[[config.BaseDir stringByAppendingPathComponent:@"app"] UTF8String];
92+
}
93+
if (config.MetadataPtr != nil) {
94+
RuntimeConfig.MetadataPtr = [config MetadataPtr];
95+
} else {
96+
RuntimeConfig.MetadataPtr = &defaultStartOfMetadataSection;
97+
}
98+
RuntimeConfig.IsDebug = [config IsDebug];
99+
RuntimeConfig.LogToSystemConsole = [config LogToSystemConsole];
100+
101+
Runtime::Initialize();
102+
runtime_ = nullptr;
103+
runtime_ = std::make_unique<Runtime>();
104+
105+
std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
106+
Isolate* isolate = runtime_->CreateIsolate();
107+
v8::Locker l(isolate);
108+
runtime_->Init(isolate);
109+
std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
110+
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
111+
printf("Runtime initialization took %llims (version %s, V8 version %s)\n", duration,
112+
NATIVESCRIPT_VERSION, V8::GetVersion());
113+
114+
if (config.IsDebug) {
115+
Isolate::Scope isolate_scope(isolate);
116+
HandleScope handle_scope(isolate);
117+
v8_inspector::JsV8InspectorClient* inspectorClient =
118+
new v8_inspector::JsV8InspectorClient(runtime_.get());
119+
inspectorClient->init();
120+
inspectorClient->registerModules();
121+
inspectorClient->connect([config ArgumentsCount], [config Arguments]);
122+
Console::AttachInspectorClient(inspectorClient);
105123
}
106-
return self;
107-
124+
}
125+
return self;
108126
}
109127

110128
- (instancetype)initWithConfig:(Config*)config {
111-
return [self initializeWithConfig:config];
129+
return [self initializeWithConfig:config];
112130
}
113131

114132
- (void)restartWithConfig:(Config*)config {
115-
[self shutdownRuntime];
116-
[self initializeWithConfig:config];
133+
[self shutdownRuntime];
134+
[self initializeWithConfig:config];
117135
}
118136

119-
120-
121137
@end

NativeScript/inspector/JsV8InspectorClient.mm

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "Helpers.h"
1313
#include "InspectorServer.h"
1414
#include "JsV8InspectorClient.h"
15+
#include "RuntimeConfig.h"
1516
#include "include/libplatform/libplatform.h"
1617
#include "utils.h"
1718

@@ -392,7 +393,26 @@
392393
{
393394
v8::Locker locker(isolate);
394395
TryCatch tc(isolate);
395-
runtime_->RunModule("inspector_modules");
396+
397+
// Check for ES module (.mjs) first, then fallback to CommonJS (.js)
398+
NSString* appPath = [NSString stringWithUTF8String:RuntimeConfig.ApplicationPath.c_str()];
399+
NSString* mjsPath =
400+
[[appPath stringByAppendingPathComponent:@"tns_modules/inspector_modules.mjs"]
401+
stringByStandardizingPath];
402+
NSString* jsPath = [[appPath stringByAppendingPathComponent:@"tns_modules/inspector_modules.js"]
403+
stringByStandardizingPath];
404+
405+
std::string modulePath;
406+
if ([[NSFileManager defaultManager] fileExistsAtPath:mjsPath]) {
407+
modulePath = [mjsPath UTF8String];
408+
} else if ([[NSFileManager defaultManager] fileExistsAtPath:jsPath]) {
409+
modulePath = [jsPath UTF8String];
410+
} else {
411+
// No inspector modules found, skip loading
412+
return;
413+
}
414+
415+
runtime_->RunModule(modulePath);
396416
// FIXME: This triggers some DCHECK failures, due to the entered v8::Context in
397417
// Runtime::init().
398418
}

0 commit comments

Comments
 (0)