Skip to content
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
14 changes: 7 additions & 7 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
node-version: lts/jod
- run: npm ci
- run: npm run build
- run: npm test --workspace gyp-to-cmake --workspace react-native-node-api-cmake --workspace react-native-node-api-modules
- run: npm test --workspace gyp-to-cmake --workspace cmake-rn --workspace react-native-node-api
test-windows:
name: Run tests on Windows
runs-on: windows-latest
Expand All @@ -39,7 +39,7 @@ jobs:
node-version: lts/jod
- run: npm ci
- run: npm run build
- run: npm test --workspace gyp-to-cmake --workspace react-native-node-api-cmake --workspace react-native-node-api-modules
- run: npm test --workspace gyp-to-cmake --workspace cmake-rn --workspace react-native-node-api

test-macos:
name: Run tests which requires MacOS
Expand All @@ -56,11 +56,11 @@ jobs:
distribution: "temurin"
- name: Setup Android SDK
uses: android-actions/setup-android@v3
# Version here should match the one in React Native template and packages/react-native-node-api-cmake/src/cli.ts
# Version here should match the one in React Native template and packages/cmake-rn/src/cli.ts
- run: sdkmanager --install "ndk;27.1.12297006"
- run: npm ci
- run: npm run build
- run: npm run copy-node-api-headers --workspace react-native-node-api-modules
- run: npm run build-weak-node-api --workspace react-native-node-api-modules
- run: npm run generate-weak-node-api-injector --workspace react-native-node-api-modules
- run: npm test --workspace react-native-node-addon-examples
- run: npm run copy-node-api-headers --workspace react-native-node-api
- run: npm run build-weak-node-api --workspace react-native-node-api
- run: npm run generate-weak-node-api-injector --workspace react-native-node-api
- run: npm test --workspace @react-native-node-api/node-addon-examples
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

<p align="center">
<img src="./docs/logo.svg" width="20%" />
</p>
Expand Down Expand Up @@ -32,7 +31,7 @@ See the document on ["how it works"](./docs/HOW_IT_WORKS.md) for a detailed desc

This mono-repository hosts the development of a few packages:

### `packages/react-native-node-api-modules`
### `packages/react-native-node-api`

Responsible for adding Node-API support to your React Native application:

Expand All @@ -49,19 +48,19 @@ Responsible for adding Node-API support to your React Native application:

Note: We'll sometimes refer to this as the "host package", as it can be seen as a host of Node-API modules in React Native apps.

### `packages/react-native-node-api-cmake`
### `packages/cmake-rn`

A wrapper around CMake making it easier to produce [prebuilt binaries](./docs/PREBUILDS.md) targeting iOS and Android matching the [the prebuilt binary specification](./docs/PREBUILDS.md).

Serves the same purpose as `cmake-js` does for the Node.js community and could potentially be upstreamed into `cmake-js` eventually.

### `packages/gyp-to-cmake`

A tool to transform `binding.gyp` files into `CMakeLists.txt` files, intended for `cmake-js` or `react-native-node-api-cmake` to build from.
A tool to transform `binding.gyp` files into `CMakeLists.txt` files, intended for `cmake-js` or `cmake-rn` to build from.

### `packages/node-addon-examples`

A wrapper around the examples in the [nodejs/node-addon-examples](https://github.com/nodejs/node-addon-examples) repo, using `gyp-to-cmake` and `react-native-node-api-cmake` to prepare prebuilds and scaffolding for loading the addons.
A wrapper around the examples in the [nodejs/node-addon-examples](https://github.com/nodejs/node-addon-examples) repo, using `gyp-to-cmake` and `cmake-rn` to prepare prebuilds and scaffolding for loading the addons.

The main purpose is to use these as tests to verify the implementation. We choose to use this as our first signal for compliance, over the [js-native-api tests in the Node.js project](https://github.com/nodejs/node/tree/main/test/js-native-api), because the examples depends much less on Node.js built-in runtime APIs.

Expand Down
2 changes: 1 addition & 1 deletion apps/test-app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ build/
local.properties
msbuild.binlog

# Ignoring the Podfile.lock as the `react-native-node-api-modules` hash updates too frequently
# Ignoring the Podfile.lock as the `react-native-node-api` hash updates too frequently
Podfile.lock

hermes/
2 changes: 1 addition & 1 deletion apps/test-app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { StyleSheet, Text, View, Button } from "react-native";

/* eslint-disable @typescript-eslint/no-require-imports -- We're using require to defer crashes */

// import { requireNodeAddon } from "react-native-node-api-modules";
// import { requireNodeAddon } from "react-native-node-api";
import nodeAddonExamples from "react-native-node-addon-examples";
// import * as ferricExample from "ferric-example";

Expand Down
4 changes: 2 additions & 2 deletions apps/test-app/babel.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module.exports = {
presets: ['module:@react-native/babel-preset'],
// plugins: [['module:react-native-node-api-modules/babel-plugin', { stripPathSuffix: true }]],
plugins: ['module:react-native-node-api-modules/babel-plugin'],
// plugins: [['module:react-native-node-api/babel-plugin', { stripPathSuffix: true }]],
plugins: ['module:react-native-node-api/babel-plugin'],
};
2 changes: 1 addition & 1 deletion apps/test-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"react": "19.0.0",
"react-native": "0.79.1",
"react-native-node-addon-examples": "*",
"react-native-node-api-modules": "*",
"react-native-node-api": "*",
"react-native-test-app": "^4.3.3"
}
}
2 changes: 1 addition & 1 deletion apps/test-app/react-native.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const project = (() => {
},
// windows: {
// sourceDir: "windows",
// solutionFile: "windows/react-native-node-api-modules-example.sln",
// solutionFile: "windows/react-native-node-api-example.sln",
// },
});
return {
Expand Down
2 changes: 1 addition & 1 deletion docs/ANDROID.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Because we're using a version of Hermes patched with Node-API support, we need to build React Native from source.

```
export REACT_NATIVE_OVERRIDE_HERMES_DIR=`npx react-native-node-api-modules vendor-hermes --silent`
export REACT_NATIVE_OVERRIDE_HERMES_DIR=`npx react-native-node-api vendor-hermes --silent`
```

## Cleaning your React Native build folders
Expand Down
6 changes: 3 additions & 3 deletions docs/AUTO-LINKING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Auto-linking

The `react-native-node-api-modules` package (sometimes referred to as "the host package") has mechanisms to automatically find and link prebuilt binaries with Node-API modules.
The `react-native-node-api` package (sometimes referred to as "the host package") has mechanisms to automatically find and link prebuilt binaries with Node-API modules.

When auto-linking, prebuilt binaries are copied (sometimes referred to as vendored) from dependencies of the app into the host package. As they're copied, they get renamed to avoid conflicts in naming as the library files across multiple dependency packages will be sharing a namespace when building the app.

Expand All @@ -15,12 +15,12 @@ The name of the library when linked / copied into the host is based on two thing

## How do I link Node-API module libraries into my app?

Linking will run when you `pod install` and as part of building your app with Gradle as long as your app has a dependency on the `react-native-node-api-modules` package.
Linking will run when you `pod install` and as part of building your app with Gradle as long as your app has a dependency on the `react-native-node-api` package.

You can also manually link by running the following in your app directory:

```bash
npx react-native-node-api-modules link --android --apple
npx react-native-node-api link --android --apple
```

> [!NOTE]
Expand Down
2 changes: 1 addition & 1 deletion docs/CLI.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# The `react-native-node-api-modules` command-line interface (CLI)
# The `react-native-node-api` command-line interface (CLI)

<!-- TODO: Write detailed documentation of each command and their parameters -->
12 changes: 6 additions & 6 deletions docs/HOW-IT-WORKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ The library has a require call to a `.node` file, which would normally not have
module.exports = require("./prebuild.node");
```

Since the app developer has added the `react-native-node-api-modules/babel-plugin` to their Babel configuration, the require statement gets transformed when the app is being bundled by Metro, into a `requireNodeAddon` call on our TurboModule.
Since the app developer has added the `react-native-node-api/babel-plugin` to their Babel configuration, the require statement gets transformed when the app is being bundled by Metro, into a `requireNodeAddon` call on our TurboModule.

The generated code looks something like this:

```javascript
module.exports = require("react-native-node-api-modules").requireNodeAddon(
module.exports = require("react-native-node-api").requireNodeAddon(
"calculator-lib--prebuild"
);
```
Expand All @@ -37,13 +37,13 @@ In Node.js dynamic libraries sharing names can be disambiguated based off their

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-modules`, loading the platform specific dynamic library
## 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.

In any case the native code stores the initialization function in a data-structure.

## `react-native-node-api-modules` creates a `node_env` and initialize the Node-API module
## `react-native-node-api` creates a `node_env` and initialize the Node-API module

The initialization function of a Node-API module expects a `node_env`, which we create by calling `createNodeApiEnv` on the `jsi::Runtime`.

Expand All @@ -52,11 +52,11 @@ The initialization function of a Node-API module expects a `node_env`, which we
An `exports` object is created for the Node-API module and both the `napi_env` and `exports` object is passed to the Node-API module's initialization function and the third party code is able to call the Node-API free functions:

- The engine-specific functions (see [js_native_api.h](https://github.com/nodejs/node/blob/main/src/js_native_api.h)) are implemented by the `jsi::Runtime` (currently only Hermes supports this).
- The runtime-specific functions (see [node_api.h](https://github.com/nodejs/node/blob/main/src/node_api.h)) are implemented by `react-native-node-api-modules`.
- The runtime-specific functions (see [node_api.h](https://github.com/nodejs/node/blob/main/src/node_api.h)) are implemented by `react-native-node-api`.

## `my-app` regain control and call `add`

When the `exports` object is populated by `calculator-lib`'s Node-API module, control is returned to `react-native-node-api-modules` which returns the `exports` object to JavaScript, with the `add` function defined on it.
When the `exports` object is populated by `calculator-lib`'s Node-API module, control is returned to `react-native-node-api` which returns the `exports` object to JavaScript, with the `add` function defined on it.

```javascript
import { add } from "calculator-lib";
Expand Down
4 changes: 2 additions & 2 deletions docs/PREBUILDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This document codifies the naming and directory structure of prebuilt binaries, expected by the auto-linking mechanism.

At the time of writing, our auto-linking host package (`react-native-node-api-modules`) support two kinds of prebuilds:
At the time of writing, our auto-linking host package (`react-native-node-api`) support two kinds of prebuilds:

## `*.android.node` (for Android)

Expand All @@ -17,7 +17,7 @@ The name of all the `.so` library files:
> [!NOTE]
> The `SONAME` doesn't have to match and is not updated as the .so is copied into the host package.
> This might cause trouble if you're trying to link with the library from other native code.
> We're tracking [#14](https://github.com/callstackincubator/react-native-node-api-modules/issues/14) to fix this 🤞
> We're tracking [#14](https://github.com/callstackincubator/react-native-node-api/issues/14) to fix this 🤞

The directory must have a `react-native-node-api-module` file (the content doesn't matter), to signal that the directory is intended for auto-linking by the `react-native-node-api-module` package.

Expand Down
21 changes: 11 additions & 10 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,27 @@
The purpose of this document is to explain how Node-API modules are supported all the way from an app loading a library package to the library's native code returning a JavaScript value to from a function call.

For the purpose of the explanation, we'll introduce a two fictitious packages:

- `calculator-lib`: A package publishing a Node-API module.
- `my-app`: An app depending on `calculator-lib`.

## Steps needed for the app developer

```bash
npm install --save calculator-lib react-native-node-api-modules
npm install --save calculator-lib react-native-node-api
```

The app developer has to install both `calculator-lib` as well as `react-native-node-api-modules`.
The app developer has to install both `calculator-lib` as well as `react-native-node-api`.
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-modules` declared as peer dependency by `calculator-lib`.
> It's important to match the exact version of the `react-native-node-api` declared as 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-modules`:
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`:

```javascript
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const nodeApi = require("react-native-node-api-modules/metro-config");
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 },
});
Expand All @@ -39,17 +40,17 @@ We will be implementing this `add` function.

## Steps needed for the author of the `calculator-lib` library

### Install `react-native-node-api-modules` as a dev-dependency and declare a peer dependency
### Install `react-native-node-api` as a dev-dependency and declare a peer dependency

```bash
npm install react-native-node-api-modules --save-dev --save-exact
npm install react-native-node-api --save-dev --save-exact
```

Update the package.json of your library to add a peer dependency on the package as well:

```bash
# Update the command to use the exact version you installed as dev-dependency
npm pkg set peerDependencies.react-native-node-api-modules=1.2.3
npm pkg set peerDependencies.react-native-node-api=1.2.3
```

### Implement native code
Expand Down Expand Up @@ -123,7 +124,7 @@ NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) {
### Build the prebuilt binaries

```
npx react-native-node-api-modules build ./addon.c
npx react-native-node-api build ./addon.c
```

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.
Expand Down
12 changes: 7 additions & 5 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config(
globalIgnores([".nx/**"]),
globalIgnores(["**/dist/**"]),
globalIgnores(["apps/test-app/ios/**"]),
globalIgnores(["packages/react-native-node-api-modules/hermes/**"]),
globalIgnores(["packages/host/hermes/**"]),
globalIgnores(["packages/node-addon-examples/examples/**"]),
globalIgnores(["packages/ferric-example/ferric_example.d.ts"]),
eslint.configs.recommended,
tseslint.configs.recommended,
{
files: [
"apps/test-app/*.js",
"packages/node-addon-examples/*.js",
"packages/react-native-node-api-modules/babel-plugin.js",
"packages/react-native-node-api-modules/react-native.config.js"
"packages/host/babel-plugin.js",
"packages/host/react-native.config.js"
],
languageOptions: {
parserOptions: {
Expand All @@ -35,8 +37,8 @@ export default tseslint.config(
{
files: [
"packages/gyp-to-cmake/bin/*.js",
"packages/react-native-node-api-modules/bin/*.mjs",
"packages/react-native-node-api-modules/scripts/*.mjs"
"packages/host/bin/*.mjs",
"packages/host/scripts/*.mjs"
],
languageOptions: {
globals: {
Expand Down
Loading