New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
No native build was found for platform
when using Electron Forge + Webpack
#2464
Comments
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:
I haven't been able to cooerce Electron to load the |
@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 |
I've tried adding
|
We're using the ASAR option to package a production app. On closer inspection it seems the ASAR has an empty Edit: Turning off the |
Thanks for the replies everyone. @chetbox same issue here, adding @mimamuh I followed your guidance and did a fresh clone of electron-react-boilerplate Have you managed to get |
@sh0shinsha Yes, I got it managed to work with electron-react-boilerplate. Have you installed @chetbox Actually I had the same thought: In case you exclude At least electron-react-boilerplate handles it like that. I have to install these native modules into a separated |
@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
If you're using electron-react-boilerplate you should be all set, they handle module linking in the In my case I set Hope this helps, thanks everyone! |
Those instructions for native modules with |
@chetbox it looks like |
@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
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 |
Using This worked:
|
@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.
@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. |
This was a good guide - it seems like the latest ERB has the following line in the 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, ... |
@petertorelli While it's hardly elegant, this note from @chetbox works for me (with
|
@chetbox @NoahAndrews How would this work with Electron Forge during development when there is no I added this to
And I'm still stuck with: 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 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 "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. |
That probably needs to be in |
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 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 What fixed it for me in the end (credit to the this GitHub comment) is running the following commands before Electron's packaging step:
This will first delete all the contents of your Make sure the version of |
@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 First add Then inside 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 |
(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. Things I've tried:
I have a lot of quesdtions, which I've been jotting while reading through this issue and trying various proposed fixes.
|
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 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 ...
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 ...
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 Here, you need to use a 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 |
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: I have verified that |
So, upon further investigation, I've discovered why node-gyp-build fails to find
In this case, __dirname, is set to the root of the |
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:
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:
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:
Which I changed back to:
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
TO
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. |
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 |
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 - |
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 "pkg": {
"assets": [ "node_modules/@serialport/bindings-cpp/prebuilds/**/*" ]
} Adjust the path based on the relative location of your |
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.
The text was updated successfully, but these errors were encountered: