I have a custom API (closed source for now), built in pure C. This API is the client side of a communication protocol (think network-based database queries). I have successfully used node-gyp
to compile the C code wrappers so that the NAPI addon addon.node
can function in JavaScript by calling the C. I have also written a .d.ts
file, so that this addon can work from TypeScript. I have no issue with both of these use cases; ie, doing a tsc index.ts && nodejs index.js
will compile and run the imported/required addon without a hitch, and the client-side operations of my C API happen without a hitch. I have made this addon into a private npm package, and have been using it in small TypeScript projects by installing it as a node_modules
dependency without any issue.
My issue arrives when I try to run a React web app which needs to use this addon. I need React because my data-querying is to be used for GUI-type web applets (that query their data, and update the GUI view, in real-time). I launch my React app with react-scripts start
, like is done with any React project created via webpack/create-react-app. I do not know if there is some other way of launching a ReactJS/ReactTS project than with a react-scripts
-family command. If you know an alternative, I will try it.
My bug exactly:
On the TypeScript side of things: I get a Module not found: Can't resolve '@my-company/my_package/addon' in '/path/to/typescript/srcdirectory'
error when I try to import/require the addon, for the right path. Yes, I am absolutely certain my path is correct (both in package.json
"paths", and the calling source); other (normal .ts
) files in the same dependency directory are found and imported just fine with the same path (and their own filename) from the same .tsx
or .ts
source. Also note that this same path+filename (ie, with addon
and not addon.node
) is recognized just fine by a non-React .ts
file, compiled with tsc
. It is the addon.node
, generated by node-gyp
that is simply not recognized when react-scripts start
handles the TypeScript transpilation.
On the JavaScript side of things: I also tried first generating the corresponding JS file for my TS source with tsc
, and "requiring" my addon; then launching react-scripts
. For the path var api = require("@my-company/my_package/addon");
in the generated JS, I get the same "Module not found" error. For var api = require("@my-company/my_package/addon.node");
, the addon IS recognized, however, the JS fails when I get to any line with api.MyFunction()
for any function, with TypeError: api.MyFunction is not a function
.
I have found the following question on SO, which seems to be related to mine: How to use a native node.js addon in React ; but it is unanswered and might not happen for exactly the same reasons as mine.
My question is: what is the appropriate way to compile an NAPI addon with node-gyp
, packaging it with a certain structure for npm, and importing it from a React JS/TS project so that the imports work ? Should some specific constraints in either the package.json
for the addon, or the one for the React project, be verified so that this can work smoothly? I'm not sure many people have experience with such a use case (going all the way from raw C to ReactTS); but there's bound to be some NAPI addon that's famous enough to be used in some React project: even a link to that might be useful to me.
Any links, ideas, or advice you provide will be greatly appreciated. Thank you for your time.
Edit: After much experimentation and documentation reading, I am now confident that the issue stems from webpack, and need to use externals
in webpack.config.js
to resolve how I import the addon in some way. What is the syntax for both the webpack.config.js
, and how should this "external" lib be imported in my .tsx
files ? Is there anything special to do with my package.json when using a specific Webpack config ?
So I managed to fix my issue. It turns out the problem was more fundamental than I thought. I figured it out through the following links, and understood the solution by asking around on the Reactiflux discord. Since the following questions didn't really explain what solutions are available, I figured I'd post them here.
How to use Native Node Modules on a React, ES6, Electron App?
Cannot load Node native addons with webpack
Basically, native node addons can only be launched from a node server, and not directly from the browser. The browser will NEVER run the code from the node addon itself, for security purposes. Ie, node-gyp/napi aren't some "C to JS" transpiler. If you want something of that kind, look up emscripten
.
A node-only solution to this problem is simply to separate the front-end logic from the back-end logic. By this I mean having on one hand a small node server (serving with express
for example) that handles the things that need to be done by the addon; and on the other hand, having a React front-end client in another project that connects to this server to get whatever data it needs for the front-end (say, by having the server communicate a JSON to the client). This allows the browser to consume the results of the addon's operations in a way where the browser itself doesn't ever run any addon code. As can be seen in the first link, some similar division is to be done for an Electron app, using ipcRenderer
or remote
instead of express
to communicate between the front-end and the back-end parts of the app.