Open chetbox opened 2 years ago
I think the issue boils down to compatiblity with https://github.com/vercel/webpack-asset-relocator-loader
It is looking for one of the following:
require('bindings')(...)
nbind.init(..)
node-pre-gyp
include patternsI haven't been able to cooerce Electron to load the .node
native libraries by copying them to .webpack/renderer/native_modules/prebuilds/linux-x64
sadly.
@chetbox Did you ever get this working? The moment I added serialport to one of my electron projects, I encountered the same bindings issue. Before I go on a wild goose chase, I'd like to see if you were able to resolve this issue and provide some guidance
Not yet. We'll have to look when we absolutely must upgrade which is not yet. Chase away. Please update us if you make any progress.
I got the same error adding serialport to an electron forge + webpack project.
hey @PredictabilityIsGood @mimamuh any updates? same experience as you guys using electron-builder
You don't need to builder anymore (since v10) but you do need to tell webpack to ignore it https://webpack.js.org/configuration/externals/
@sh0shinsha I switched to electron-react-boilerplate which excludes the serialport libs from the bundler similar as @reconbot mentioned. So simply try to exclude serialport
and I think it should work even with forge + webpack then.
I've tried adding serialport
and @serialport/bindings-cpp
to webpack's externals
but I still get this message in the renderer after running electron-forge make
:
Uncaught Error: Cannot find module 'serialport'
We're using the ASAR option to package a production app. On closer inspection it seems the ASAR has an empty node_modules
folder. Is there anything I need to do to tell Electron Forge to include serialport
in node_modules
inside the ASAR?
Edit: Turning off the ASAR
option still results in an empty node_modules
folder when running electron-forge make
.
Thanks for the replies everyone.
@chetbox same issue here, adding serialport
to externals results in the same message: Cannot find module 'serialport'
@mimamuh I followed your guidance and did a fresh clone of electron-react-boilerplate 4.5.0
and added serialport: 10.4.0
. After packaging I get the same error message as OP:
Have you managed to get serialport
working with electron-react-boilerplate?
@sh0shinsha Yes, I got it managed to work with electron-react-boilerplate. Have you installed serialport
like desribed here in the docs?. This might be your issue.
@chetbox Actually I had the same thought: In case you exclude serialport
from webpacks'
build process then you might have to include the excluded module yourself into electrons build process somehow in case electron (forge) doesn't do so automatically for you, which I doubt it does when I read the docs here. That might explain to me why it's not included in your ASAR folder?
At least electron-react-boilerplate handles it like that. I have to install these native modules into a separated node_modules
folder so that it can be copied it into my app before it is packaged by electron. Check out these docs here, maybe it helps to understand the process.
@mimamuh you saved me mate! Following the steps in the docs you linked regarding how native modules are handled in electron-react-boilerplate set me on the right path.
I'll outline the steps I took to solve my No native build was found for platform
error. I've modeled my project's webpack configs on the ones in electron-react-boilerplate, so these steps may be helpful for those using electron-react-boilerplate or those with a similar webpack setup:
externals: ['serialport']
in webpack config -> in my case I just added it right to webpack.config.main.prod
since I'm not merging configs like in electron-react-boilerplatelibrary: { type: 'commonjs2' }
in webpack config's output
serialport
, npm uninstall serialport
in your root package.json
)cd ./release/app
then npm install serialport
If you're using electron-react-boilerplate you should be all set, they handle module linking in the ./release/app/package.json
: https://github.com/electron-react-boilerplate/electron-react-boilerplate/blob/main/release/app/package.json
In my case I set serialport: =10.4.0
in both the ./release/app/package.json
and devDependencies
in my root package.json
since I'm using an npm workspace monorepo and not the exact folder structure of electron-react-boilerplate. If anyone has a better solution to this I would welcome it but won't pursue it further here since it is outside the scope of serialport
.
Hope this helps, thanks everyone!
@sh0shinsha Yes, I got it managed to work with electron-react-boilerplate. Have you installed
serialport
like desribed here in the docs?. This might be your issue.@chetbox Actually I had the same thought: In case you exclude
serialport
fromwebpacks'
build process then you might have to include the excluded module yourself into electrons build process somehow in case electron (forge) doesn't do so automatically for you, which I doubt it does when I read the docs here. That might explain to me why it's not included in your ASAR folder?At least electron-react-boilerplate handles it like that. I have to install these native modules into a separated
node_modules
folder so that it can be copied it into my app before it is packaged by electron. Check out these docs here, maybe it helps to understand the process.
Those instructions for native modules with electron-react-boilerplate
work in development using serialport
but not when the app is packaged. I'm not sure why and I don't understand how to debug this tooling well enough. Here's a repo with the changes if anyone wants to investigate: https://github.com/chetbox/electron-react-boilerplate
(Note that I had to comment out the renderer IPC code because it caused a crash with contextIsolation: false
.
@chetbox it looks like serialport
isn't present in your ./release/app/package.json
. Let me know if the steps I outlined help.
@sh0shinsha yes, that's the key for Electron React Boilerplate. 👍🏼 Thanks for helping me understand how that build system works.
With Electron Forge I can get a packaged (non-ASAR) build working by copying serialport
and its dependencies into the packaged electron-forge
folder.
yarn package
cp -r node_modules/serialport out/my-new-app-linux-x64/resources/app/node_modules/
cp -r node_modules/@serialport out/my-new-app-linux-x64/resources/app/node_modules/
cp -r node_modules/debug out/my-new-app-linux-x64/resources/app/node_modules/
cp -r node_modules/ms out/my-new-app-linux-x64/resources/app/node_modules/
cp -r node_modules/node-gyp-build out/my-new-app-linux-x64/resources/app/node_modules/
./out/my-new-app-linux-x64/my-new-app
I assume this means the Electron Forge + Webpack build system needs to be better aware of how to handle native modules like this. I think the issue is with @electron-forge/plugin-webpack or with @vercel/webpack-asset-relocator-loader (as I mentioned above) not with serialport
.
Using electron-builder
.
This worked:
// webpack.config.js
externals: {
serialport: "commonjs2 serialport", // Ref: https://copyprogramming.com/howto/electron-and-serial-ports
},
@maneetgoyal Wonderful! This seems to have worked on my project as well.
Do you know specifically why this works? Even in this linked reference, the sentence is pretty vague.
Electron Forge is using electron-rebuild. With the simplest project which only dependents serialport, electron, and electron-rebuild without any bundle tool, It will create @serialport/bindings-cpp/build/Release/.forge-meta but won't build anything.
You don't need to builder anymore (since v10) but you do need to tell webpack to ignore it https://webpack.js.org/configuration/externals/
@reconbot Does it mean the prebuilt is also working on Electron? If yes, I guess we need a config file to tell the prebuilt path. I found https://github.com/serialport/bindings-cpp/commit/16f966233930bc7c7302d2b7a53d70282b42e165 and can't see the same thing after binding was moved to bindings-cpp.
@mimamuh you saved me mate! Following the steps in the docs you linked regarding how native modules are handled in electron-react-boilerplate set me on the right path.
I'll outline the steps I took to solve my
No native build was found for platform
error. I've modeled my project's webpack configs on the ones in electron-react-boilerplate, so these steps may be helpful for those using electron-react-boilerplate or those with a similar webpack setup:1. `externals: ['serialport']` in webpack config -> in my case I just added it right to `webpack.config.main.prod` since I'm not merging configs like in [electron-react-boilerplate](https://github.com/electron-react-boilerplate/electron-react-boilerplate) 2. `library: { type: 'commonjs2' }` in webpack config's `output` 3. (if you've already installed `serialport`, `npm uninstall serialport` in your root `package.json`) 4. `cd ./release/app` then `npm install serialport` * (https://electron-react-boilerplate.js.org/docs/native-modules/#native-modules-in-electron-react-boilerplate) * (https://github.com/electron-react-boilerplate/electron-react-boilerplate/blob/main/.erb/scripts/check-native-dep.js)
If you're using electron-react-boilerplate you should be all set, they handle module linking in the
./release/app/package.json
: https://github.com/electron-react-boilerplate/electron-react-boilerplate/blob/main/release/app/package.jsonIn my case I set
serialport: =10.4.0
in both the./release/app/package.json
anddevDependencies
in my rootpackage.json
since I'm using an npm workspace monorepo and not the exact folder structure of electron-react-boilerplate. If anyone has a better solution to this I would welcome it but won't pursue it further here since it is outside the scope ofserialport
.Hope this helps, thanks everyone!
This was a good guide - it seems like the latest ERB has the following line in the webpack.config.base.ts
:
import { dependencies as externals } from '../../release/app/package.json';
const configuration: webpack.Configuration = {
externals: [...Object.keys(externals || {})],
I stumbled across this thread because I'm still seeing the serialport binding issues despite the externals being already included. Doesn't change if i comment out the above and explicitly set serialport as the only external.
Output:
An unhandled error occurred inside electron-rebuild
node-gyp failed to rebuild '/Users/andrew/code/js/electron/my-cool-app/release/app/node_modules/@serialport/bindings-cpp'.
For more information, rerun with the DEBUG environment variable set to "electron-rebuild".
Error: `make` failed with exit code: 2
Error: node-gyp failed to rebuild '/Users/andrew/code/js/electron/my-cool-app/release/app/node_modules/@serialport/bindings-cpp'.
For more information, rerun with the DEBUG environment variable set to "electron-rebuild".
Error: `make` failed with exit code: 2
at NodeGyp.rebuildModule (/Users/andrew/code/js/electron/my-cool-app/node_modules/electron-rebuild/lib/src/module-type/node-gyp.js:120:19)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async ModuleRebuilder.rebuildNodeGypModule (/Users/andrew/code/js/electron/my-cool-app/node_modules/electron-rebuild/lib/src/module-rebuilder.js:98:9)
at async ModuleRebuilder.rebuild (/Users/andrew/code/js/electron/my-cool-app/node_modules/electron-rebuild/lib/src/module-rebuilder.js:128:14)
at async Rebuilder.rebuildModuleAt (/Users/andrew/code/js/electron/my-cool-app/node_modules/electron-rebuild/lib/src/rebuild.js:149:13)
at async Rebuilder.rebuild (/Users/andrew/code/js/electron/my-cool-app/node_modules/electron-rebuild/lib/src/rebuild.js:112:17)
at async /Users/andrew/code/js/electron/my-cool-app/node_modules/electron-rebuild/lib/src/cli.js:158:9
Error: Command failed: ../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .
at checkExecSyncError (node:child_process:841:11)
at execSync (node:child_process:912:15)
at Object.<anonymous> (/Users/andrew/code/js/electron/my-cool-app/.erb/scripts/electron-rebuild.js:16:11)
at Module._compile (node:internal/modules/cjs/loader:1126:14)
at Module.m._compile (/Users/andrew/code/js/electron/my-cool-app/node_modules/ts-node/src/index.ts:1618:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1180:10)
at Object.require.extensions.<computed> [as .js] (/Users/andrew/code/js/electron/my-cool-app/node_modules/ts-node/src/index.ts:1621:12)
at Module.load (node:internal/modules/cjs/loader:1004:32)
at Function.Module._load (node:internal/modules/cjs/loader:839:12) {
status: 255,
...
Same issue here, but I'm using the electron/electron-forge boilerplate, not the react one. This means there is only the .webpack/{main|renderer}
folders after yarn start
.
@andrewrt @mimamuh Is there a hack to make this work for non react boilerplates?
@petertorelli While it's hardly elegant, this note from @chetbox works for me (with externals: { serialport: 'serialport' }
in webpack.main.config.js
):
With Electron Forge I can get a packaged (non-ASAR) build working by copying
serialport
and its dependencies into the packagedelectron-forge
folder.yarn package cp -r node_modules/serialport out/my-new-app-linux-x64/resources/app/node_modules/ cp -r node_modules/@serialport out/my-new-app-linux-x64/resources/app/node_modules/ cp -r node_modules/debug out/my-new-app-linux-x64/resources/app/node_modules/ cp -r node_modules/ms out/my-new-app-linux-x64/resources/app/node_modules/ cp -r node_modules/node-gyp-build out/my-new-app-linux-x64/resources/app/node_modules/ ./out/my-new-app-linux-x64/my-new-app
@chetbox @NoahAndrews How would this work with Electron Forge during development when there is no out
directory created yet?
I added this to webpack.renderer.config.js
externals: {
serialport: "serialport",
},
And I'm still stuck with: Uncaught ReferenceError: serialport is not defined
Thanks.
@jvolker I meant to come back and clarify what we're actually doing, which is only based on @chetbox's solution. Thanks for the reminder. @petertorelli, maybe this is useful to you too.
We have a copy-to-package-directory.js
file:
const fs = require('fs');
const path = require('path');
// https://stackoverflow.com/a/22185855/4651874
function copyRecursiveSync(src, dest) {
let exists = fs.existsSync(src);
let stats = exists && fs.statSync(src);
let isDirectory = exists && stats.isDirectory();
if (isDirectory) {
console.log(`Making directory ${dest}`);
fs.mkdirSync(dest, { recursive: true });
fs.readdirSync(src).forEach(function(childItemName) {
console.log(`Copying ${childItemName}`);
copyRecursiveSync(path.join(src, childItemName),
path.join(dest, childItemName));
});
} else {
console.log(`Copying ${src} to ${dest}`);
fs.copyFileSync(src, dest);
}
}
// Squirrel won't package up unexpected files unless they are in the "resources" folder
// See https://github.com/electron-userland/electron-forge/issues/135#issuecomment-991376807
module.exports = function(extractPath, electronVersion, platform, arch, done) {
// https://github.com/serialport/node-serialport/issues/2464#issuecomment-1122454950
copyRecursiveSync(path.join('node_modules', 'serialport'), path.join(extractPath, 'resources', 'app', 'node_modules', 'serialport'));
copyRecursiveSync(path.join('node_modules', '@serialport'), path.join(extractPath, 'resources', 'app', 'node_modules', '@serialport'));
copyRecursiveSync(path.join('node_modules', 'debug'), path.join(extractPath, 'resources', 'app', 'node_modules', 'debug'));
copyRecursiveSync(path.join('node_modules', 'ms'), path.join(extractPath, 'resources', 'app', 'node_modules', 'ms'));
copyRecursiveSync(path.join('node_modules', 'node-gyp-build'), path.join(extractPath, 'resources', 'app', 'node_modules', 'node-gyp-build'));
console.log("Done copying files");
done();
}
In package.json
, we configure Forge to run that script:
"forge": {
"packagerConfig": {
"afterExtract": [
"copy-to-package-directory.js"
]
}
}
Thanks, @NoahAndrews.
Unfortunately, it doesn't solve the issue during development though, it seems. The "afterExtract" hook is not being triggered then.
I added this to
webpack.renderer.config.js
externals: { serialport: "serialport", },
That probably needs to be in webpack.main.config.js
.
Thanks. Yes, I can confirm this works using serialport in the main process. Is there any chance to get this working in renderer as well?
@jvolker - While it might work in this instance, that's waaaay too gnarly a hack to carry forward in a shipping project. I'm trying to migrate an Electron app we've been shipping since 2018 to Mac m1. Our "ancient" build environment is still stable so we continue to use it (electron 10 and builder 23) and just punt on m1 support for now (and we can't rely on rosetta due to libusb bandwidth issues). Ideally we're forcing electron-forge to build non-web apps for hardware front-ends (USB, VISA, serial), which is probably a bad idea to force a square peg into a round hole. What we need is a boilerplate packager that is designed for non-web hardware interfacing apps that use electron, and jettison the web stuff! :)
I was struggling with a No native build was found for platform
error message when importing serialport
in Electron's main process. I was using electron-forge
to build my distributable. I also tried using electron-rebuild
manually, to no avail.
I spent a couple of days digging through Stack Overflow posts and GitHub issues and did some experiments. In the end it looks like the issue is caused by electron-rebuild
not deeming serialport
's native node modules necessary to be rebuilt on certain platforms. In my case, electron-rebuild
was rebuilding the serialport
native node modules on my M1 MacBook Pro (arm64), but wasn't rebuilding these modules on a Raspberry Pi 4 Model B (armv7l).
What fixed it for me in the end (credit to the this GitHub comment) is running the following commands before Electron's packaging step:
rm --recursive node_modules/@serialport
npm install --no-save --build-from-source serialport@10.4.0
This will first delete all the contents of your node_modules/@serialport
folder, which contains prebuilt native node modules for several platforms in the bindings-cpp
package. Next, we build serialport
's native node modules from scratch, so that we (hopefully) end up with a native node module that matches our current platform.
Make sure the version of serialport
used in the above command matches the version you depend on!
@glenn-kroeze I'm using serialport from within a fork started by the renderer process (my app can run with or without a renderer, so the same fork can be called from within either, depending on whether a window is created. I support GUI and CLI modes for CI/CD.). This creates all kinds of problems in forge/webpack. I don't think this is a serialport issue at all, but a packager issue.
After struggling a lot with this issue, i was able to resolve using another approach. In my case we need to use usb
and serialport
dependencies.
First add externals: ['usb', 'serialport'],
to webpack.main.config.ts
Then inside forge.config.ts
we added a hook to include these modules on node_modules
internal dependencies
hooks: {
packageAfterPrune: async (forgeConfig, buildPath) => {
console.log(buildPath);
const packageJson = JSON.parse(fs.readFileSync(path.resolve(buildPath, 'package.json')).toString());
packageJson.dependencies = {
serialport: '^10.5.0',
usb: '^2.8.0',
};
fs.writeFileSync(path.resolve(buildPath, 'package.json'), JSON.stringify(packageJson));
return new Promise((resolve, reject) => {
const npmInstall = spawn('yarn', ['install', '--production=true'], {
cwd: buildPath,
stdio: 'inherit',
shell: true,
});
npmInstall.on('close', code => {
if (code === 0) {
resolve();
} else {
reject(new Error('process finished with error code ' + code));
}
});
npmInstall.on('error', error => {
reject(error);
});
});
},
},
Not an elegant solution but works. We cannot use references from original package.json
because these libraries are used from another internal library.
(EDIT) I was able to get a solution working, with some help on electron's discord. I will show the solution that worked for me in a new comment. The following is my original post(which contained many questions) with me editing in the answers to many of my own questions, in case others come along with similar issues and levels of cluelessness to me.
I've been struggling with this issue for a while now, and not having any luck trying the numerous proposed solutions here.
My setup is the same as OP @chetbox (electron-forge
, @electron-forge/plugin-webpack
and serialport
, WITHOUT react).
Things I've tried:
externals: { serialport: "commonjs2 serialport" },
in webpack.config.js
With this, I can use npm start
with no errors, but npm run make
results in a binary that Cannot find module 'serialport'
electron-builder
. I later changed this to just externals: { serialport: "serialport" }
, which works fine. Notcopy-to-package-directory.js
solution hooks
I have a lot of quesdtions, which I've been jotting while reading through this issue and trying various proposed fixes.
I'm still relatively new to electron and even much of the node ecosystem so forgive me if any of these questions are obvious.
1) Q: serialport
is listed under my dependencies
. Am I supposed to ALSO install either of @serialport/bindings-cpp
or @serialport/bindings-interface
?
(EDIT)
A: only serialport
is necessary to require directly in pacakge.json
2) Q: I saw this plugin mentioned in the docs: auto-unpack-natives.
Is this possibly the intended way to package native code such as serialport
?
I tried installing the plugin and copying the example settings for forge.config.js
from that docs page, but this didn't seem to solve anything (Running binaries from npm run make
still says Cannot find module 'serialport'
)
(EDIT)
A: The solution that ended up working for me did NOT require auto-unpack-natives
.
Still no idea if this is even potentially relevant at all. i.e. If the underl;ying issues were resolved (see edited question 4 below), then would this plugin potentially play some part?
3) Q: I'm only concerned with creating a Linux .deb package at the moment. When I run make
, the package is written to out/make/deb/x64/appname_X.X.X_amd64.deb
, but the files are also output to /out/appname-linux-x64/*
Does it make sense to try running the executable straight out of that build dir as a shortcut (/out/appname-linux-x64
), or should I reinstall the .deb each time in order to properly test if serialport was bundled?
(EDIT)
A: Yes, now that I have seriaport being packaged correctly, I can confirm that running straight from the build path works just as well as running after a full .deb install.
4) Q: I think a couple people here have said that it appears to be more of an issue with the packager, than with serialport itself.
If that is the case, then is there an upstream bug to track this? Should an issue be filed on electron-packager
, or electron-forge
, or electron-rebuild
?
(EDIT)
A: A separate issue has already been created on electron forge. According to one comment in that issue, there are two PRs which, once merged, would solve this issue out of the box:
This is an issue with the webpack asset relocator we use. Specifically the fix in cases like this is to either fix the module, or the asset relocator, or both. In this case it required changes in both:
- feat: build and ship ESM js files too serialport/bindings-cpp#88
- [fix: resolve paths used in the node-gyp-build fn vercel/webpack-asset-relocator-loader#168] (https://github.com/vercel/webpack-asset-relocator-loader/pull/168)
If both those PRs land and get shipped, the
serialport
module will work out of the box.
5) Q: For those who have been successful in working around this issue, is there a fully functioning template project anywhere that I can reference? (EDIT) A: I don't need it anymore, but if I find some free time I might try to set one up based on my currently working setup.
Here is the hook snippet I was pointed to on discord, which worked for me
// forge.config.js
...
hooks: {
packageAfterPrune: async (_, buildPath, __, platform) => {
const commands = [
"install",
"--no-package-lock",
"--no-save",
"serialport",
];
return new Promise((resolve, reject) => {
const oldPckgJson = path.join(buildPath, "package.json");
const newPckgJson = path.join(buildPath, "_package.json");
fs.renameSync(oldPckgJson, newPckgJson);
const npmInstall = spawn("npm", commands, {
cwd: buildPath,
stdio: "inherit",
shell: true,
});
npmInstall.on("close", (code) => {
if (code === 0) {
fs.renameSync(newPckgJson, oldPckgJson);
/**
* On windows code signing fails for ARM binaries etc.,
* we remove them here
*/
if (platform === "win32") {
const problematicPaths = [
"android-arm",
"android-arm64",
"darwin-x64+arm64",
"linux-arm",
"linux-arm64",
"linux-x64",
];
problematicPaths.forEach((binaryFolder) => {
fs.rmSync(
path.join(
buildPath,
"node_modules",
"@serialport",
"bindings-cpp",
"prebuilds",
binaryFolder
),
{ recursive: true, force: true }
);
});
}
resolve();
} else {
reject(new Error("process finished with error code " + code));
}
});
npmInstall.on("error", (error) => {
reject(error);
});
});
},
},
And then in webpack.main.config.js
, just:
externals: { serialport: "serialport" },
@mimamuh I'm using the electron-react-boilerplate and I intend to use the data in one of my components. Could you please help me confirm if I'm using it the right way?
In main.ts
...
mainWindow = new BrowserWindow({
show: false,
width: 800,
height: 480,
icon: getAssetPath('icon.png'),
webPreferences: {
preload: app.isPackaged
? path.join(__dirname, 'preload.js')
: path.join(__dirname, '../../.erb/dll/preload.js'),
nodeIntegration: true,
},
autoHideMenuBar: true,
titleBarStyle: 'hidden',
frame: false,
});
const port = new SerialPort({ path: '/dev/ttyAMA0', baudRate: 115200 });
const parser = port.pipe(new ReadlineParser({ delimiter: '\r\n' }));
if (port.isOpen) {
parser.on('data', (data: string) => {
if (data.startsWith('$')) {
mainWindow?.webContents.send('serial-data', data);
}
});
}
...
In preload.ts
...
const electronHandler = {
ipcRenderer: {
sendMessage(channel: Channels, args: unknown[]) {
ipcRenderer.send(channel, args);
},
on(channel: Channels, func: (...args: unknown[]) => void) {
const subscription = (_event: IpcRendererEvent, ...args: unknown[]) =>
func(...args);
ipcRenderer.on(channel, subscription);
return () => {
ipcRenderer.removeListener(channel, subscription);
};
},
once(channel: Channels, func: (...args: unknown[]) => void) {
ipcRenderer.once(channel, (_event, ...args) => func(...args));
},
},
serialData: (callback: any) => ipcRenderer.on('serial-data', callback),
...
In the component
useEffect(() => {
window.electron.serialData((_event: any, data: string) => {
setText(data);
});
}, []);
Although using the externals
configuration above is useful, it requires adding dependencies to the package. json
and ultimately generating a node_modules
, this is not something I can accept. After investigating the cause of this issue, I successfully resolved it.
Here, you need to use a copy webpack plugin
and a string replace loader
to install using the following command
npm i copy-webpack-plugin string-replace-loader -D
The following is the configuration of 'webpack'
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /node-gyp-build\.js$/,
loader: 'string-replace-loader',
options: {
search: /path\.join\(dir, 'prebuilds'/g,
replace: "path.join(__dirname, 'prebuilds'",
}
}
]
},
plugins: [
new CopyWebpackPlugin({
patterns: [
{
from: path.resolve(__dirname, "./node_modules/@serialport/bindings-cpp/prebuilds"),
to: path.resolve(__dirname, "./dist/prebuilds")
}
]
}),
]
}
Hello @NoahAndrews in your copy script you use and mention the resources folder. Is that meant to be the output folder of Webpack? I am using electron-forge and all is generated under .webpack/renderer and .webpack/main so I am not sure where should I move the compiled version of serialport.
Thank you in advance and regards
edit: I am trying to run in development mode edit2: @jvolker did you manage to have it working on development mode? Thanks in advance
Hello all! I'm joining the party on this problem. I'm unclear where this thread has left us with the current state of things. I've tried a few of the workarounds above to no success. I'm running basically the vanilla electron forge webpack + typescript boilderplate on mac. I'm attempting to include serialport in the project and it's leading to nothing but profound sadness.
In development, because of adding serialport to the webpack main externals, dev errors out with: Cannot read properties of undefined (reading 'SerialPort')
. Even the packaged app bundle threw Cannot read properties of undefined (reading 'SerialPort')
. This was after using @thehans 's proposed solution.
I have verified that node_modules/@serialport/bindings-cpp/build/Release/bindings.node is being compiled, which _I think_ is the native built asset for my target. All that said, with just the basic configuration, no including @thehans 's workaround, I get
Error: No native build was found for platform=darwin arch=arm64 runtime=electron abi=116 uv=1 armv=8 libc=glibc node=18.15.0 electron=25.3.1 webpack=true. I am at a loss for what to try next in order to ensure that both development works with
electron-forge start` as well as with packaging.
So, upon further investigation, I've discovered why node-gyp-build fails to find bindings.node
. It boils down to this line in bindings-cpp/dist/load-bindings.js:
const binding = (0, node_gyp_build_1.default)((0, path_1.join)(__dirname, '../'));
In this case, __dirname, is set to the root of the .webpack/main
folder rather than the root of node_modules/serialport/node_modules/@serialport/bindings-cpp/. This leads to the checks for bindings.node
failing leading to the error I mentioned previously. I still need to track down where/when __dirname is set, but that's what appears to be going awry, at least for me.
I'm making progress, I believe. I figure I'll keep this updated as I do in hopes it will help someone else out facing this same problem. Turns out webpack has a __dirname polyfill. Without that turned on, __dirname will not behave predictably it seems in the current webpack. I've added:
node: { __dirname: true },
to webpack.main.config.ts which seems to be moving things forward. I can now verify that __dirname is behaving as would be expected. Now I'm facing a new error:
TypeError: Cannot read properties of undefined (reading 'SerialPort')
So it seems like it was finally able to find the native module correctly. Progress is a new error! ;-)
Wow. I think I might cry. I have it working.
Somewhere in the misery of working on this, SerialPort was being included incorrectly. I had:
import SerialPort from 'serialport';
Which I changed back to:
import { SerialPort } from 'serialport';
Which matches the SerialPort documentation. I think the prior import was a vestige of hacking around with my initial attempt at loading SerialPort in the renderer process and experimenting there.
So the punchline to all of my debugging and the solution to at least my situation was:
ADD
node: { __dirname: true },
TO
webpack.main.config.ts
I seriously hope this helps preserve someone else's sanity at the expense of my own.
EDIT: Ok, I just want to note, this has solved my woes for development only. Packaging is still not sorted, but I think that won't be quite the chore that it has been to get development worked out. I'll report back as I continue.
Hi @tkw722 I've not used electron forge much myself, but I believe that the bazecor project recently transitions to electron forge from electron builder. I wonder if any of those changes would be worth reviewing to see if it gives any pointers for problems you are seeing with the prod build
Thanks for sharing your findings @tkw722! I do not recall very well (short memory guy here) but for development I just had to do eval(require(blabla)) and that is...Then for production/bundling I had to do the one of the several after prune hook solution if I am not mistaken
Here is the hook snippet I was pointed to on discord, which worked for me
// forge.config.js ... hooks: { packageAfterPrune: async (_, buildPath, __, platform) => { const commands = [ "install", "--no-package-lock", "--no-save", "serialport", ]; return new Promise((resolve, reject) => { const oldPckgJson = path.join(buildPath, "package.json"); const newPckgJson = path.join(buildPath, "_package.json"); fs.renameSync(oldPckgJson, newPckgJson); const npmInstall = spawn("npm", commands, { cwd: buildPath, stdio: "inherit", shell: true, }); npmInstall.on("close", (code) => { if (code === 0) { fs.renameSync(newPckgJson, oldPckgJson); /** * On windows code signing fails for ARM binaries etc., * we remove them here */ if (platform === "win32") { const problematicPaths = [ "android-arm", "android-arm64", "darwin-x64+arm64", "linux-arm", "linux-arm64", "linux-x64", ]; problematicPaths.forEach((binaryFolder) => { fs.rmSync( path.join( buildPath, "node_modules", "@serialport", "bindings-cpp", "prebuilds", binaryFolder ), { recursive: true, force: true } ); }); } resolve(); } else { reject(new Error("process finished with error code " + code)); } }); npmInstall.on("error", (error) => { reject(error); }); }); }, },
And then in
webpack.main.config.js
, just:externals: { serialport: "serialport" },
Oh hey, my Discord answer made it to GitHub!
I managed to get an Electron Forge + Serialport app built and signed for MacOS (arm and intel), Windows, and Linux so if anyone is stuck or needs help hit me up on discord - .burzo
. Hopefully I'll be able to help.
For react-electron-boilerplate:
Add
externals: ['serialport']
inwebpack.config.main.prod
library: { type: 'commonjs2' }
in webpack config'soutput
inwebpack.config.main.prod
cd ./release/app
thennpm install serialport
Thank you @sh0shinsha, it helped
In case it helps anyone else out who also gets directed to this thread for help with compiling a Node application using serialport using pkg but without React, Electron or Webpack, this is what finally worked for my setup.
Add this to package.json
:
"pkg": {
"assets": [ "node_modules/@serialport/bindings-cpp/prebuilds/**/*" ]
}
Adjust the path based on the relative location of your prebuilds
directory, and optionally include only the platform/architecture(s) you're targeting, but this should package the native binaries in the virtual file system in a way that node-gyp
can load them.
For react-electron-boilerplate:
- Add
externals: ['serialport']
inwebpack.config.main.prod
library: { type: 'commonjs2' }
in webpack config'soutput
inwebpack.config.main.prod
cd ./release/app
thennpm install serialport
Thank you @sh0shinsha, it helped
SerialPort Version
10.4.0
Node Version
14.19.1
Electron Version
17.2.0
Platform
Linux [redacted] 5.13.0-37-generic #42~20.04.1-Ubuntu SMP Tue Mar 15 15:44:28 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
Architecture
x64
Hardware or chipset of serialport
No response
What steps will reproduce the bug?
I'm having trouble using the Electron Forge Typescript + Webpack template to replicate this example: https://github.com/serialport/electron-serialport
yarn create electron-app my-new-app --template=typescript-webpack && cd my-new-app
yarn add serialport tableify
yarn add --dev @types/tableify
renderer.js
intorenderer.ts
index.html
intoindex.html
webPreferences: { nodeIntegration: true, contextIsolation: false }
to the options ofBrowserWindow
inindex.ts
target: 'electron-renderer'
towebpack.renderer.config.js
npx electron-rebuild
yarn start
What happens?
This error appears on the console and Serialport fails to start.
What should have happened?
Serialport should list all serial devices in the Electron window that opens exactly as with https://github.com/serialport/electron-serialport
Additional information
The same issue occurs when running a packaged Electron app using
yarn make
.I have observed the same behaviour using Electron 12.0.9. (This is the project we are using Serialport in.)
A colleague has replicated the issue with Electron 12.0.9 on macOS with an M1 MacBook Pro.