👋 Introduction
If you’re developing native modules in HarmonyOS and need to load JavaScript or ArkTS modules from your native C++ code, you’re in the right place.
Whether you’re pulling in a system module like @ohos.hilog or dynamically importing your own .ets files—this article will walk you through how to safely and efficiently use napi_load_module on the main thread using Node-API.
⚠️ Spoiler: Don’t try this in background threads — there are strict rules!
📦 What is napi_load_module?
The napi_load_module function lets you load a JavaScript/ArkTS module dynamically at runtime. Once loaded, you can:
- Call exported functions (using napi_get_named_property)
- Access exported variables (using napi_get_property)
✨ Function Signature
napi_status napi_load_module(napi_env env, const char* path, napi_value* result);
napi_load_module method
Parameter Description env The current Node-API environment. path The module path or name (e.g., @ohos.hilog or ets/Test). result The object representing the loaded module.
🚧 Constraints You Should Know
Using napi_load_module comes with a few caveats:
✅ Allowed:
- In the main thread
- Outside the Init() method
- Outside thread-safe function callbacks
❌ Not Allowed:
- ❗ In background threads
- ❗ Inside the Init() method
- ❗ Within thread-safe function callbacks 🧠 Tip: For more advanced scenarios (like threading), use napi_load_module_with_info() instead—it’s more flexible!
⚙️ Implementation in Sample Project
🧩 API Declaration in index.d.ts
export const add: (a: number, b: number) => number;
export const loadModule: () => void;
index.d.ts
🏗️napi_init.cpp
#include "napi/native_api.h"
#include <string>
// Native C++ function to add two numbers passed from JavaScript
static napi_value Add(napi_env env, napi_callback_info info) {
size_t argc = 2;
napi_value args[2] = {nullptr};
// Retrieve arguments from JavaScript
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
// Check the types of the first and second arguments
napi_valuetype valuetype0;
napi_typeof(env, args[0], &valuetype0);
napi_valuetype valuetype1;
napi_typeof(env, args[1], &valuetype1);
// Convert the first and second arguments to double
double value0;
napi_get_value_double(env, args[0], &value0);
double value1;
napi_get_value_double(env, args[1], &value1);
// Calculate the sum of the two numbers
napi_value sum;
napi_create_double(env, value0 + value1, &sum);
// Return the result back to JavaScript
return sum;
}
// Function to dynamically load a JavaScript (ArkTS) module and invoke its contents
static napi_value loadModule(napi_env env, napi_callback_info info) {
napi_value result;
// 1. Load the Test.ets module dynamically using napi_load_module
napi_status status = napi_load_module(env, "ets/Test", &result);
// 2. Get the 'test' function exported from Test.ets
napi_value testFn;
napi_get_named_property(env, result, "test", &testFn);
// 3. Call the 'test' function (no arguments)
napi_call_function(env, result, testFn, 0, nullptr, nullptr);
// 4. Retrieve the 'value' variable exported from Test.ets
napi_value value;
napi_value key;
std::string keyStr = "value";
napi_create_string_utf8(env, keyStr.c_str(), keyStr.size(), &key);
napi_get_property(env, result, key, &value);
// Return the value back to JavaScript (optional use in JS)
return result;
}
// Module initialization function
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
// Define the functions that will be exported to JavaScript
napi_property_descriptor desc[] = {
{ "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "loadModule", nullptr, loadModule, nullptr, nullptr, nullptr, napi_default, nullptr }
};
// Register the functions with the exports object
napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
return exports;
}
EXTERN_C_END
// Define the native module metadata and entry point
static napi_module demoModule = {
.nm_version = 1, // N-API version
.nm_flags = 0, // No special flags
.nm_filename = nullptr, // Module filename (optional)
.nm_register_func = Init, // Initialization function
.nm_modname = "entry", // Name of the module (used in JavaScript)
.nm_priv = ((void *)0), // Optional private data
.reserved = {0}, // Reserved (must be zeroed)
};
// Register the module with the N-API runtime when the shared library is loaded
extern "C" __attribute__((constructor)) void RegisterEntryModule(void) {
napi_module_register(&demoModule);
}
napi_init.cpp
📲Test.ets
let value = 123;
function test() {
console.log("Hello HarmonyOSNext");
}
export { value, test };
Test.ets
▶️Index.ets
import { hilog } from '@kit.PerformanceAnalysisKit';
import testNapi from 'libentry.so';
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
testNapi.loadModule()
hilog.info(0x0000, 'testTag', 'Test NAPI 2 + 3 = %{public}d', );
})
}
.width('100%')
}
.height('100%')
}
}
index.ets
🧮build-profile.json5 (Entry Level)
Set runtimeOnly config in arkOptions-buildOption please.
{
"apiType": "stageMode",
"buildOption": {
"arkOptions" : {
"runtimeOnly" : {
"sources": [
"./src/main/ets/Test.ets"
]
}
},
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "",
"cppFlags": "",
"abiFilters": [
"arm64-v8a",
"x86_64"
]
}
},
"buildOptionSet": [
{
"name": "release",
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": false,
"files": [
"./obfuscation-rules.txt"
]
}
}
},
"nativeLib": {
"debugSymbol": {
"strip": true,
"exclude": []
}
}
},
],
"targets": [
{
"name": "default"
},
{
"name": "ohosTest",
}
]
}
build-profile.json5
🧾 Conclusion
Using napi_load_module in HarmonyOS native development gives you the power to:
- Dynamically load both system and custom modules
- Interact with ArkTS logic from native code
- Keep your application modular and lightweight Just remember the golden rule: always call this from the main thread!
📚 Additional Resources
HarmonyOS Docs: Node-API Reference
The OpenCms demo, brought to you by Alkacon Software.developer.huawei.com
https://forums.developer.huawei.com/forumPortal/en/topic/0201190049990984085?fid=0102647487706140266
Written by Bunyamin Eymen Alagoz
Top comments (0)