| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
Review requested:
|
Sorry, something went wrong.
|
From the PR description this seems super valuable and a great built-in alternative to the weak-node-api library we've been working on to add Node-API support to React Native. As such, I'd be happy for us to adopt this approach over the stuff we have now 👍 The limitation around the module being bound to a single runtime, might not be an issue as a multi-runtime host can inject functions that deal with that internally. I'm left wondering how (if at all) add-ons which are statically linked into the process are affected by this proposal? I guess not at all, as they can still register themselves and call into the global Node-API functions resolved at link time. |
Sorry, something went wrong.
|
nice! node-api symbol visibility has been a pain for us in deno so we'd love to adopt this approach as well. |
Sorry, something went wrong.
| NULL, \ | ||
| {0}, \ | ||
| }; \ | ||
| NAPI_C_CTOR(_register_##modname) { napi_module_register(&_module); } \ |
There was a problem hiding this comment.
We should still support addons compiled with legacy Node-API headers. Could this new v-table approach be opt-in?
Sorry, something went wrong.
There was a problem hiding this comment.
I agree, we should make it opt-in to start with and play with it a bit.
If it works well, then we can promote it to be default at some point.
I considered to use the NAPI_EXPERIMENTAL, but it seems it is not the right approach since we use the NAPI_EXPERIMENTAL for new Node-API functions, and the module loading is an orthogonal process. Thus, a special opt-in flag would be better.
Sorry, something went wrong.
There was a problem hiding this comment.
I have added a new macro NODE_API_MODULE_USE_VTABLE. It is currently used only by two tests.
Sorry, something went wrong.
| @@ -33,17 +32,6 @@ struct uv_loop_s; // Forward declaration. | |||
| #define NAPI_NO_RETURN | |||
| #endif | |||
|
|
|||
| // Used by deprecated registration method napi_module_register. | |||
There was a problem hiding this comment.
For anyone wondering, this was moved into the node_api_types.h.
Sorry, something went wrong.
There was a problem hiding this comment.
I had to move it because the new vtable struct uses it.
Sorry, something went wrong.
|
There might be a potential a potential coordination problem, where the addon tries to initialize itself before the vtable gets injected (set) by the host. I don't see that as an issue when the addon relies on "symbol based" module registration, since the host can ensure to call the initialize function after setting the vtable, but how about an addon trying to call napi_module_register when loaded? |
Sorry, something went wrong.
these should probably be exclusive modes, so napi_module_register wouldn't be called, or could only be called from within the host call to the inject function. |
Sorry, something went wrong.
| @@ -1493,3 +1493,169 @@ node_api_create_buffer_from_arraybuffer(napi_env env, | |||
| } | |||
|
|
|||
| #endif | |||
|
|
|||
| node_api_vtable g_vtable = { | |||
There was a problem hiding this comment.
Should this be const?
Sorry, something went wrong.
There was a problem hiding this comment.
Good point! I will fix it.
Sorry, something went wrong.
There was a problem hiding this comment.
I have added const for the global variables related to the v-tables.
Sorry, something went wrong.
The napi_module_register usage is deprecated. No new code is supposed to use it anymore. I am going to add a conditional flag and restore the deleted test as @legendecas suggested. This test is using the napi_module_register method and it can be used as a show case how to use the Node-API directly when needed. |
Sorry, something went wrong.
Right, I would expect it too, but we should verify and test this scenario. |
Sorry, something went wrong.
|
The issues that I am facing on Mac and Linux are due to the use of real "C" compilers where the inline keyword has a different semantic than in "C++". Changing it to static inline generates its own set of issues. Thus, I am still working on it. |
Sorry, something went wrong.
|
Nice!👍 In Lynx/PrimJS, we are currently using a similar vtable approach to address the needs of multi-runtime injection. However, our previous API was not fully aligned with the Node-API standard, which is a problem I have been working to fix recently. |
Sorry, something went wrong.
It is great to hear it! Any suggestions to improve code in this PR to fit your scenario are welcome. |
Sorry, something went wrong.
Codecov Report❌ Patch coverage is 98.73418% with 5 lines in your changes missing coverage. Please review.
@@ Coverage Diff @@
## main #60916 +/- ##
==========================================
- Coverage 90.28% 90.21% -0.07%
==========================================
Files 730 730
Lines 234802 234963 +161
Branches 43953 44077 +124
==========================================
- Hits 211991 211980 -11
+ Misses 14530 14497 -33
- Partials 8281 8486 +205
... and 35 files with indirect coverage changes 🚀 New features to boost your workflow:
|
Sorry, something went wrong.
| @@ -2,7 +2,8 @@ | |||
| "targets": [ | |||
| { | |||
| "target_name": "binding", | |||
| "sources": [ "binding.c" ] | |||
| "sources": [ "binding.c" ], | |||
| 'defines': [ 'NODE_API_MODULE_USE_VTABLE' ] | |||
There was a problem hiding this comment.
We can add a new target named binding_vtable and verify that the hello_world test can be kept and run in the same process with the new vtable binding.
Sorry, something went wrong.
There was a problem hiding this comment.
Thank you for the suggestion! I had duplicated all the targets and added there the "_vtable" suffix.
Sorry, something went wrong.
|
|
||
| #ifdef NODE_API_MODULE_USE_VTABLE | ||
| #define NODE_API_MODULE_SET_VTABLE_DEFINITION \ | ||
| const node_api_module_vtable* g_node_api_module_vtable = \ |
There was a problem hiding this comment.
Could we add a test that contains two compilation units and this global variable can be properly defined?
Sorry, something went wrong.
There was a problem hiding this comment.
All tests are changed to build addons with and without optional support for v-table. They show that the tests can run in both modes. In one case node-api/1_hello_world I have added a test that supports v-table, but does not implement a fallback implementation when it is missing. It passes in new version of Node.js, but fails in old version.
All other tests pass in the old versions on Node.sj except for the cases when the tests are using APIs that are not part of the previous Node.js version, e.g. Float16.
IMHO, we should run the Node-API tests against the new and old Node.js versions, but it is a subject for another discussion.
Sorry, something went wrong.
|
👍 From ABI stability, adding new function pointers at the end of the struct/V-table is ABI-stable. Lastly, there are some runtimes that support multiple JS engines, and they would need to dispatch to the correct V-table (based on the node_env) -- I believe both PrimJS and OpenHarmony's Native Engine are doing this. This is also one reason why JS engines should not provide Node-API symbols, otherwise they will trigger symbol conflicts. What might be good to consider is adding a function that returns a V-table for a given version. This way, if a Node Addon is using say N-API version 8, and the runtime supports, say, N-API version 10, such node_api_get_vtable(NAPI_VERSION_8, flags) might return a properly initialized V-Table with newer functions replaced with an assertion or something. Just a random idea inspired by an old blog post... edit: If we can bump NAPI_MODULE_VERSION then maybe its the best time to introduce napi_register_module_v2() which takes a 3rd argument: a versioned struct/v-table with function pointers to query the host/runtime (e.g. ask for V-Tables, require other addons, etc.). WDYT? |
Sorry, something went wrong.
@mani3xis , thank you for the feedback and great ideas! In the last few weeks I was working on improving the code and was able to achieve good results that should now enable multiple runtimes in the same executable since the v-table is now exposed from the napi_env as you suggested. |
Sorry, something went wrong.
|
@vmoroz Hi, Could I experimentally try out the solution proposed in this PR in my project first? I’ve been struggling with symbol conflicts between multiple NAPI implementations lately (callstackincubator/react-native-node-api#341), and this solution seems like it could fix my problem—I can’t wait to give it a shot. |
Sorry, something went wrong.
Hi @RobinWuu , yes, feel free to try it. I am curious to know if it works for you. At this point I consider the implementation to be quite complete. Though it is still a subject of a code review, addressing feedback, etc. I cannot predict when and if at all it will be merged to Node.js project. I.e. there is some risk in using it at the early stage. |
Sorry, something went wrong.
|
Beyond my expertise to review, but based on the frictions it clears regarding integrating Node-API into React Native and Lynx, I'm really looking forward to this. Hope it can be merged! |
Sorry, something went wrong.
|
I believe this now has use-cases for:
@toyobayashi would this be useful for your many-target Node-API implementation, emnapi, as well? Hope this can be re-reviewed sometime soon as it would have widespread impact cross-ecosystem. |
Sorry, something went wrong.
|
@shirakaba In emnapi, the legacy approach to function symbol resolution is to directly import most Node-API functions from the JavaScript world using wasm imports (like WebAssembly.instantiate(module, { env: { ...nodeApiSymbols } })). However, the remaining runtime code for the Node-API implemented in C (such as libuv_threadpool, async_work, tsfn, and async_cleanup_hook) must be statically linked into the final wasm file. This means that if emnapi updates its version to modify these functionalities, users must recompile the code. I believe the ideal approach would be:
or
The vtable provides a possibility for the second point. However, there are several potential problems:
Overall, the vtable pattern offers some inspiration for emnapi in solving runtime ABI compatibility issues, and I will actively follow the changes here to achieve the same behavior as much as possible. I believe vtables are of great significance to the Node-API ecosystem. Excellent work! |
Sorry, something went wrong.
|
|
||
| #ifdef NODE_API_MODULE_USE_VTABLE_IMPL | ||
|
|
||
| #ifndef NODE_API_MODULE_NO_VTABLE_FALLBACK |
There was a problem hiding this comment.
Why do we need two approaches of vtable implementation?
Sorry, something went wrong.
There was a problem hiding this comment.
We must support two approaches to enable v-table Node-API modules to work in runtimes that support or not support the v-table. From the PR description:
So, if the runtime supports v-table, then we use the env to get it.
If it does not support it then the module uses a fallback approach where it builds the vtable dynamically for its use.
In case if module does not support vtable, then it continues to bind to the Node-API functions as before.
Sorry, something went wrong.
|
BTW: I'm still really excited about the prospects of this landing some day 😅 It would make building Node-API addons for React Native much more flexible and remove a silly workaround needed to satisfy the Android linker at runtime. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Summary
This PR reverses how Node-API modules bind to runtime functions. Currently, modules depend on Node-API symbols exported by the runtime (e.g., node.exe). With this change, the runtime instead provides Node-API functions to modules via vtables—structs containing function pointers exposed through napi_env and other previously opaque stracts.
This enables runtime-agnostic modules: the same pre-built .node module can be loaded by any Node-API-compatible runtime without recompilation or platform-specific binding hooks. This includes standalone runtimes, embedded scenarios (e.g., libnode inside an application), and even applications hosting multiple embedded JS runtimes simultaneously—each runtime provides its own vtables to the modules it loads.
The Issue
The current Node-API module binding has platform-specific limitations (see abi-stable-node#471):
The Solution
Reverse the dependency: instead of modules binding to runtime symbols, the runtime exposes its API through vtables in napi_env and related handles.
Design
Two vtables: node_api_js_vtable (~120 functions from js_native_api.h) and node_api_module_vtable (~40 functions from node_api.h). Separated because js_native_api.h can be used independently of Node.js-specific APIs.
Exposed via structs: Three previously opaque structs now have concrete definitions with vtable pointers:
Similarly for napi_threadsafe_function__ and napi_async_cleanup_hook_handle__ (for functions which don't take napi_env as their first argument).
Sentinel detection: The sentinel is compared against NODE_API_VT_SENTINEL to detect vtable-enabled runtimes vs. legacy runtimes. Since legacy napi_env__ has a C++ vtable ptr (always aligned) as the first field, the sentinel has the bit 0 set to 1 to avoid any possible matches.
Opt-in: Define NODE_API_MODULE_USE_VTABLE to enable. Node-API functions become static inline wrappers that dispatch through the vtable, with optional fallback to symbol binding for legacy runtimes.
Compatibility
*Fallback can be disabled with NODE_API_MODULE_NO_VTABLE_FALLBACK.
How it works:
N.B.: Multi-runtime environments require vtable-enabled modules. Legacy modules bind to whichever runtime's symbols are visible in the process—they cannot distinguish between runtimes.
Testing
Test Infrastructure Changes
Build Variants
Each test addon is compiled in multiple configurations via binding.gyp:
{ "target_name": "binding" }, // original (no vtable) { "target_name": "binding_vtable", "defines": [ "NODE_API_MODULE_USE_VTABLE" ] }, // vtable with fallbackThe 1_hello_world test also includes additional variants:
{ "target_name": "binding_vtable_nofb", "defines": [ "NODE_API_MODULE_USE_VTABLE", "NODE_API_MODULE_NO_VTABLE_FALLBACK" ] }, // vtable only, no fallback { "target_name": "binding_vtable_noimpl", "defines": [ "NODE_API_MODULE_USE_VTABLE", "NODE_API_MODULE_NO_VTABLE_IMPL" ] }, // direct vtable accessBackward Compatibility Verification
All vtable-enabled tests (with fallback) were run against Node.js v24. Tests pass except those using APIs added after v24 (e.g., Float16). This confirms that the fallback mechanism works correctly with older runtimes.
The PR includes a number of other changes which are going to be split up into separate PRs: #61318, #61320, #61321