Skip to content

Link addons against a generated Hermes stub via --node-api-linkage - #501

Open
kraenhansen wants to merge 3 commits into
nextfrom
claude/bold-rubin-aojs9k
Open

kraenhansen wants to merge 3 commits into
nextfrom
claude/bold-rubin-aojs9k

Conversation

@kraenhansen

@kraenhansen kraenhansen commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

A first pass at #494: an addon can link Node-API against a generated stub of libhermesvm instead of weak-node-api. It then records hermesvm and calls Hermes directly. The existing iOS and Android test-app jobs check this end to end.

What's in here

  • New node-api-stub package:
    • src/node-api-functions.ts (getNodeApiFunctions(), nodeApiHeaders) and generateFunction, both moved from weak-node-api.
    • scripts/generate-stub.ts, which generates generated/node_api_stub.cpp. Every function body is abort(), and there's no WEAK_NODE_API_UNREACHABLE.
    • A CMakeLists.txt defining the node-api-stub target, excluded from all. The library name comes from NODE_API_STUB_LIBRARY_NAME (default hermesvm), and the headers come from NODE_API_INC.
    • It depends only on node-api-headers and zod, not on react-native-node-api or cmake-rn, so it stays out of the existing dev-dependency loop between cmake-rn and weak-node-api.
  • weak-node-api: its generator, and the host's generate-injector.mts, now get the function list from node-api-stub. weak-node-api no longer builds a stub. It no longer exports getNodeApiFunctions, and its node-api-headers dependency moved to node-api-stub. The weak-node-api source it generates is byte-identical to before.
  • --node-api-linkage <weak-node-api|hermes|none>:
    • Defined in packages/host/src/node/cli/options.ts and used by cmake-rn. It replaces --no-weak-node-api-linkage, with no alias.
    • cmake-rn depends on node-api-stub directly.
  • The node-api CMake target (cmake-rn/cmake/node-api-config.cmake, loaded with find_package(node-api REQUIRED CONFIG)):
    • In weak-node-api mode it's the imported weak-node-api library.
    • In hermes mode, the config adds node-api-stub to the addon's own build, once per build, and node-api links it. cmake-rn leaves the stub target out of the prebuilds. A hermes addon doesn't need weak-node-api to be built.
    • In hermes mode the weak-node-api target is still defined whenever its prebuild exists, so addons that name it directly build alongside. node-addon-examples relies on this.
  • E2E:
    • tests/async/binding.gyp is removed. gyp-to-cmake was regenerating tests/async/CMakeLists.txt from it with weak-node-api, which is why the first run failed. The hand-written CMakeLists.txt now links node-api.
    • node-addon-examples builds with --node-api-linkage hermes.
    • Test app (iOS) and Test app (Android) run scripts/verify-hermes-linkage.mts (TypeScript, run with node --run verify:hermes-linkage). It checks that the async prebuild depends on hermesvm and not on weak-node-api. It fails if the real Hermes shipped in the built app is missing any napi_* the addon imports. It warns about Node-API functions in the stub (taken from node-api-headers' v10 list) that the real Hermes doesn't export.

Local verification (Linux)

  • Generated the stub; it defines all 155 Node-API v10 functions.
  • Configured a root project with two addons that both call find_package(node-api), through the new config in hermes mode. The stub is built once, and both addons' only NEEDED entries are libhermesvm.so and libc.
  • verify-hermes-linkage.mts passes on that build and fails on the negative cases (an addon that doesn't link hermesvm, and a library missing the imported symbols).
  • tsc --build, the cmake-rn tests, prettier:check, depcheck, and publint on node-api-stub and cmake-rn all pass.
  • pnpm install --frozen-lockfile accepts the lockfile.
  • ESLint is clean on the changed files.
  • Not checked here:
    • Apple and Android builds aren't possible here.
    • node-addon-examples' own dependencies couldn't be installed (codeload.github.com is blocked in my environment), so its existing verify-prebuilds.mts couldn't be type-checked locally.

Open questions / not done yet

Refs #494

🤖 Generated with Claude Code

https://claude.ai/code/session_01U2xSE4PR7T7WFxexRhihBT

Generate a link-time stub of libhermesvm from getNodeApiFunctions() and
build it alongside weak-node-api. Add `--node-api-linkage
<weak-node-api|hermes|none>` to cmake-rn (defined once in the host),
replacing `--no-weak-node-api-linkage`, and a neutral `node-api-linkage`
CMake target that follows the selected mode.

The async test addon now links the neutral target and node-addon-examples
builds with `--node-api-linkage hermes`, so the iOS and Android test app
jobs exercise an addon calling Hermes directly, and verify its linkage and
imports against the app's real Hermes.

Refs #494

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2xSE4PR7T7WFxexRhihBT
@kraenhansen kraenhansen added Apple 🍎 Anything related to the Apple platform (iOS, macOS, Cocoapods, Xcode, XCFrameworks, etc.) Android 🤖 Anything related to the Android platform (Gradle, NDK, Android SDK) CMake RN Our `cmake` wrapping CLI Host 🏡 Our `react-native-node-api-modules` package Linking 🔗 Discovering and copying prebuilds from packages into the host weak-node-api labels Nov 1, 2026 — with Claude
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2xSE4PR7T7WFxexRhihBT
@kraenhansen kraenhansen self-assigned this Nov 1, 2026
@kraenhansen

Copy link
Copy Markdown
Collaborator Author

Three changes needed here. Please fix the failing jobs first, because they're the proof that this works.

1. async never links Hermes

Test app (iOS) and Test app (Android) both fail in verify-hermes-linkage.sh, because async still links weak-node-api:

Dependencies of tests/async/build/RelWithDebInfo/addon.android.node/x86_64/libaddon.so:
  libweak-node-api.so
##[error]Expected tests/async/build/RelWithDebInfo/addon.android.node/x86_64/libaddon.so to link hermesvm

The change to tests/async/CMakeLists.txt gets overwritten at build time. bootstrap runs gyp-to-cmake --namespaced-targets --weak-node-api ., which regenerates that file from tests/async/binding.gyp. The Android log shows it:

Transforming tests/async/binding.gyp → tests/async/CMakeLists.txt

Make sure the hand-written CMakeLists.txt survives, for example by removing tests/async/binding.gyp if nothing else needs it. This wouldn't have shown up in the mocha suite, since both routes reach Hermes, so keep verify-hermes-linkage.sh as the acceptance check.

2. Move the stub out of weak-node-api

The stub isn't weak-node-api, and an addon on hermes linkage shouldn't need that package to build. The RFC in #447 leaves weak-node-api out, so this matters beyond naming.

Add a new node-api-stub package holding:

  • src/node-api-functions.ts (getNodeApiFunctions()), moved from weak-node-api
  • the generator pieces both need, such as generateFunction
  • the stub generator and its CMake project

Then:

  • weak-node-api's generator and the host's generate-injector.mts get the function list from node-api-stub.
  • cmake-rn depends on node-api-stub directly, with no re-export from the host.
  • Remove BUILD_NODE_API_STUB, the hermesStub* exports and the WEAK_NODE_API_UNREACHABLE define from the stub's generated source.
  • Avoid node-api-stub depending on react-native-node-api, directly or through cmake-rn, so it doesn't join the existing dev-only dependency loop if that's practical.

3. Rename the CMake target to node-api

Use node-api instead of node-api-linkage, so addons read target_link_libraries(addon PRIVATE node-api), and rename the config to node-api-config.cmake. It pairs with weak-node-api, and it doesn't clash with the facade in #496. --node-api-linkage stays as the CLI option, since that chooses the linkage and the target is what gets linked.

Please update the PR description and the changeset to match.

@kraenhansen kraenhansen linked an issue Nov 1, 2026 that may be closed by this pull request
4 tasks
- Add a `node-api-stub` package holding `getNodeApiFunctions()`,
  `generateFunction` and the stub generator with its CMake project.
  weak-node-api's generator and the host's injector generator now read
  the function list from it, and weak-node-api no longer builds a stub.
- In `hermes` mode, cmake-rn adds the stub to the addon's own build
  (and leaves it out of the prebuilds), so the addon doesn't need
  weak-node-api to be built.
- Rename the CMake target to `node-api` (`node-api-config.cmake`).
- Remove `tests/async/binding.gyp`, which made gyp-to-cmake overwrite
  the hand-written CMakeLists.txt with one linking weak-node-api.
- Port verify-hermes-linkage to TypeScript, comparing the real Hermes
  against node-api-headers' symbol list instead of a stub binary.

Refs #494

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U2xSE4PR7T7WFxexRhihBT
@kraenhansen
kraenhansen marked this pull request as ready for review November 1, 2026 23:29

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Android 🤖 Anything related to the Android platform (Gradle, NDK, Android SDK) Apple 🍎 Anything related to the Apple platform (iOS, macOS, Cocoapods, Xcode, XCFrameworks, etc.) CMake RN Our `cmake` wrapping CLI Host 🏡 Our `react-native-node-api-modules` package Linking 🔗 Discovering and copying prebuilds from packages into the host weak-node-api

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Link addons against a generated Hermes stub instead of weak-node-api

2 participants