Skip to content
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

Open
chetbox opened this issue Mar 25, 2022 · 45 comments
Open
Labels

Comments

@chetbox
Copy link

chetbox commented Mar 25, 2022

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
  • Copy the contents of renderer.js into renderer.ts
  • Copy the contents of index.html into index.html
  • Add webPreferences: { nodeIntegration: true, contextIsolation: false } to the options of BrowserWindow in index.ts
  • target: 'electron-renderer' to webpack.renderer.config.js
  • npx electron-rebuild
  • yarn start

What happens?

This error appears on the console and Serialport fails to start.

Uncaught Error: No native build was found for platform=linux arch=x64 runtime=electron abi=101 uv=1 libc=glibc node=16.13.0 electron=17.2.0 webpack=true
    loaded from: [redacted]/my-new-app/node_modules/electron/dist/resources/electron.asar

    at Function.load.path (index.js?04e8:6:99)
    at load (index.js?04e8:6:99)
    at eval (load-bindings.js?bdc2:10:1)
    at Object../node_modules/@serialport/bindings-cpp/dist/load-bindings.js (index.js:85:1)
    at __webpack_require__ (index.js:841:33)
    at fn (index.js:1028:21)
    at eval (darwin.js?fd34:8:25)
    at Object../node_modules/@serialport/bindings-cpp/dist/darwin.js (index.js:30:1)
    at __webpack_require__ (index.js:841:33)
    at fn (index.js:1028:21)

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.

@chetbox
Copy link
Author

chetbox commented Mar 25, 2022

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 patterns

I 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.

@PredictabilityIsGood
Copy link

PredictabilityIsGood commented Apr 18, 2022

@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

@chetbox
Copy link
Author

chetbox commented Apr 18, 2022

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.

@mimamuh
Copy link

mimamuh commented Apr 30, 2022

I got the same error adding serialport to an electron forge + webpack project.

@sh0shinsha
Copy link

hey @PredictabilityIsGood @mimamuh any updates? same experience as you guys using electron-builder

@reconbot
Copy link
Member

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/

@mimamuh
Copy link

mimamuh commented May 10, 2022

@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.

@chetbox
Copy link
Author

chetbox commented May 10, 2022

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'

@chetbox
Copy link
Author

chetbox commented May 10, 2022

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.

@sh0shinsha
Copy link

sh0shinsha commented May 10, 2022

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:

image

Have you managed to get serialport working with electron-react-boilerplate?

@mimamuh
Copy link

mimamuh commented May 10, 2022

@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.

@sh0shinsha
Copy link

sh0shinsha commented May 10, 2022

@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
  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

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!

@chetbox
Copy link
Author

chetbox commented May 10, 2022

@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.

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.

@sh0shinsha
Copy link

@chetbox it looks like serialport isn't present in your ./release/app/package.json. Let me know if the steps I outlined help.

@chetbox
Copy link
Author

chetbox commented May 10, 2022

@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.

@maneetgoyal
Copy link

Using electron-builder.

This worked:

// webpack.config.js
    externals: {
      serialport: "commonjs2 serialport", // Ref: https://copyprogramming.com/howto/electron-and-serial-ports
    },

@PredictabilityIsGood
Copy link

@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.

@shenzhuxi
Copy link

shenzhuxi commented Aug 31, 2022

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 serialport/bindings-cpp@16f9662 and can't see the same thing after binding was moved to bindings-cpp.

@andrewrt
Copy link

@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.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!

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,

...

@petertorelli
Copy link

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?

@NoahAndrews
Copy link
Contributor

@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 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

@jvolker
Copy link

jvolker commented Dec 1, 2022

@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.

@NoahAndrews
Copy link
Contributor

NoahAndrews commented Dec 1, 2022

@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"
    ]
  }
}

@jvolker
Copy link

jvolker commented Dec 2, 2022

Thanks, @NoahAndrews.

Unfortunately, it doesn't solve the issue during development though, it seems. The "afterExtract" hook is not being triggered then.

@NoahAndrews
Copy link
Contributor

NoahAndrews commented Dec 2, 2022

I added this to webpack.renderer.config.js

externals: {
   serialport: "serialport",
},

That probably needs to be in webpack.main.config.js.

@jvolker
Copy link

jvolker commented Dec 7, 2022

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?

@petertorelli
Copy link

@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! :)

@glenn-kroeze
Copy link

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!

@petertorelli
Copy link

@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.

@rtoscani
Copy link

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.

@thehans
Copy link

thehans commented Apr 18, 2023

(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'
    • (EDIT) I realized later that they said this was for electron-builder. I later changed this to just externals: { serialport: "serialport" }, which works fine. Not
  • @NoahAndrews' copy-to-package-directory.js solution
  • @rtoscani's hooks
  • @glenn-kroeze's rebuild trick

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:

    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.

@thehans
Copy link

thehans commented Apr 20, 2023

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" },

@deyemiobaa
Copy link

@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);
    });
  }, []);

@zeng-hang
Copy link

zeng-hang commented May 18, 2023

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")
        }
      ]
    }),
  ]
}

@javierguzman
Copy link

javierguzman commented Jun 8, 2023

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

@tkw722
Copy link

tkw722 commented Jul 24, 2023

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.

@tkw722
Copy link

tkw722 commented Jul 24, 2023

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.

@tkw722
Copy link

tkw722 commented Jul 24, 2023

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! ;-)

@tkw722
Copy link

tkw722 commented Jul 24, 2023

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.

@GazHank
Copy link
Contributor

GazHank commented Jul 24, 2023

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

@javierguzman
Copy link

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

@Burzo
Copy link

Burzo commented Sep 5, 2023

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.

@andrey-pr
Copy link

For react-electron-boilerplate:

  1. Add externals: ['serialport'] in webpack.config.main.prod

  2. library: { type: 'commonjs2' } in webpack config's output in webpack.config.main.prod

  3. cd ./release/app then npm install serialport

Thank you @sh0shinsha, it helped

@EDais
Copy link

EDais commented Feb 27, 2024

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.

@AviadCohen24
Copy link

AviadCohen24 commented Mar 4, 2024

For react-electron-boilerplate:

  1. Add externals: ['serialport'] in webpack.config.main.prod
  2. library: { type: 'commonjs2' } in webpack config's output in webpack.config.main.prod
  3. cd ./release/app then npm install serialport

Thank you @sh0shinsha, it helped

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests