Skip to content

Update docs on usage #216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/ANDROID.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@

Because we're using a version of Hermes patched with Node-API support, we need to build React Native from source.

Follow [the React Native documentation on how to build from source](https://reactnative.dev/contributing/how-to-build-from-source#update-your-project-to-build-from-source).

In particular, you will have to edit the `android/settings.gradle` file as follows:

> ```diff
> // ...
> include ':app'
> includeBuild('../node_modules/@react-native/gradle-plugin')
>
> + includeBuild('../node_modules/react-native') {
> + dependencySubstitution {
> + substitute(module("com.facebook.react:react-android")).using(project(":packages:react-native:ReactAndroid"))
> + substitute(module("com.facebook.react:react-native")).using(project(":packages:react-native:ReactAndroid"))
> + substitute(module("com.facebook.react:hermes-android")).using(project(":packages:react-native:ReactAndroid:hermes-engine"))
> + substitute(module("com.facebook.react:hermes-engine")).using(project(":packages:react-native:ReactAndroid:hermes-engine"))
> + }
> + }
> ```

To download our custom version of Hermes, you need to run from your app package:

```
npx react-native-node-api vendor-hermes
```

This will print a path which needs to be stored in `REACT_NATIVE_OVERRIDE_HERMES_DIR` to instruct the React Native Gradle scripts to use it.

Copy link
Preview

Copilot AI Aug 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider adding an example of what the printed path looks like to help developers understand the expected output format.

Suggested change
Example output:

/path/to/your/project/node_modules/.cache/react-native-node-api/hermes-android-0.73.3

Copilot uses AI. Check for mistakes.

This can be combined into a single line:

```
export REACT_NATIVE_OVERRIDE_HERMES_DIR=`npx react-native-node-api vendor-hermes --silent`
```
Expand Down
8 changes: 0 additions & 8 deletions docs/HOW-IT-WORKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,6 @@ module.exports = require("react-native-node-api").requireNodeAddon(
> In the time of writing, this code only supports iOS as passes the path to the library with its .framework.
> We plan on generalizing this soon 🤞

### A note on the need for path-hashing

Notice that the `requireNodeAddon` call doesn't reference the library by it's original name (`prebuild.node`) but instead a name containing a hash.

In Node.js dynamic libraries sharing names can be disambiguated based off their path on disk. Dynamic libraries added to an iOS application are essentially hoisted and occupy a shared global namespace. This leads to collisions and makes it impossible to disambiguate multiple libraries sharing the same name. We need a way to map a require call, referencing the library by its path relative to the JS file, into a unique name of the library once it's added into the application.

To work around this issue, we scan for and copy any library (including its entire xcframework structure with nested framework directories) from the dependency package into our host package when the app builds and reference these from its podspec (as vendored_frameworks). We use a special file in the xcframeworks containing Node-API modules. To avoid collisions we rename xcframework, framework and library files to a unique name, containing a hash. The hash is computed based off the package-name of the containing package and the relative path from the package root to the library file (with any platform specific file extensions replaced with the neutral ".node" extension).

## Transformed code calls into `react-native-node-api`, loading the platform specific dynamic library

The native implementation of `requireNodeAddon` is responsible for loading the dynamic library and allow the Node-API module to register its initialization function, either by exporting a `napi_register_module_v1` function or by calling the (deprecated) `napi_module_register` function.
Expand Down
43 changes: 30 additions & 13 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,15 @@ The app developer has to install both `calculator-lib` as well as `react-native-
The reason for the latter is a current limitation of the React Native Community CLI which doesn't consider transitive dependencies when enumerating packages for auto-linking.

> [!WARNING]
> It's important to match the exact version of the `react-native-node-api` declared as peer dependency by `calculator-lib`.
> It's important to match the version range of the `react-native-node-api` declared as a peer dependency by `calculator-lib`.

For the app to resolve the Node-API dynamic library files, the app developer must update their Metro config to use a `resolveRequest` function exported from `react-native-node-api`:
For the app to resolve the Node-API dynamic library files, the app developer must update their Babel config to use a `requireNodeAddon` function exported from `react-native-node-api`:

```javascript
const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
const nodeApi = require("react-native-node-api/metro-config");
module.exports = mergeConfig(getDefaultConfig(__dirname), {
resolver: { resolveRequest: nodeApi.resolveRequest },
});
module.exports = {
presets: ["module:@react-native/babel-preset"],
plugins: ["module:react-native-node-api/babel-plugin"], // 👈 This needs to be added to the babel.config.js of the app
};
```

At some point the app code will import (or require) the entrypoint of `calculator-lib`:
Expand Down Expand Up @@ -121,18 +120,36 @@ NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) {
}
```

### Build the prebuilt binaries
```cmake
# CMakeLists.txt

```
npx react-native-node-api build ./addon.c
cmake_minimum_required(VERSION 3.15...3.31)
project(addon)

add_compile_definitions(-DNAPI_VERSION=4)

file(GLOB SOURCE_FILES "addon.c")

add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_JS_INC})
target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_JS_LIB})
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)

if(MSVC AND CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET)
# Generate node.lib
execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS})
endif()
```

This is a shorthand command which generates a CMake project from the single source-file and prebuilds for both the Apple and Android platforms. See the [CLI documentation](./CLI.md) for more information on the options available and [documentation on prebuilds](./PREBUILDS.md) for the specifics on their format and structure.
### Build the prebuilt binaries

<!-- TODO: Add a listing of the files produced when running command: Some temp (cached) CMakeList.txt, the CMake project dir, 2x platform specific prebuild directories -->
```
npx cmake-rn
```

### Load and export the native module

Copy link
Preview

Copilot AI Aug 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path './build/Release/addon.node' should be documented to explain that this is the output path from the cmake-rn build process, as it may not be immediately clear to developers where this path comes from.

Suggested change
<!--
The path './build/Release/addon.node' refers to the output file generated by the `cmake-rn` build process.
After running `npx cmake-rn`, the compiled native module will be placed at this location.
-->

Copilot uses AI. Check for mistakes.

```javascript
module.exports = require("./prebuild.node");
module.exports = require("./build/Release/addon.node");
```
Loading