node-serialport icon indicating copy to clipboard operation
node-serialport copied to clipboard

`No native build was found for platform` when using Electron Forge + Webpack

Open chetbox opened this issue 3 years ago • 48 comments
trafficstars

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 avatar Mar 25 '22 15:03 chetbox

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.

chetbox avatar Mar 25 '22 17:03 chetbox

@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

PredictabilityIsGood avatar Apr 18 '22 17:04 PredictabilityIsGood

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.

chetbox avatar Apr 18 '22 18:04 chetbox

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

mimamuh avatar Apr 30 '22 10:04 mimamuh

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

sh0shinsha avatar May 10 '22 00:05 sh0shinsha

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 avatar May 10 '22 01:05 reconbot

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

mimamuh avatar May 10 '22 08:05 mimamuh

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 avatar May 10 '22 10:05 chetbox

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.

chetbox avatar May 10 '22 10:05 chetbox

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?

sh0shinsha avatar May 10 '22 11:05 sh0shinsha

@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 avatar May 10 '22 11:05 mimamuh

@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
  • (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!

sh0shinsha avatar May 10 '22 13:05 sh0shinsha

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

chetbox avatar May 10 '22 13:05 chetbox

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

sh0shinsha avatar May 10 '22 14:05 sh0shinsha

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

chetbox avatar May 10 '22 14:05 chetbox

Using electron-builder.

This worked:

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

maneetgoyal avatar Jul 17 '22 11:07 maneetgoyal

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

PredictabilityIsGood avatar Jul 18 '22 15:07 PredictabilityIsGood

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.

shenzhuxi avatar Aug 31 '22 14:08 shenzhuxi

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

...

andrewrt avatar Sep 14 '22 20:09 andrewrt

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 avatar Oct 08 '22 18:10 petertorelli

@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

ItsHarper avatar Oct 21 '22 03:10 ItsHarper

@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 avatar Dec 01 '22 15:12 jvolker

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

ItsHarper avatar Dec 01 '22 17:12 ItsHarper

Thanks, @NoahAndrews.

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

jvolker avatar Dec 02 '22 13:12 jvolker

I added this to webpack.renderer.config.js

externals: {
   serialport: "serialport",
},

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

ItsHarper avatar Dec 02 '22 15:12 ItsHarper

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 avatar Dec 07 '22 11:12 jvolker

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

petertorelli avatar Dec 26 '22 17:12 petertorelli

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 [email protected]

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 avatar Jan 06 '23 09:01 glenn-kroeze

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

petertorelli avatar Jan 06 '23 19:01 petertorelli

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.

rtoscani avatar Feb 24 '23 17:02 rtoscani