Skip to content

Commit 81fa851

Browse files
committed
implement basic esm handling
1 parent 5a87628 commit 81fa851

File tree

11 files changed

+681
-14
lines changed

11 files changed

+681
-14
lines changed

NativeScript/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ if(ENABLE_JS_RUNTIME)
164164
set(SOURCE_FILES
165165
${SOURCE_FILES}
166166
napi/v8/v8-api.cpp
167+
napi/v8/v8-module-loader.cpp
167168
napi/v8/jsr.cpp
168169
napi/v8/SimpleAllocator.cpp
169170
)

NativeScript/napi/common/js_native_api.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,12 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_run_script_source(napi_env env,
440440
const char* source_url,
441441
napi_value* result);
442442

443+
// ES Module support
444+
NAPI_EXTERN napi_status NAPI_CDECL napi_run_script_as_module(napi_env env,
445+
napi_value script,
446+
const char* source_url,
447+
napi_value* result);
448+
443449
// Memory management
444450
NAPI_EXTERN napi_status NAPI_CDECL napi_adjust_external_memory(
445451
napi_env env, int64_t change_in_bytes, int64_t *adjusted_value);

NativeScript/napi/v8/v8-api.cpp

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "js_native_api.h"
1010
#include "v8-api.h"
11+
#include "v8-module-loader.h"
1112

1213
#ifdef ANDROID
1314
#include <android/log.h>
@@ -3272,6 +3273,110 @@ napi_status napi_run_script_source(napi_env env, napi_value script,
32723273
env, v8impl::JsValueFromV8LocalValue(source_with_comment), result);
32733274
}
32743275

3276+
// ES Module support
3277+
napi_status NAPI_CDECL napi_run_script_as_module(napi_env env,
3278+
napi_value script,
3279+
const char* source_url,
3280+
napi_value* result) {
3281+
NAPI_PREAMBLE(env);
3282+
CHECK_ARG(env, script);
3283+
CHECK_ARG(env, source_url);
3284+
CHECK_ARG(env, result);
3285+
3286+
v8::Local<v8::Value> v8_script = v8impl::V8LocalValueFromJsValue(script);
3287+
3288+
if (!v8_script->IsString()) {
3289+
return napi_set_last_error(env, napi_string_expected);
3290+
}
3291+
3292+
v8::Local<v8::Context> context = env->context();
3293+
v8::Isolate* isolate = env->isolate;
3294+
3295+
v8::TryCatch module_try_catch(isolate);
3296+
3297+
// Initialize ES module system on first use
3298+
static bool es_module_initialized = false;
3299+
if (!es_module_initialized) {
3300+
v8impl::InitializeESModuleSystem(isolate);
3301+
es_module_initialized = true;
3302+
}
3303+
3304+
// Create script origin for ES module
3305+
v8::ScriptOrigin origin(
3306+
isolate,
3307+
v8::String::NewFromUtf8(isolate, source_url, v8::NewStringType::kNormal).ToLocalChecked(),
3308+
0, 0, false, -1, v8::Local<v8::Value>(), false, false,
3309+
true // is_module = true for ES modules
3310+
);
3311+
3312+
v8::ScriptCompiler::Source source(v8_script.As<v8::String>(), origin);
3313+
3314+
// Compile as ES module
3315+
v8::MaybeLocal<v8::Module> maybe_module = v8::ScriptCompiler::CompileModule(
3316+
isolate, &source, v8::ScriptCompiler::kNoCompileOptions);
3317+
3318+
if (maybe_module.IsEmpty()) {
3319+
CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, maybe_module, napi_generic_failure);
3320+
return napi_generic_failure;
3321+
}
3322+
3323+
v8::Local<v8::Module> module = maybe_module.ToLocalChecked();
3324+
3325+
// Register the module in our module registry for resolution
3326+
// Use the source_url as the module path
3327+
std::string modulePath = source_url;
3328+
3329+
// Safe Global handle management: Clear any existing entry first
3330+
auto it = v8impl::g_moduleRegistry.find(modulePath);
3331+
if (it != v8impl::g_moduleRegistry.end()) {
3332+
// Clear the existing Global handle before replacing it
3333+
it->second.Reset();
3334+
}
3335+
3336+
v8impl::g_moduleRegistry[modulePath].Reset(isolate, module);
3337+
3338+
// Check for pending exception from compilation
3339+
if (module_try_catch.HasCaught()) {
3340+
// Log the exception to console to debug
3341+
v8::Local<v8::Value> exception = module_try_catch.Exception();
3342+
v8::Local<v8::Message> message = module_try_catch.Message();
3343+
if (!message.IsEmpty()) {
3344+
v8::String::Utf8Value error(isolate, message->Get());
3345+
fprintf(stderr, "Error compiling module: %s\n", *error);
3346+
} else {
3347+
v8::String::Utf8Value error(isolate, exception);
3348+
fprintf(stderr, "Error compiling module: %s\n", *error);
3349+
}
3350+
return napi_set_last_error(env, napi_generic_failure);
3351+
}
3352+
3353+
// Use our ES module resolver
3354+
v8::TryCatch instantiate_try_catch(isolate);
3355+
if (!module->InstantiateModule(context, &v8impl::ResolveModuleCallback).FromMaybe(false)) {
3356+
if (instantiate_try_catch.HasCaught()) {
3357+
// Store the exception in env->last_exception instead of throwing
3358+
v8::Local<v8::Value> exception = instantiate_try_catch.Exception();
3359+
v8::String::Utf8Value error(isolate, exception);
3360+
3361+
if (!env->last_exception.IsEmpty()) {
3362+
env->last_exception.Reset();
3363+
}
3364+
env->last_exception.Reset(env->isolate, instantiate_try_catch.Exception());
3365+
}
3366+
return napi_set_last_error(env, napi_generic_failure);
3367+
}
3368+
3369+
// Evaluate the module
3370+
v8::MaybeLocal<v8::Value> maybe_result = module->Evaluate(context);
3371+
CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, maybe_result, napi_generic_failure);
3372+
3373+
// Get the module namespace as the result
3374+
v8::Local<v8::Value> namespace_obj = module->GetModuleNamespace();
3375+
*result = v8impl::JsValueFromV8LocalValue(namespace_obj);
3376+
3377+
return GET_RETURN_STATUS(env);
3378+
}
3379+
32753380
napi_status NAPI_CDECL napi_add_finalizer(napi_env env, napi_value js_object,
32763381
void* finalize_data,
32773382
napi_finalize finalize_cb,

0 commit comments

Comments
 (0)