# emnapi

<p align="center">
  <img src="https://toyobayashi.github.io/emnapi-docs/emnapi.svg" alt="emnapi logo" width="256" />
</p>

[![Build](https://github.com/toyobayashi/emnapi/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/toyobayashi/emnapi/actions/workflows/main.yml)

[Node-API](https://nodejs.org/docs/latest/api/n-api.html) implementation for [Emscripten](https://emscripten.org/index.html), [wasi-sdk](https://github.com/WebAssembly/wasi-sdk) and clang `wasm32-unknown-unknown` target, [napi-rs support is comming soon](https://github.com/napi-rs/napi-rs/issues/796).

Emscripten is the first class support target, currently thread related APIs are unavailable on `wasm32-unknown-unknown` and `wasm32-wasi` target.

This project aims to

- Help users port their or existing Node-API native addons to wasm with code change as less as possible.
- Make runtime behavior matches native Node.js as much as possible.

See documentation for more details:
- [https://toyobayashi.github.io/emnapi-docs/guide/](https://toyobayashi.github.io/emnapi-docs/guide/)
- [https://emnapi-docs.vercel.app/guide/](https://emnapi-docs.vercel.app/guide/)

中文文档：
- [https://toyobayashi.github.io/emnapi-docs/zh/guide/](https://toyobayashi.github.io/emnapi-docs/zh/guide/)
- [https://emnapi-docs.vercel.app/zh/guide/](https://emnapi-docs.vercel.app/zh/guide/)

[Full API List](https://toyobayashi.github.io/emnapi-docs/reference/list.html)

[How to build Node-API official examples](https://github.com/toyobayashi/node-addon-examples)

## Prerequests

You will need to install:

- Node.js `>= v16.15.0`
- npm `>= v8`
- Emscripten `>= v3.1.9` / wasi-sdk / LLVM clang with wasm support
- (Optional) CMake `>= v3.13`
- (Optional) ninja
- (Optional) make

There are several choices to get `make` for Windows user

- Install [mingw-w64](https://www.mingw-w64.org/downloads/), then use `mingw32-make`
- Download [MSVC prebuilt binary of GNU make](https://github.com/toyobayashi/make-win-build/releases), add to `%Path%` then rename it to `mingw32-make`
- Install [Visual Studio 2022](https://visualstudio.microsoft.com/) C++ desktop workload, use `nmake` in `Visual Studio Developer Command Prompt`
- Install [Visual C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/), use `nmake` in `Visual Studio Developer Command Prompt`

Verify your environment:

```bash
node -v
npm -v
emcc -v

# clang -v
# clang -print-targets # ensure wasm32 target exists

cmake --version

# if you use ninja
ninja --version

# if you use make
make -v

# if you use nmake in Visual Studio Developer Command Prompt
nmake /?
```

## Build from source

You need to set `EMSDK` and `WASI_SDK_PATH` environment variables.

```bash
git clone https://github.com/toyobayashi/emnapi.git
cd ./emnapi
npm install -g node-gyp
npm install
npm run build             # output ./packages/*/dist
node ./script/release.js  # output ./out

# test
npm run rebuild:test
npm test
```

## Quick Start

### NPM Install

```bash
npm install -D @tybys/emnapi
npm install @tybys/emnapi-runtime

# for non-emscripten
npm install @tybys/emnapi-core
```

Each package should match the same version.

### Using C

Create `hello.c`.

```c
#include <node_api.h>

#define NAPI_CALL(env, the_call)                                \
  do {                                                          \
    if ((the_call) != napi_ok) {                                \
      const napi_extended_error_info *error_info;               \
      napi_get_last_error_info((env), &error_info);             \
      bool is_pending;                                          \
      const char* err_message = error_info->error_message;      \
      napi_is_exception_pending((env), &is_pending);            \
      if (!is_pending) {                                        \
        const char* error_message = err_message != NULL ?       \
          err_message :                                         \
          "empty error message";                                \
        napi_throw_error((env), NULL, error_message);           \
      }                                                         \
      return NULL;                                              \
    }                                                           \
  } while (0)

static napi_value js_hello(napi_env env, napi_callback_info info) {
  napi_value world;
  const char* str = "world";
  NAPI_CALL(env, napi_create_string_utf8(env, str, NAPI_AUTO_LENGTH, &world));
  return world;
}

NAPI_MODULE_INIT() {
  napi_value hello;
  NAPI_CALL(env, napi_create_function(env, "hello", NAPI_AUTO_LENGTH,
                                      js_hello, NULL, &hello));
  NAPI_CALL(env, napi_set_named_property(env, exports, "hello", hello));
  return exports;
}
```

The C code is equivalant to the following JavaScript:

```js
module.exports = (function (exports) {
  const hello = function hello () {
    // native code in js_hello
    const world = 'world'
    return world
  }

  exports.hello = hello
  return exports
})(module.exports)
```

#### Building

<details>
<summary>emscripten</summary><br />

```bash
emcc -O3 \
     -I./node_modules/@tybys/emnapi/include \
     -L./node_modules/@tybys/emnapi/lib/wasm32-emscripten \
     --js-library=./node_modules/@tybys/emnapi/dist/library_napi.js \
     -sEXPORTED_FUNCTIONS="['_malloc','_free']" \
     -o hello.js \
     hello.c \
     -lemnapi
```

</details>

<details>
<summary>wasi-sdk</summary><br />

```bash
clang -O3 \
      -I./node_modules/@tybys/emnapi/include \
      -L./node_modules/@tybys/emnapi/lib/wasm32-wasi \
      --target=wasm32-wasi \
      --sysroot=$WASI_SDK_PATH/share/wasi-sysroot \
      -mexec-model=reactor \
      -Wl,--initial-memory=16777216 \
      -Wl,--export-dynamic \
      -Wl,--export=malloc \
      -Wl,--export=free \
      -Wl,--export=napi_register_wasm_v1 \
      -Wl,--import-undefined \
      -Wl,--export-table \
      -o hello.wasm \
      hello.c \
      -lemnapi
```

</details>

<details>
<summary>clang wasm32</summary><br />

Choose `libdlmalloc.a` or `libemmalloc.a` for `malloc` and `free`.

```bash
clang -O3 \
      -I./node_modules/@tybys/emnapi/include \
      -L./node_modules/@tybys/emnapi/lib/wasm32 \
      --target=wasm32 \
      -nostdlib \
      -Wl,--no-entry \
      -Wl,--initial-memory=16777216 \
      -Wl,--export-dynamic \
      -Wl,--export=malloc \
      -Wl,--export=free \
      -Wl,--export=napi_register_wasm_v1 \
      -Wl,--import-undefined \
      -Wl,--export-table \
      -o hello.wasm \
      hello.c \
      -lemnapi \
      -ldlmalloc # -lemmalloc
```

</details>

#### Initialization

To initialize emnapi, you need to import the emnapi runtime to create a `Context` by `createContext` or `getDefaultContext` first.
Each context owns isolated Node-API object such as `napi_env`, `napi_value`, `napi_ref`. If you have multiple emnapi modules, you should reuse the same `Context` across them. 

```ts
declare namespace emnapi {
  // module '@tybys/emnapi-runtime'
  export class Context { /* ... */ }
  /** Create a new context */
  export function createContext (): Context
  /** Create or get */
  export function getDefaultContext (): Context
  // ...
}
```

<details>
<summary>emscripten</summary><br />

then call `Module.emnapiInit` after emscripten runtime initialized.
`Module.emnapiInit` only do initialization once, it will always return the same binding exports after successfully initialized.

```ts
declare namespace Module {
  interface EmnapiInitOptions {
    context: emnapi.Context

    /** node_api_get_module_file_name */
    filename?: string

    /**
     * Support following async_hooks related things
     * on Node.js runtime only
     * 
     * napi_async_init,
     * napi_async_destroy,
     * napi_make_callback,
     * async resource parameter of
     * napi_create_async_work and napi_create_threadsafe_function
     */
    nodeBinding?: typeof import('@tybys/emnapi-node-binding')
  }
  export function emnapiInit (options: EmnapiInitOptions): any
}
```

```html
<script src="node_modules/@tybys/emnapi-runtime/dist/emnapi.min.js"></script>
<script src="hello.js"></script>
<script>
Module.onRuntimeInitialized = function () {
  var binding;
  try {
    binding = Module.emnapiInit({ context: emnapi.getDefaultContext() });
  } catch (err) {
    console.error(err);
    return;
  }
  var msg = 'hello ' + binding.hello();
  window.alert(msg);
};

// if -sMODULARIZE=1
Module({ /* Emscripten module init options */ }).then(function (Module) {
  var binding = Module.emnapiInit({ context: emnapi.getDefaultContext() });
});
</script>
```

If you are using `Visual Studio Code` and have `Live Server` extension installed, you can right click the HTML file in Visual Studio Code source tree and click `Open With Live Server`, then you can see the hello world alert!

Running on Node.js:

```js
const emnapi = require('@tybys/emnapi-runtime')
const Module = require('./hello.js')

Module.onRuntimeInitialized = function () {
  let binding
  try {
    binding = Module.emnapiInit({ context: emnapi.getDefaultContext() })
  } catch (err) {
    console.error(err)
    return
  }
  const msg = `hello ${binding.hello()}`
  console.log(msg)
}

// if -sMODULARIZE=1
Module({ /* Emscripten module init options */ }).then((Module) => {
  const binding = Module.emnapiInit({ context: emnapi.getDefaultContext() })
})
```

</details>

<details>
<summary>wasi-sdk or clang wasm32</summary><br />

For non-emscripten, you need to use `@tybys/emnapi-core`. The initialization is similar to emscripten.

```html
<script src="node_modules/@tybys/emnapi-runtime/dist/emnapi.min.js"></script>
<script src="node_modules/@tybys/emnapi-core/dist/emnapi-core.min.js"></script>
<script>
const napiModule = emnapiCore.createNapiModule({
  context: emnapi.getDefaultContext()
})

fetch('./hello.wasm').then(res => res.arrayBuffer()).then(wasmBuffer => {
  return WebAssembly.instantiate(wasmBuffer, {
    env: {
      ...napiModule.imports.env,
      // Currently napi-rs imports all symbols from env module
      ...napiModule.imports.napi,
      ...napiModule.imports.emnapi
    },
    // clang
    napi: napiModule.imports.napi,
    emnapi: napiModule.imports.emnapi
  })
}).then(({ instance }) => {
  const binding = napiModule.init(
    instance, // WebAssembly.Instance
    instance.exports.memory, // WebAssembly.Memory
    instance.exports.__indirect_function_table // WebAssembly.Table
  )
  // binding === napiModule.exports
})
</script>
```

Using WASI on Node.js

```js
const { createNapiModule } = require('@tybys/emnapi-core')
const { getDefaultContext } = require('@tybys/emnapi-runtime')
const { WASI } = require('wasi')

const napiModule = createNapiModule({
  context: getDefaultContext()
})

const wasi = new WASI({ /* ... */ })

WebAssembly.instantiate(require('fs').readFileSync('./hello.wasm'), {
  wasi_snapshot_preview1: wasi.wasiImport,
  env: {
    ...napiModule.imports.env,
    // Currently napi-rs imports all symbols from env module
    ...napiModule.imports.napi,
    ...napiModule.imports.emnapi
  },
  // clang
  napi: napiModule.imports.napi,
  emnapi: napiModule.imports.emnapi
}).then(({ instance }) => {
  wasi.initialize(instance)
  const binding = napiModule.init(
    instance,
    instance.exports.memory,
    instance.exports.__indirect_function_table
  )
  // binding === napiModule.exports
})
```

Using WASI on browser, you can use WASI polyfill in [wasm-util](https://github.com/toyobayashi/wasm-util),
and [memfs-browser](https://github.com/toyobayashi/memfs-browser)

```js
import { createNapiModule } from '@tybys/emnapi-core'
import { getDefaultContext } from '@tybys/emnapi-runtime'
import { WASI } from '@tybys/wasm-util'
import { Volumn, createFsFromVolume } from 'memfs-browser'

const napiModule = createNapiModule({
  context: getDefaultContext()
})

const fs = createFsFromVolume(Volume.from({ /* ... */ }))
const wasi = WASI.createSync({ fs, /* ... */ })

WebAssembly.instantiate(wasmBuffer, {
  wasi_snapshot_preview1: wasi.wasiImport,
  env: {
    ...napiModule.imports.env,
    // Currently napi-rs imports all symbols from env module
    ...napiModule.imports.napi,
    ...napiModule.imports.emnapi
  },
  // clang
  napi: napiModule.imports.napi,
  emnapi: napiModule.imports.emnapi
}).then(({ instance }) => {
  wasi.initialize(instance)
  const binding = napiModule.init(
    instance,
    instance.exports.memory,
    instance.exports.__indirect_function_table
  )
  // binding === napiModule.exports
})
```

</details>

### Using C++

Alternatively, you can also use [`node-addon-api`](https://github.com/nodejs/node-addon-api) which is official Node-API C++ wrapper, already shipped ([v5.1.0](https://github.com/nodejs/node-addon-api/releases/tag/v5.1.0)) in this package but without Node.js specific API such as `CallbackScope`.

**Note: C++ wrapper can only be used to target Node.js v14.6.0+ and modern browsers those support `FinalizationRegistry` and `WeakRef` ([v8 engine v8.4+](https://v8.dev/blog/v8-release-84))!**

Create `hello.cpp`.

```cpp
#include <napi.h>

Napi::String Method(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  return Napi::String::New(env, "world");
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports.Set(Napi::String::New(env, "hello"),
              Napi::Function::New(env, Method)).Check();
  return exports;
}

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
```

Compile `hello.cpp` using `em++`. C++ exception is disabled by Emscripten default, and not supported by wasi-sdk, so predefine `-DNAPI_DISABLE_CPP_EXCEPTIONS` and `-DNODE_ADDON_API_ENABLE_MAYBE` here. If you would like to enable C++ exception, use `-sDISABLE_EXCEPTION_CATCHING=0` instead and remove `.Check()` call. See official documentation [here](https://github.com/nodejs/node-addon-api/blob/main/doc/error_handling.md).

#### Building

<details>
<summary>emscripten</summary><br />

```bash
em++ -O3 \
     -DNAPI_DISABLE_CPP_EXCEPTIONS \
     -DNODE_ADDON_API_ENABLE_MAYBE \
     -I./node_modules/@tybys/emnapi/include \
     -L./node_modules/@tybys/emnapi/lib/wasm32-emscripten \
     --js-library=./node_modules/@tybys/emnapi/dist/library_napi.js \
     -sEXPORTED_FUNCTIONS="['_malloc','_free']" \
     -o hello.js \
     hello.cpp \
     -lemnapi
```

</details>

<details>
<summary>wasi-sdk</summary><br />

```bash
clang++ -O3 \
        -DNAPI_DISABLE_CPP_EXCEPTIONS \
        -DNODE_ADDON_API_ENABLE_MAYBE \
        -I./node_modules/@tybys/emnapi/include \
        -L./node_modules/@tybys/emnapi/lib/wasm32-wasi \
        --target=wasm32-wasi \
        --sysroot=$WASI_SDK_PATH/share/wasi-sysroot \
        -mexec-model=reactor \
        -Wl,--initial-memory=16777216 \
        -Wl,--export-dynamic \
        -Wl,--export=malloc \
        -Wl,--export=free \
        -Wl,--export=napi_register_wasm_v1 \
        -Wl,--import-undefined \
        -Wl,--export-table \
        -o hello.wasm \
        hello.cpp \
        -lemnapi
```

</details>

<details>
<summary>clang wasm32</summary><br />

`node-addon-api` is using the C++ standard libraries, so you must use WASI if you are using `node-addon-api`.

You can still use `wasm32-unknown-unknown` target if you use Node-API C API only in C++.

```bash
clang++ -O3 \
        -I./node_modules/@tybys/emnapi/include \
        -L./node_modules/@tybys/emnapi/lib/wasm32 \
        --target=wasm32 \
        -nostdlib \
        -Wl,--no-entry \
        -Wl,--initial-memory=16777216 \
        -Wl,--export-dynamic \
        -Wl,--export=malloc \
        -Wl,--export=free \
        -Wl,--export=napi_register_wasm_v1 \
        -Wl,--import-undefined \
        -Wl,--export-table \
        -o node_api_c_api_only.wasm \
        node_api_c_api_only.cpp \
        -lemnapi \
        -ldlmalloc # -lemmalloc
```

`operator new` and `operator delete`.

```cpp
#include <stddef.h>

extern "C" void* malloc(size_t size);
extern "C" void free(void* p);

void* operator new(size_t size) {
  return malloc(size);
}

void operator delete(void* p) noexcept {
  free(p);
}
```

</details>

### Using CMake

Create `CMakeLists.txt`.

```cmake
cmake_minimum_required(VERSION 3.13)

project(emnapiexample)

add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/node_modules/@tybys/emnapi")

add_executable(hello hello.c)

target_link_libraries(hello emnapi)
if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
  target_link_options(hello PRIVATE
    "-sEXPORTED_FUNCTIONS=\"['_malloc','_free']\""
  )
elseif(CMAKE_SYSTEM_NAME STREQUAL "WASI")
  target_link_options(hello PRIVATE
    "-mexec-model=reactor"
    "-Wl,--export=napi_register_wasm_v1"
    "-Wl,--initial-memory=16777216,--export-dynamic,--export=malloc,--export=free,--import-undefined,--export-table"
  )
elseif((CMAKE_C_COMPILER_TARGET STREQUAL "wasm32") OR (CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-unknown-unknown"))
  target_link_options(hello PRIVATE
    "-nostdlib"
    "-Wl,--export=napi_register_wasm_v1"
    "-Wl,--no-entry"
    "-Wl,--initial-memory=16777216,--export-dynamic,--export=malloc,--export=free,--import-undefined,--export-table"
  )
  target_link_libraries(hello dlmalloc)
  # target_link_libraries(hello emmalloc)
endif()
```

```bash
mkdir build

# emscripten
emcmake cmake -DCMAKE_BUILD_TYPE=Release -G Ninja -H. -Bbuild

# wasi-sdk
cmake -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PATH/share/cmake/wasi-sdk.cmake \
      -DWASI_SDK_PREFIX=$WASI_SDK_PATH \
      -DCMAKE_BUILD_TYPE=Release \
      -G Ninja -H. -Bbuild

# wasm32
cmake -DCMAKE_TOOLCHAIN_FILE=node_modules/@tybys/emnapi/cmake/wasm32.cmake \
      -DLLVM_PREFIX=$WASI_SDK_PATH \
      -DCMAKE_BUILD_TYPE=Release \
      -G Ninja -H. -Bbuild

cmake --build build
```

Output code can run in recent version modern browsers and Node.js latest LTS. IE is not supported.

### Using Rust (Experimental)

Currently you can use [napi-rs](https://github.com/napi-rs/napi-rs) like this, more work is working in progress.  

Note: WASI target require rust nightly toolchain.

<details>
<summary>Cargo.toml</summary><br />

```toml
[package]
edition = "2021"
name = "binding"
version = "0.0.0"

# We should build binary for WASI reactor
# https://github.com/rust-lang/rust/pull/79997
# https://github.com/WebAssembly/WASI/issues/24
# for wasm
[[bin]]
name = "binding"
path = "src/main.rs"

# for native
# [lib]
# name = "binding"
# path = "src/lib.rs"
# crate-type = ["cdylib"]

[dependencies]
napi = { version = "2.10.13", default-features = false, features = ["napi8", "compat-mode"] }
napi-sys = { version = "2.2.3", features = ["napi8"] }
napi-derive = "2.10.0"

[build-dependencies]
napi-build = "2.0.1"

[profile.release]
strip = "symbols"
```

</details>

<details>
<summary>.cargo/config.toml</summary><br />

```toml
[build]
target = [
  "wasm32-unknown-unknown",
  "wasm32-wasi"
]

[target.wasm32-unknown-unknown]
rustflags = [
  "-L./node_modules/@tybys/emnapi/lib/wasm32",
  "-lemnapi",
  "-ldlmalloc",
  # "-lemmalloc",
  "-C", "link-arg=--no-entry",
  "-C", "link-arg=--initial-memory=16777216",
  "-C", "link-arg=--export-dynamic",
  "-C", "link-arg=--export=malloc",
  "-C", "link-arg=--export=free",
  "-C", "link-arg=--export=napi_register_wasm_v1",
  "-C", "link-arg=--export-table",
  "-C", "link-arg=--import-undefined",
]

[target.wasm32-wasi]
rustflags = [
  "-L./node_modules/@tybys/emnapi/lib/wasm32-wasi",
  "-lemnapi",
  "-C", "link-arg=--initial-memory=16777216",
  "-C", "link-arg=--export-dynamic",
  "-C", "link-arg=--export=malloc",
  "-C", "link-arg=--export=free",
  "-C", "link-arg=--export=napi_register_wasm_v1",
  "-C", "link-arg=--export-table",
  "-C", "link-arg=--import-undefined",
  "-Z", "wasi-exec-model=reactor", # +nightly
]
```

</details>

<details>
<summary>src/main.rs</summary><br />

```rust
#![no_main]

use napi::*;

#[cfg(target_arch = "wasm32")]
use napi::bindgen_prelude::*;
#[cfg(target_arch = "wasm32")]
use napi_sys::*;

#[macro_use]
extern crate napi_derive;

fn sum(a: i32, b: i32) -> i32 {
  a + b
}

#[js_function(2)]
fn sum_js(ctx: CallContext) -> napi::Result<napi::JsNumber> {
  let arg0 = ctx.get::<napi::JsNumber>(0)?.get_int32()?;
  let arg1 = ctx.get::<napi::JsNumber>(1)?.get_int32()?;
  let ret = sum(arg0, arg1);
  ctx.env.create_int32(ret)
}

fn module_register(_env: napi::Env, mut exports: napi::JsObject) -> napi::Result<()> {
  exports.create_named_method("sum", sum_js)?;

  Ok(())
}

#[cfg(not(target_arch = "wasm32"))]
#[module_exports]
fn init(exports: napi::JsObject, env: napi::Env) -> napi::Result<()> {
  module_register(env, exports)
}

#[cfg(target_arch = "wasm32")]
#[no_mangle]
pub unsafe extern "C" fn napi_register_wasm_v1(env: napi_env, exports: napi_value) -> () {
  let env_object = napi::Env::from_raw(env);
  let exports_object = napi::JsObject::from_napi_value(env, exports).unwrap();
  module_register(env_object, exports_object).unwrap();
}
```

</details>

### Multithread (Emscripten Only)

If you want to use async work or thread safe functions,
there are additional C source file need to be compiled and linking.
Recommend use CMake directly.

```cmake
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/node_modules/@tybys/emnapi")

add_executable(hello hello.c)

target_link_libraries(hello emnapi-mt)
target_compile_options(hello PRIVATE "-sUSE_PTHREADS=1")
target_link_options(hello PRIVATE
  "-sALLOW_MEMORY_GROWTH=1"
  "-sEXPORTED_FUNCTIONS=\"['_malloc','_free']\""
  "-sUSE_PTHREADS=1"
  "-sPTHREAD_POOL_SIZE=4"
  # try to specify stack size if you experience pthread errors
  "-sSTACK_SIZE=2MB"
  "-sDEFAULT_PTHREAD_STACK_SIZE=2MB"
)
```

```bash
emcmake cmake -DCMAKE_BUILD_TYPE=Release -DEMNAPI_WORKER_POOL_SIZE=4 -G Ninja -H. -Bbuild
cmake --build build
```

## Preprocess Macro Options

### `-DEMNAPI_WORKER_POOL_SIZE=4`

This is [`UV_THREADPOOL_SIZE`](http://docs.libuv.org/en/v1.x/threadpool.html?highlight=UV_THREADPOOL_SIZE) equivalent at compile time, if not predefined, emnapi will read `UV_THREADPOOL_SIZE` from Emscripten [environment variable](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code-environment-variables) at runtime, you can set `UV_THREADPOOL_SIZE` like this:

```js
Module.preRun = Module.preRun || [];
Module.preRun.push(function () {
  if (typeof ENV !== 'undefined') {
    ENV.UV_THREADPOOL_SIZE = '2';
  }
});
```

It represent max of `EMNAPI_WORKER_POOL_SIZE` async work (`napi_queue_async_work`) can be executed in parallel. Default is not defined, read `UV_THREADPOOL_SIZE` at runtime.

You can set both `PTHREAD_POOL_SIZE` and `EMNAPI_WORKER_POOL_SIZE` to `number of CPU cores` in general.
If you use another library function which may create `N` child threads in async work,
then you need to set `PTHREAD_POOL_SIZE` to `EMNAPI_WORKER_POOL_SIZE * (N + 1)`.

This option only has effect if you use `-sUSE_PTHREADS`.
Emnapi will create `EMNAPI_WORKER_POOL_SIZE` threads when initializing,
it will throw error if `PTHREAD_POOL_SIZE < EMNAPI_WORKER_POOL_SIZE && PTHREAD_POOL_SIZE_STRICT == 2`.

See [Issue #8](https://github.com/toyobayashi/emnapi/issues/8) for more detail.

### `-DEMNAPI_NEXTTICK_TYPE=0`

This option only has effect if you use `-sUSE_PTHREADS`, Default is `0`.
Tell emnapi how to delay async work in `uv_async_send` / `uv__async_close`.

- `0`: Use `setImmediate()` (Node.js native `setImmediate` or browser `MessageChannel` and `port.postMessage`)
- `1`: Use `Promise.resolve().then()`

### `-DEMNAPI_USE_PROXYING=1`

This option only has effect if you use `-sUSE_PTHREADS`. Default is `1` if emscripten version `>= 3.1.9`, else `0`.

- `0`

    Use JavaScript implementation to send async work from worker threads, runtime code will access the Emscripten internal `PThread` object to add custom worker message listener.

- `1`:

    Use Emscripten [proxying API](https://emscripten.org/docs/api_reference/proxying.h.html) to send async work from worker threads in C. If you experience something wrong, you can switch set this to `0` and feel free to create an issue.

## Performance compare with Embind

See source code [here](https://github.com/toyobayashi/emnapi/tree/main/packages/bench)

- OS: Windows_NT x64 10.0.22621.963
- CPU: 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz   2.30 GHz
- Memory: 16.0 GB
- Chrome: 108.0.5359.125
- Emscripten: 3.1.28
- emnapi: 0.22.0

```
binding: function () {}
                 embind #emptyFunction x 37,148,158 ops/sec ±0.39% (67 runs sampled)
                 emnapi #emptyFunction x 40,207,668 ops/sec ±0.79% (67 runs sampled)
node-addon-api + emnapi #emptyFunction x 13,871,523 ops/sec ±0.35% (67 runs sampled)
Fastest is emnapi #emptyFunction

binding: function (obj) { return obj }
                 embind #returnParam x 19,230,099 ops/sec ±0.59% (68 runs sampled)
                 emnapi #returnParam x 14,930,264 ops/sec ±0.68% (64 runs sampled)
node-addon-api + emnapi #returnParam x 11,402,133 ops/sec ±0.47% (67 runs sampled)
Fastest is embind #returnParam

binding: function (int) { return copy(int) }
                 embind #convertInteger x 9,178,287 ops/sec ±0.67% (67 runs sampled)
                 emnapi #convertInteger x 9,898,681 ops/sec ±0.56% (66 runs sampled)
node-addon-api + emnapi #convertInteger x 7,888,491 ops/sec ±0.79% (67 runs sampled)
Fastest is emnapi #convertInteger

binding: function (str) { return copy(str) }
                 embind #convertString x 2,574,960 ops/sec ±0.46% (66 runs sampled)
                 emnapi #convertString x 3,412,941 ops/sec ±0.66% (68 runs sampled)
node-addon-api + emnapi #convertString x 2,913,797 ops/sec ±0.62% (67 runs sampled)
Fastest is emnapi #convertString

binding: function (param) { return param.length }
                 embind #ObjectGet x 6,192,531 ops/sec ±0.73% (67 runs sampled)
                 emnapi #ObjectGet x 5,268,653 ops/sec ±0.59% (66 runs sampled)
node-addon-api + emnapi #ObjectGet x 4,828,204 ops/sec ±0.57% (67 runs sampled)
Fastest is embind #ObjectGet

binding: function (obj, key, value) { obj[key] = value }
                 embind #ObjectSet x 11,467,433 ops/sec ±0.55% (67 runs sampled)
                 emnapi #ObjectSet x 9,476,512 ops/sec ±0.91% (66 runs sampled)
node-addon-api + emnapi #ObjectSet x 7,647,341 ops/sec ±0.36% (66 runs sampled)
Fastest is embind #ObjectSet
```
