Repository navigation
Build addon paths in TypeScript and expose dlopen(module, filename) - #500
Merged
Merged
Conversation
Move platform path construction and the exports cache out of CxxNodeApiHostModule into TypeScript, replacing the native requireNodeAddon with a dlopen(module, filename) matching Node's process.dlopen. requireNodeAddon is rebuilt on top of it. Refs #498 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U2xSE4KqJbTcKKr4FKKUX2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U2xSE4KqJbTcKKr4FKKUX2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U2xSE4KqJbTcKKr4FKKUX2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #498.
What changed
CxxNodeApiHostModuleno longer exposesrequireNodeAddon. It exposesdlopen(module, filename)instead: it creates a freshnapi_env, callshermes_napi_load_module(env, filename)and assigns the result tomodule.exports. It has no cache, so a second call runs the addon's init again, as in Node.nodeAddons_and the#if __APPLE__ / __ANDROID__path construction are gone.src/react-native/index.ts):dlopen(module, filename)export, with the same semantics as Node'sprocess.dlopenand noflags.requireNodeAddon(libraryName)builds the filename itself (lib<name>.soon Android,@rpath/<name>.framework/<name>onios/macos/visionos, and it throws on any other platform). It callsdlopenand cachesmodule.exportsin a module-levelMap. A failed load is not cached. The error message is unchanged:Failed to load '<name>' addon from '<path>': <reason>.HOW-IT-WORKS.md) and aminorchangeset.How exports reach
module.exportsHermes has no public bridge between
napi_valueandjsi::Value. The value is set on the global asRN$NodeAddon_<env ptr>through Node-API, read back through JSI, and then removed withnapi_delete_property. Previously that global stayed set for the whole life of the runtime. Errors are passed back the same way: the exception Hermes left pending is rethrown as the original JS error, not flattened into a string in C++. If no exception is pending, anError("Node-API status N")is created instead.Cache is per runtime
The cache is a module-scoped
Mapin the RN entrypoint. Each JS runtime (for example after a reload) evaluates the bundle again, so it gets its own cache, asnodeAddons_on the per-runtime TurboModule did before.Known differences / notes (from the issue)
hermes_napi_load_modulecreates its own exports object, so themodule.exportswe pass in is overwritten, not handed to init.dlopencall creates a newnapi_envthat lives until runtime teardown.dlopendirectly a second time is now possible, so the reload case from NAPI: hermes_napi_load_module can run another addon's init through lastRegisteredModule meta/hermes#2280 (the wrong init can run for addons that usenapi_module_register) is no longer hidden for those callers.requireNodeAddonstill hides it through its cache.requireNodeAddon<T>keeps its generic return type through an overload signature, so noasassertion is needed. Before this PR, the nativeSpecgeneric did the same job.Testing
tsc --build tsconfig.react-native.json,eslintandprettier --checkpass.CxxNodeApiHostModule.cpppasses aclang++ -fsyntax-onlycheck against React Native's JSI/TurboModule headers and thenode-api-headersheaders.Apple 🍎/Android 🤖jobs or a local run.Generated by Claude Code