I have the following renderer:
import SerialPort from \"serialport\";
new SerialPort(\"/dev/tty-usbserial1\", { baudRate: 57600 });
It\'s buil
I think @Alec Mev
answer is amazing, at the time of written.
In my case, all of the internet suggested hacks (like copying, loaders, etc..) was unsuccessful and really unclear.
I wanted to call my own native node addon through electron renderer, but as mentioned by Alec Webpack doesn't handle the .node file as required. So in my case:
Folder structure
Electron project dir
│ electron.main.js
│ package.json
│
├───Addon
│ │ addon.js
│ │ package.json
│ │
│ └───dist
│ addon.node
│
└───render-site
│ index.html
│ js.js
│ webpack.config.js
│
└───build
render-site
is your webpack site, dist
is your addon binary location.Very important is to set webpack target to 'electron-renderer'
, it allow you to use require of electron
and other node modules like fs
much more easily.
In your site call the addon like the docs suggested
const addon = require('electron').remote.require('./addon/dist/addon.node');
That's it. You can run the webpack and serve the site while using the addon.
Production is very straight forward, you just make sure to copy the addon to the build of electron (I'm using electron-builder copying system) with the relative path to electron.main.js.
Hope it helped.
I wanna thank @Alec Mev for practically the only answer on the entire internet that truly works and has real knowledge inside. I've spent days troubleshooting issues with webpack and native modules for electron until I tried Alec's answer and it perfectly works.
Just a small addition, that if the native code is being used within the main process and not the renderer (most common case when app security is stronger), the shim needs to be adjusted as following:
module.exports = x =>
__non_webpack_require__(
`${require('electron').app.getAppPath()}/${x}`
);
The first problem is that node-bindings
, which node-serialport
relies on to resolve the path to its Node.js addon, simply doesn't work in Electron. There's an open issue for this, and I don't think the associated PR is even a complete fix, since I've done some debugging, and it appears that fileName
remains undefined
throughout the whole getFileName
.
The second problem: even if it somehow found a serialport.node
somewhere, it wouldn't work after packaging the application for distribution, since the addon itself isn't in the dist
directory, and Webpack can't just bundle it together with the main JS file.
One could attempt to solve this with node-loader, given a correctly working node-bindings
, but that wouldn't help either, since node-bindings
uses elaborate heuristics, which Webpack simply can't extrapolate from, when trying to understand what files could be required by its require
. The only safe thing Webpack could do is include the whole project, "just in case", and that's a certain no-go, obviously, so node-loader
just doesn't copy anything.
So, we need to replace node-bindings
and copy serialport.node
manually.
First, we must grab the addon and put it in dist
. This needs to be done in main's Webpack build, since the renderer is served as web page, potentially from an in-memory file system (so the *.node
file may not be emitted to disk, and Electron will never see it). Here's how:
import CopyWebpackPlugin from "copy-webpack-plugin";
const config = {
// ...
plugins: [
new CopyWebpackPlugin([
"node_modules/serialport/build/Release/serialport.node",
]),
],
// ...
};
Hardcoded, unfortunately, but easy to fix if something changes.
Second, we must substitute node-bindings
with our own shim, src/bindings.js
:
module.exports = x =>
__non_webpack_require__(
`${require("electron").remote.app.getAppPath()}/${x}`
);
__non_webpack_require__
is self-explanatory (yes, plain require
won't work, without some trickery, as it's handled by Webpack), and the require("electron").remote.app.getAppPath()
is necessary because __dirname
doesn't actually resolve to what one would expect - an absolute path to dist
- but rather to some directory buried deep inside Electron.
And here's how the replacement is done, in renderer's Webpack config:
import { NormalModuleReplacementPlugin } from "webpack";
const config = {
// ...
plugins: [
new NormalModuleReplacementPlugin(
/^bindings$/,
`${__dirname}/src/bindings`
),
],
// ...
};
And that's it! Once the above is done, and index.html
+ renderer.js
are being served by some server (or whatever your approach is), and the dist
looks something like this:
dist/
main.js
package.json
serialport.node
electron dist
should "just work".
Could potentially get away with adding node-serialport
as a dependency to the generated dist/package.json
and just npm i
nstalling it in there, and marking serialport
as an external in Webpack, but that feels even dirtier (package version mismatches, etc.).
Another way is to just declare everything as externals, and have electron-packager
just copy the whole production part of node_modules
to dist
for you, but that's a whole lot of megabytes for basically nothing.
I had a similar error when tried to use a native module in electron-renderer
.
Although the error message was something like this in my case:
node-loader: The specified module could not be found
I believe it's all related to node loaders. For me, native-addon-loader was the loader that worked great.
Also, make sure your Webpack target is either node
, electron-main
or electron-renderer
, otherwise it won't work (native modules can't be used on the client side).