You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@mhdawson adding stuff to env wouldn't work because env is shared among addons, unless we update the fields of env at all points before ceding control to the addon. That, in turn would add to the cost of each callback.
With regards to the index.js, it is an additional step just to receive the module information, which is something that existed before N-API ... isn't this a clear sign of a missing/forgot feature?
@raffaeler we intentionally didn't create a one-to-one mapping of pre-existing API to N-API. Another consequence, had we sent the Napi::Object module into the initializer, would have been that we would have expressed an opinion in favour of CommonJS modules and against ESM modules, which would have driven us to design explicit support for ESM modules later on. We did assume that most npm packages interject an index.js which processes the native bindings and provides a more JavaScript-friendly interface to its dependents. Passing through a JavaScript object and having the native addon decorate it is the lowest common denominator.
I acknowledge that this means that, in the case of your package, you would have no choice but to change the way it initializes itself. To that end I created https://gist.github.com/gabrielschulhof/df079c9a89fe05dfa740b7171e0aa760 to illustrate the way in which I would convey the addon's absolute path into the addon, formulating the whole thing as an npm package that provides the addon to its dependents.
Can you please go over the code and point out the ways in which it would fail under the circumstances in which your addon is being used?
@gabrielschulhof Thanks for the example, but I want to exclude any requirement in terms of a javascript initializer as the initial require can also be made from another native addon. I really don't understand why I can 'expand' __dirname for my module inside C++. It looke like a discordant note.
I am not asking to restore the Module object but just an addon specific object containing the information about the module itself.
A different approach would be a dictionary with all the modules. Not sure about the security implications of this choice.
Going back to my previous comment, the documentation doesn't help in telling me what are the paths that:
are always available in any environment
are giving me some starting point for probing the subfolders.
For example:
what's the best way to find the folder fo the apps' package.json?
is it true that (beyond development time) addons are always inside the node_modules folder?
An additional consideration. I remember to have seen different results (even in javascript) when the addon is packed from webpack.
I strongly believe that:
the documentation should give more info about the possible change of the variable/objects values depending on all these situations (pure node, electron, webpack, etc.)
add to the runtime a number of values that are always available and trustable
I really don't understand why I can 'expand' __dirname for my module inside C++.
@raffaeler Node.js wraps CommonJS modules in the following:
(function(exports,require,module,__filename,__dirname){// original contents of JS file});
and then passes exports, require, module, __filename, and __dirname to the resulting function when executing the module.
AFAIK it does no such thing for native modules, and also AFAIK there is no way to retrieve values of variables that are in scope. I guess it's setting __filename on module and passing that into the native module loader, and that was useful.
I have to say that I'm amazed that there is code out there that loads one native addon from another native addon! Thanks for bringing up this use case! How exactly is that accomplished? Is it done by acquiring and then calling an instance of the require() function, or perhaps by using uv_dlopen() directly?
We should definitely consider formalizing some values we make available to native modules, but we should do so while keeping in mind the diverse nature of the environments in which these modules might be loaded.
We also need to keep in mind that dlopen() may follow symlinks when loading a module, so the path given to dlopen() may not be the path where the module actually resides, and dlopen() doesn't report back the results of its traversal – at least not in a portable way. In this case, if we were to report that path to the module, it would be incorrect.
@gabrielschulhof got it, now I get why __dirname is not available, thanks for the explanation.
How exactly is that accomplished? Is it done by acquiring and then calling an instance of the require() function, or perhaps by using uv_dlopen() directly
I expect my addon can be loaded from other native addons. On my side I did it in other addons by getting process.mainModule.require from C++, but my addon is not currently doing it.
Said that, my addon do load other dlls shipped as part of the addon package (no need for require in this case).
The reason for this is that my addon is a generic host for .NET code (which dynamically generates the code needed to call and marshal from/to javascript and typescript). I dynamically load using dlopen() and once the .NET CLR is loaded it will load other assemblies that will be found in the folders I passed to it.
We should definitely consider formalizing some values we make available to native modules, but we should do so while keeping in mind the diverse nature of the environments in which these modules might be loaded.
That would be great. Glad to have contributed to that.
@raffaeler@gabrielschulhof we've discussed in the recent N-API team meetings and @gabrielschulhof is trying out some different options and will post an update once he's done that.
@gabrielschulhof The fact that the addon initializer returns a single napi_value and takes a parameter napi_value exports is already an expression of favor of CommonJS over ES6 modules. ES6 modules export zero or more individual named values, not exactly one unnamed value like CommonJS and current N-API.
@argv-minus-one the intention with N-API was to get bindings into JS somehow and let JS impose its abstractions, like CommonJS or ESM, on top. The simplest way to achieve that was to transmit one napi_value into JS.
The problem in this case arises from the fact that the native addon is the package entry point, rather than a script.
This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made.
@gabrielschulhof@mhdawson Do you have any update on this?
I still have the need to start from C++ and would be glad to migrate to N-API, but I am stuck on this.
Thank you
We've discussed this and don't see any good options that fit with the abstractions we want to preserve in the API. Since there are alternatives which covers most of the use case, at this point unless there are more requests/cases where it blocks adoption its not in the work we are planning. Alternatives include:
making the exports a function that can receive the module and return the bindings as a function of that -> const addon = require('./build/Release/addon.node')(module); there are then several options of how to create the bindings with knowledge of the module object.
Hi @mhdawson
I can think to other use-cases that will benefit from my request.
For example if you want to host javascript code from native code (or any other language), the starting point cannot be javascript.
(I want to remember that in my case I am not in control of javascript code at all)
Let's see the issue from another perspective.
Instead of the module, could you instead expose the information that the runtime environment has? (I need the addon path but others may be useful as well)
I could see that even many js libraries are struggling in bulding paths without having precise/reliable information.
@raffaeler I have an idea how you can get your add-on's path from the address of a symbol inside your add-on, however, it's platform-dependent. What kinds of platforms do you need to support?
@gabrielschulhof frankly at this stage I can't predict them. For sure Windows and Linux, but since my addon is a kind of "glue" between nodejs and .NET, it can potentially be used anywhere.
BTW by spying file activities I see a lot of addons making deep search through various folder to find themselves. This means that many others have the same need but just didn't ask for it. Making this search leads to poor performance and can potentially break very easily.
It is my belief that an official way to take the path would have beneficial effects on many addons.
I didn't save a list @mhdawson. I installed (and later removed) a bunch of addons to try understanding how could they get the correct folders. Using Windows utility procmon I discovered they were just trying probing entire trees.
BTW if I remember well, not directly related, but node-gyp itself probes debug, release and other folders to search for the native addon just because there is no info about the directory structure
@raffaeler it's not node-gyp that does that but bindings. However, since bindings itself is loaded from another JS module, it is able to compute the package root directory and it searches a hard-coded list of (historical) paths for modules descending from that package root directory.
Activity
gabrielschulhof commentedon Feb 23, 2019
@mhdawson adding stuff to
envwouldn't work becauseenvis shared among addons, unless we update the fields ofenvat all points before ceding control to the addon. That, in turn would add to the cost of each callback.@raffaeler we intentionally didn't create a one-to-one mapping of pre-existing API to N-API. Another consequence, had we sent the
Napi::Object moduleinto the initializer, would have been that we would have expressed an opinion in favour of CommonJS modules and against ESM modules, which would have driven us to design explicit support for ESM modules later on. We did assume that most npm packages interject anindex.jswhich processes the native bindings and provides a more JavaScript-friendly interface to its dependents. Passing through a JavaScript object and having the native addon decorate it is the lowest common denominator.I acknowledge that this means that, in the case of your package, you would have no choice but to change the way it initializes itself. To that end I created https://gist.github.com/gabrielschulhof/df079c9a89fe05dfa740b7171e0aa760 to illustrate the way in which I would convey the addon's absolute path into the addon, formulating the whole thing as an npm package that provides the addon to its dependents.
Can you please go over the code and point out the ways in which it would fail under the circumstances in which your addon is being used?
raffaeler commentedon Feb 23, 2019
@gabrielschulhof Thanks for the example, but I want to exclude any requirement in terms of a javascript initializer as the initial require can also be made from another native addon. I really don't understand why I can 'expand'
__dirnamefor my module inside C++. It looke like a discordant note.I am not asking to restore the Module object but just an addon specific object containing the information about the module itself.
A different approach would be a dictionary with all the modules. Not sure about the security implications of this choice.
Going back to my previous comment, the documentation doesn't help in telling me what are the paths that:
For example:
package.json?node_modulesfolder?raffaeler commentedon Feb 23, 2019
An additional consideration. I remember to have seen different results (even in javascript) when the addon is packed from
webpack.I strongly believe that:
my 2 cents
gabrielschulhof commentedon Feb 23, 2019
@raffaeler Node.js wraps CommonJS modules in the following:
and then passes
exports,require,module,__filename, and__dirnameto the resulting function when executing the module.AFAIK it does no such thing for native modules, and also AFAIK there is no way to retrieve values of variables that are in scope. I guess it's setting
__filenameonmoduleand passing that into the native module loader, and that was useful.I have to say that I'm amazed that there is code out there that loads one native addon from another native addon! Thanks for bringing up this use case! How exactly is that accomplished? Is it done by acquiring and then calling an instance of the
require()function, or perhaps by usinguv_dlopen()directly?We should definitely consider formalizing some values we make available to native modules, but we should do so while keeping in mind the diverse nature of the environments in which these modules might be loaded.
gabrielschulhof commentedon Feb 23, 2019
We also need to keep in mind that
dlopen()may follow symlinks when loading a module, so the path given todlopen()may not be the path where the module actually resides, anddlopen()doesn't report back the results of its traversal – at least not in a portable way. In this case, if we were to report that path to the module, it would be incorrect.raffaeler commentedon Feb 23, 2019
@gabrielschulhof got it, now I get why
__dirnameis not available, thanks for the explanation.I expect my addon can be loaded from other native addons. On my side I did it in other addons by getting
process.mainModule.requirefrom C++, but my addon is not currently doing it.Said that, my addon do load other dlls shipped as part of the addon package (no need for
requirein this case).The reason for this is that my addon is a generic host for .NET code (which dynamically generates the code needed to call and marshal from/to javascript and typescript). I dynamically load using
dlopen()and once the .NET CLR is loaded it will load other assemblies that will be found in the folders I passed to it.That would be great. Glad to have contributed to that.
gabrielschulhof commentedon Feb 25, 2019
Hey! Can you unmute me? I joined by phone.
raffaeler commentedon Feb 25, 2019
@gabrielschulhof I can't ... you only can do it via web interface. Sorry
mhdawson commentedon Mar 4, 2019
@raffaeler @gabrielschulhof we've discussed in the recent N-API team meetings and @gabrielschulhof is trying out some different options and will post an update once he's done that.
argv-minus-one commentedon May 21, 2019
@gabrielschulhof The fact that the addon initializer returns a single
napi_valueand takes a parameternapi_value exportsis already an expression of favor of CommonJS over ES6 modules. ES6 modules export zero or more individual named values, not exactly one unnamed value like CommonJS and current N-API.gabrielschulhof commentedon May 22, 2019
@argv-minus-one the intention with N-API was to get bindings into JS somehow and let JS impose its abstractions, like CommonJS or ESM, on top. The simplest way to achieve that was to transmit one
napi_valueinto JS.The problem in this case arises from the fact that the native addon is the package entry point, rather than a script.
github-actions commentedon Dec 17, 2020
This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made.
raffaeler commentedon Dec 17, 2020
@gabrielschulhof @mhdawson Do you have any update on this?
I still have the need to start from C++ and would be glad to migrate to N-API, but I am stuck on this.
Thank you
mhdawson commentedon Jan 22, 2021
We've discussed this and don't see any good options that fit with the abstractions we want to preserve in the API. Since there are alternatives which covers most of the use case, at this point unless there are more requests/cases where it blocks adoption its not in the work we are planning. Alternatives include:
raffaeler commentedon Jan 22, 2021
Hi @mhdawson
I can think to other use-cases that will benefit from my request.
For example if you want to host javascript code from native code (or any other language), the starting point cannot be javascript.
(I want to remember that in my case I am not in control of javascript code at all)
Let's see the issue from another perspective.
Instead of the module, could you instead expose the information that the runtime environment has? (I need the addon path but others may be useful as well)
I could see that even many js libraries are struggling in bulding paths without having precise/reliable information.
Thanks
gabrielschulhof commentedon Feb 2, 2021
@raffaeler I have an idea how you can get your add-on's path from the address of a symbol inside your add-on, however, it's platform-dependent. What kinds of platforms do you need to support?
raffaeler commentedon Feb 2, 2021
@gabrielschulhof frankly at this stage I can't predict them. For sure Windows and Linux, but since my addon is a kind of "glue" between nodejs and .NET, it can potentially be used anywhere.
BTW by spying file activities I see a lot of addons making deep search through various folder to find themselves. This means that many others have the same need but just didn't ask for it. Making this search leads to poor performance and can potentially break very easily.
It is my belief that an official way to take the path would have beneficial effects on many addons.
mhdawson commentedon Feb 2, 2021
@raffaeler do you have that list of other modules that you can share?
raffaeler commentedon Feb 3, 2021
I didn't save a list @mhdawson. I installed (and later removed) a bunch of addons to try understanding how could they get the correct folders. Using Windows utility procmon I discovered they were just trying probing entire trees.
BTW if I remember well, not directly related, but node-gyp itself probes debug, release and other folders to search for the native addon just because there is no info about the directory structure
gabrielschulhof commentedon Feb 3, 2021
@raffaeler it's not node-gyp that does that but bindings. However, since
bindingsitself is loaded from another JS module, it is able to compute the package root directory and it searches a hard-coded list of (historical) paths for modules descending from that package root directory.But check out nodejs/node#37195 – let's see how it goes 🤷
raffaeler commentedon Feb 10, 2021
Great work @gabrielschulhof
Starting from what version this change will be available?
Thank you
node-api: allow retrieval of add-on file name
node-api: allow retrieval of add-on file name
node-api: allow retrieval of add-on file name
node-api: allow retrieval of add-on file name
node-api: allow retrieval of add-on file name
node-api: allow retrieval of add-on file name
node-api: allow retrieval of add-on file name