Skip to content

feat: ES modules (ESM) support with conditional esm or commonjs consumption + better error handling #276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a2a35d4
fix(catalyst): variable-length arrays
NathanWalker Jul 11, 2025
98d9ad5
feat: support conditional esm, .mjs, consumption
NathanWalker Jul 16, 2025
f42605c
feat: consume esm if .mjs is found + better error handling
NathanWalker Jul 16, 2025
485570c
feat: dynamic import, lazy loaded chunks wip
NathanWalker Jul 16, 2025
b7ac75c
feat: dynamic import support for lazy loading
NathanWalker Jul 16, 2025
f72b565
chore: 9.0.0-esm.1
NathanWalker Jul 16, 2025
b2b65fa
feat: handling external modules
NathanWalker Jul 16, 2025
bd6aee6
feat: provide valid import.meta.url behavior
NathanWalker Jul 17, 2025
109775b
feat: more verbose exception output
NathanWalker Jul 18, 2025
b81746d
feat: workers
NathanWalker Jul 18, 2025
828d022
chore: 9.0.0-esm.2
NathanWalker Jul 18, 2025
5add9e3
feat: worker improvements
NathanWalker Jul 19, 2025
8b78f90
feat: import.meta improvements
NathanWalker Jul 19, 2025
9c482b8
chore: safe global registry handling
NathanWalker Jul 23, 2025
b6d497e
feat: more robust commonjs vs esm conditional handling
NathanWalker Jul 23, 2025
0d0d3b1
chore: 9.0.0-esm.4
NathanWalker Jul 23, 2025
091358d
chore: 9.0.0-esm.5
NathanWalker Jul 24, 2025
2891409
chore: fix tests and add more robustness to module resolution cases
NathanWalker Jul 25, 2025
25a4e90
chore: 9.0.0-esm.6
NathanWalker Jul 25, 2025
37cde9b
fix: allow dynamic import chunks to resolve properly under diverse bu…
NathanWalker Jul 27, 2025
ef9a75a
chore: 9.0.0-esm.7
NathanWalker Jul 27, 2025
5acef93
feat: improved exception handling to not stop execution and show stac…
NathanWalker Jul 29, 2025
db0289d
chore: 9.0.0-esm.8
NathanWalker Jul 29, 2025
13dd1ff
feat: exception handling improvements with in-flight displays
NathanWalker Aug 1, 2025
800cfbe
feat: skip inspector_modules if external file not found
NathanWalker Aug 9, 2025
499faff
feat: add config option showErrorDisplay to conditionally show in-fli…
NathanWalker Aug 20, 2025
810c83b
feat: search sub framework paths in metadata generator
NathanWalker Aug 20, 2025
2f6dfca
feat: opt for os_log with graceful backwards compat fallback
NathanWalker Aug 20, 2025
60d353d
Merge remote-tracking branch 'origin/main' into feat/allow-conditiona…
NathanWalker Aug 22, 2025
a3bbfdf
fix: worker url resolution with ~ beginning
NathanWalker Aug 22, 2025
41a98f9
fix: error display
NathanWalker Aug 22, 2025
decfb98
feat: error handling improvements and cleanup debug logs
NathanWalker Aug 22, 2025
57989f4
chore: cleanup logs
NathanWalker Aug 22, 2025
79e73cb
chore: 9.0.0-esm.9
NathanWalker Aug 22, 2025
9637918
feat: use consistent logs
NathanWalker Aug 24, 2025
e29c06a
chore: 9.0.0-esm.10
NathanWalker Aug 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion NativeScript/NativeScript-Prefix.pch
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#ifndef NativeScript_Prefix_pch
#define NativeScript_Prefix_pch

#define NATIVESCRIPT_VERSION "8.9.0"
#define NATIVESCRIPT_VERSION "9.0.0-esm.10"

#ifdef DEBUG
#define SIZEOF_OFF_T 8
Expand Down
159 changes: 89 additions & 70 deletions NativeScript/NativeScript.mm
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
#include <Foundation/Foundation.h>
#include "NativeScript.h"
#include <Foundation/Foundation.h>
#include "inspector/JsV8InspectorClient.h"
#include "runtime/Console.h"
#include "runtime/RuntimeConfig.h"
#include "runtime/Helpers.h"
#include "runtime/Runtime.h"
#include "runtime/RuntimeConfig.h"
#include "runtime/Tasks.h"

using namespace v8;
using namespace tns;

namespace tns {
// External flag from Runtime.mm to track JavaScript errors
extern bool jsErrorOccurred;
}

@implementation Config

@synthesize BaseDir;
Expand All @@ -23,99 +28,113 @@ @implementation NativeScript

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

- (void)runScriptString: (NSString*) script runLoop: (BOOL) runLoop {

std::string cppString = std::string([script UTF8String]);
runtime_->RunScript(cppString);

if (runLoop) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
}
- (void)runScriptString:(NSString*)script runLoop:(BOOL)runLoop {
std::string cppString = std::string([script UTF8String]);
runtime_->RunScript(cppString);

if (runLoop) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
}

tns::Tasks::Drain();

tns::Tasks::Drain();
}

std::unique_ptr<Runtime> runtime_;

- (void)runMainApplication {
runtime_->RunMainScript();
runtime_->RunMainScript();

// In debug mode, if JavaScript errors occurred, keep the app alive indefinitely
// This prevents iOS from terminating the app and allows hot-reload to work
if (RuntimeConfig.IsDebug && jsErrorOccurred) {
// NSLog(@"🔧 Debug mode - JavaScript errors detected, hijacking main thread to keep app alive");
// NSLog(@"🔧 Debug mode - Entering infinite run loop to prevent app termination");
// NSLog(@"🔧 Debug mode - Hot-reload and error modal should work normally");

// Main thread hijack: Enter infinite run loop to keep app alive
while (true) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true);
tns::Tasks::Drain();
}
// Note: This line is never reached in debug mode with errors
}

CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
tns::Tasks::Drain();
// Normal execution path (no errors or release mode)
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
tns::Tasks::Drain();
}

- (bool)liveSync {
if (runtime_ == nullptr) {
return false;
}
if (runtime_ == nullptr) {
return false;
}

Isolate* isolate = runtime_->GetIsolate();
return tns::LiveSync(isolate);
Isolate* isolate = runtime_->GetIsolate();
return tns::LiveSync(isolate);
}

- (void)shutdownRuntime {
if (RuntimeConfig.IsDebug) {
Console::DetachInspectorClient();
}
tns::Tasks::ClearTasks();
if (runtime_ != nullptr) {
runtime_ = nullptr;
}
if (RuntimeConfig.IsDebug) {
Console::DetachInspectorClient();
}
tns::Tasks::ClearTasks();
if (runtime_ != nullptr) {
runtime_ = nullptr;
}
}

- (instancetype)initializeWithConfig:(Config*)config {
if (self = [super init]) {
RuntimeConfig.BaseDir = [config.BaseDir UTF8String];
if (config.ApplicationPath != nil) {
RuntimeConfig.ApplicationPath = [[config.BaseDir stringByAppendingPathComponent:config.ApplicationPath] UTF8String];
} else {
RuntimeConfig.ApplicationPath = [[config.BaseDir stringByAppendingPathComponent:@"app"] UTF8String];
}
if (config.MetadataPtr != nil) {
RuntimeConfig.MetadataPtr = [config MetadataPtr];
} else {
RuntimeConfig.MetadataPtr = &defaultStartOfMetadataSection;
}
RuntimeConfig.IsDebug = [config IsDebug];
RuntimeConfig.LogToSystemConsole = [config LogToSystemConsole];

Runtime::Initialize();
runtime_ = nullptr;
runtime_ = std::make_unique<Runtime>();

std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
Isolate* isolate = runtime_->CreateIsolate();
v8::Locker l(isolate);
runtime_->Init(isolate);
std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
printf("Runtime initialization took %llims (version %s, V8 version %s)\n", duration, NATIVESCRIPT_VERSION, V8::GetVersion());

if (config.IsDebug) {
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
v8_inspector::JsV8InspectorClient* inspectorClient = new v8_inspector::JsV8InspectorClient(runtime_.get());
inspectorClient->init();
inspectorClient->registerModules();
inspectorClient->connect([config ArgumentsCount], [config Arguments]);
Console::AttachInspectorClient(inspectorClient);
}
if (self = [super init]) {
RuntimeConfig.BaseDir = [config.BaseDir UTF8String];
if (config.ApplicationPath != nil) {
RuntimeConfig.ApplicationPath =
[[config.BaseDir stringByAppendingPathComponent:config.ApplicationPath] UTF8String];
} else {
RuntimeConfig.ApplicationPath =
[[config.BaseDir stringByAppendingPathComponent:@"app"] UTF8String];
}
return self;

if (config.MetadataPtr != nil) {
RuntimeConfig.MetadataPtr = [config MetadataPtr];
} else {
RuntimeConfig.MetadataPtr = &defaultStartOfMetadataSection;
}
RuntimeConfig.IsDebug = [config IsDebug];
RuntimeConfig.LogToSystemConsole = [config LogToSystemConsole];

Runtime::Initialize();
runtime_ = nullptr;
runtime_ = std::make_unique<Runtime>();

std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
Isolate* isolate = runtime_->CreateIsolate();
v8::Locker l(isolate);
runtime_->Init(isolate);
std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
printf("Runtime initialization took %llims (version %s, V8 version %s)\n", duration,
NATIVESCRIPT_VERSION, V8::GetVersion());

if (config.IsDebug) {
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
v8_inspector::JsV8InspectorClient* inspectorClient =
new v8_inspector::JsV8InspectorClient(runtime_.get());
inspectorClient->init();
inspectorClient->registerModules();
inspectorClient->connect([config ArgumentsCount], [config Arguments]);
Console::AttachInspectorClient(inspectorClient);
}
}
return self;
}

- (instancetype)initWithConfig:(Config*)config {
return [self initializeWithConfig:config];
return [self initializeWithConfig:config];
}

- (void)restartWithConfig:(Config*)config {
[self shutdownRuntime];
[self initializeWithConfig:config];
[self shutdownRuntime];
[self initializeWithConfig:config];
}



@end
Loading
Loading