DEV Community

HarmonyOS
HarmonyOS

Posted on

Node API Part-6 : Loading a Module in the Main Thread Using Node-API on HarmonyOSNext

Read the original article:Node API Part-6 : Loading a Module in the Main Thread Using Node-API on HarmonyOSNext

👋 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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

napi_init.cpp

📲Test.ets

let value = 123;

function test() {
  console.log("Hello HarmonyOSNext");
}

export { value, test };
Enter fullscreen mode Exit fullscreen mode

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%')
  }
}
Enter fullscreen mode Exit fullscreen mode

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",
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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

Document

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)