emnapi Documentation

website·Indexed 30 days ago

https://emnapi-docs.vercel.app/

A library providing a nearly full implementation of Node-API for Emscripten, allowing Node-API native addons to run in the browser with minimal code changes. Documentation covers function and class binding, multithreaded asynchronous operations, C++ integration via node-addon-api, and build configurations using CMake and node-gyp.

Tokens
21K
Snippets
85
Records
104
Agent score
72%

What's inside emnapi

  1. Overview of emnapi

    emnapi is a near-complete implementation of the Node-API (formerly N-API) for Emscripten. Starting from v0.29.0, it also supports the wasi-sdk and the clang wasm32-unknown-unknown target. Its primary goal is to allow developers to port existing Node-API native addons to WebAssembly (Wasm) with minimal code changes, ensuring that runtime behavior closely matches native Node.js.
  2. Run Node-API native addons in the browser

    Node-API native addons can be ported to run in a browser environment with minimal code changes, enabling cross-platform portability between Node.js and web browsers.
  3. Build emnapi projects using node-gyp (Experimental)

    node-gyp support for emnapi is experimental. Currently, it works primarily on Linux; Windows and macOS may encounter issues with static library linking during cross-compilation. To configure the architecture, use the --arch flag with wasm32 or wasm64.
    node-gyp configure --arch=wasm32
  4. Compare emnapi and embind for Node.js and Browser targets

    While embind allows code to run in both the browser and Node.js, it often requires writing separate bindings if you want native performance in Node.js (via native addons) and Wasm performance in the browser. emnapi solves this by providing a Node-API implementation for Wasm, allowing the same Node-API binding code to be used across both environments. It is specifically recommended for projects targeting StackBlitz's WebContainer.
  5. Manage native class memory in emnapi

    When exposing C++ classes to JavaScript via emnapi, memory management depends on the JavaScript runtime's support for FinalizationRegistry and WeakRef.

    • If supported: The finalizer callback is called automatically when the JavaScript object is garbage collected, similar to native Node.js addons.
    • If NOT supported: You must expose a manual cleanup method (e.g., dispose()) from the C++ side. The JavaScript consumer must call this method to delete the native instance pointer to prevent memory leaks.
    Module.onRuntimeInitialized = function () {
      const MyClass = Module.emnapiExports.MyClass
      const instance = new MyClass(10, "hello")
    
      // Manual cleanup for runtimes without FinalizationRegistry
      if (typeof FinalizationRegistry !== 'function') {
        instance.dispose()
        // After disposing, the instance must no longer be used
      }
    }
  6. Use multithreading and thread-safe functions in emnapi

    The following APIs in node_api.h provide multithreading and thread-safe function capabilities, provided the pthread and environment requirements are met:
    napi_create_async_work
    napi_delete_async_work
    napi_queue_async_work
    napi_cancel_async_work
    napi_create_threadsafe_function
    napi_get_threadsafe_function_context
    napi_call_threadsafe_function
    napi_acquire_threadsafe_function
    napi_release_threadsafe_function
    napi_unref_threadsafe_function
    napi_ref_threadsafe_function
  7. Handle errors using Maybe type with C++ exceptions disabled

    When C++ exceptions are disabled and NODE_ADDON_API_ENABLE_MAYBE is predefined, functions that can fail return a Maybe<T> boxed value instead of throwing. Developers must check if the Maybe value is empty using .To() or .IsNothing(). If empty, a JavaScript exception is pending and should be managed via env.GetAndClearPendingException() before calling other Node-API functions.
    Napi::Env env = ...;
    Napi::Function jsFunctionThatThrows = someValue.As<Napi::Function>();
    
    // Propagating: Check if Maybe is empty
    Maybe<Napi::Value> maybeResult = jsFunctionThatThrows({ arg1, arg2 });
    Napi::Value result;
    if (!maybeResult.To(&result)) {
        return result; // Propagate pending exception
    }
    
    // Handling: Clear the pending exception
    if (maybeResult.IsNothing()) {
        Napi::Error e = env.GetAndClearPendingException();
        std::cerr << "Caught JavaScript exception: " + e.Message();
    }
  8. Implement member accessors (getters and setters) for emnapi classes

    Member accessors allow JavaScript properties to map to C++ getter and setter methods. Use napi_unwrap to retrieve the C++ instance from the JavaScript this object.
    // Getter implementation
    napi_value MyClass::JsGetX(napi_env env, napi_callback_info info) {
      napi_value this_arg;
      NAPI_CALL(env, napi_get_cb_info(env, info, nullptr, nullptr, &this_arg, nullptr));
    
      MyClass* instance = nullptr;
      NAPI_CALL(env, napi_unwrap(env, this_arg, reinterpret_cast<void**>(&instance)));
    
      napi_value ret;
      NAPI_CALL(env, napi_create_int32(env, instance->GetX(), &ret));
      return ret;
    }
    
    // Setter implementation
    napi_value MyClass::JsSetX(napi_env env, napi_callback_info info) {
      size_t argc = 1;
      napi_value args[1];
      napi_value this_arg;
      NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &this_arg, nullptr));
    
      int x = 0;
      NAPI_CALL(env, napi_get_value_int32(env, args[0], &x));
    
      MyClass* instance = nullptr;
      NAPI_CALL(env, napi_unwrap(env, this_arg, reinterpret_cast<void**>(&instance)));
    
      instance->SetX(x);
    
      napi_value undefined;
      NAPI_CALL(env, napi_get_undefined(env, &undefined));
      return undefined;
    }
  9. Implement asynchronous tasks using Napi::AsyncWorker with callbacks

    To perform a heavy operation in a child thread and notify the main thread upon completion, extend the Napi::AsyncWorker class.

    • Execute(): This method runs in the child thread. Perform the heavy computation here. Do not access JavaScript objects or the Napi::Env directly inside this method.
    • OnOK(): This method runs in the main thread after Execute() completes successfully. Use it to call the JavaScript callback with the result.
    • OnError(): This method runs in the main thread if an error occurs during execution.

    To start the worker, instantiate your class and call the Queue() method.

    #include <napi.h>
    
    class MonteCarloEstimatePiWorker : public Napi::AsyncWorker {
     public:
      MonteCarloEstimatePiWorker(int points, const Napi::Function& callback)
          : Napi::AsyncWorker(callback), points_(points), result_(0) {}
    
      void Execute() {
        // Runs in child thread
        result_ = monte_carlo_estimate_pi(points_);
      }
    
      void OnOK() {
        // Runs in main thread
        Callback().Call(Env().Undefined(), {
          Env().Null(),
          Napi::Number::New(Env(), result_)
        });
      }
    
      void OnError(const Napi::Error& e) {
        // Runs in main thread
        Callback().Call(Env().Undefined(), { e.Value() });
      }
    
     private:
      int points_;
      double result_;
    };
    
    static Napi::Value JsEstimate(const Napi::CallbackInfo& info) {
      Napi::Env env = info.Env();
      int points = info[0].As<Napi::Number>().Uint32Value();
      Napi::Function callback = info[1].As<Napi::Function>();
      
      auto* piWorker = new MonteCarloEstimatePiWorker(points, callback);
      piWorker->Queue();
      return env.Undefined();
    }
  10. Handle errors in node-addon-api with C++ exceptions enabled

    When C++ exceptions are enabled, node-addon-api automatically converts JavaScript exceptions and internal API failures (like invalid arguments) into C++ exceptions of type Napi::Error. These can be caught using standard try/catch blocks. If a Napi::Error escapes a native method, it is automatically converted back into a JavaScript exception upon returning to the JS environment.
    // Throwing a C++ exception
    Napi::Env env = ...;
    throw Napi::Error::New(env, "Example exception");
    
    // Handling a C++ exception
    Napi::Function jsFunctionThatThrows = someValue.As<Napi::Function>();
    Napi::Value result;
    try {
        result = jsFunctionThatThrows({ arg1, arg2 });
    } catch (const Napi::Error& e) {
        std::cerr << "Caught JavaScript exception: " + std::string(e.what());
    }
  11. Synchronize memory between WASM and JS using emnapi_sync_memory

    Because WASM memory may grow or copied memory may change, use emnapi_sync_memory (or the exported runtime method emnapiSyncMemory) to synchronize data between the WASM environment and JavaScript. This is particularly necessary when using napi_create_external_arraybuffer or when memory has been modified on the WASM side after the JS object was created.
    #include <emnapi.h>
    
    napi_status emnapi_sync_memory(napi_env env,
                                   bool js_to_wasm,
                                   napi_value* arraybuffer_or_view,
                                   size_t byte_offset,
                                   size_t length);
    
    void finalizer(napi_env env, void* finalize_data, void* finalize_hint) {
       free(finalize_data);
    }
    
    napi_value createExternalArraybuffer(napi_env env, napi_callback_info info) {
      uint8_t* external_data = malloc(3);
      external_data[0] = 0;
      external_data[1] = 1;
      external_data[2] = 2;
      napi_value array_buffer;
      napi_create_external_arraybuffer(env, external_data, 3, finalizer, NULL, &array_buffer);
    
      external_data[0] = 3; // JS ArrayBuffer memory will not change automatically
      emnapi_sync_memory(env, false, array_buffer, 0, NAPI_AUTO_LENGTH);
      // after sync memory, new Uint8Array(array_buffer)[0] === 3
    
      return array_buffer;
    }
    // JavaScript/TypeScript usage
    declare function emnapiSyncMemory (
      jsToWasm: boolean,
      arrayBufferOrView: ArrayBuffer | ArrayBufferView,
      byteOffset?: number,
      length?: number
    ): void
    
    const array_buffer = Module.emnapiExports.createExternalArraybuffer()
    new Uint8Array(array_buffer)[1] = 4
    Module.emnapiSyncMemory(true, array_buffer)
  12. Implement a string concatenation function with a callback using node-addon-api (C++)

    This example shows how to use the node-addon-api C++ wrapper to implement a function that concatenates two strings and passes the result to a JavaScript callback.

    Constraint: node-addon-api requires the runtime to support FinalizationRegistry and WeakRef.

    Napi::Value JsConcatString(const Napi::CallbackInfo& info) {
      Napi::Env env = info.Env();
      
      if (info.Length() < 3) {
        Napi::TypeError e = Napi::TypeError::New(env, "Wrong number of arguments");
        NAPI_THROW(e, Napi::Value());
      }
    
      if (!info[0].IsString() || !info[1].IsString() || !info[2].IsFunction()) {
        Napi::TypeError e = Napi::TypeError::New(env, "Wrong arguments");
        NAPI_THROW(e, Napi::Value());
      }
    
      Napi::Value undefined = env.Undefined();
    
      std::string str1 = info[0].As<Napi::String>().Utf8Value();
      std::string str2 = info[1].As<Napi::String>().Utf8Value();
      std::string result = str1 + str2;
    
      info[2].As<Napi::Function>().Call(undefined, { Napi::String::New(env, result) });
    
      return undefined;
    }