I have a macOS electron app that is based on this electron-forge Webpack Typescript boilerplate, integrated with React, as documented here.
TL;DR
I'm able to spawn a binary node module in dev mode (yarn start
) but not able to in production mode (yarn package
).
A bit further
A binary node module that is being spawn
ed but not imported is not being packed by webpack
.
The Full Problem:
In my code, I use the NodeJS spawn module to run a child process in the background. This child process is an installed dependency node module (btw it's @loadmill/agent npm package, but the problem could be applied to any package that is being called by a binary file, instead of a js file).
spawn('loadmill-agent', ['start', '-t', token])
But I don't explicitly import this package into the code. (i.e there is no
import '@loadmill/agent'
line anywhere in the code)
It works well in development mode. When I run yarn start
, the child process is spawned and I can communicate with it and all is well under the sun.
However, when I package the app and run the same line of code, I get an error.
Uncaught Exception:
Error: spawn loadmill-agent ENOENT
at Process.ChildProcess._handle.onexit (node:internal/child_process:282:19)
at one rrorNT (node:internal/child_process:477:16)
at processTicksAndRejections (node:internal/process/task_queues:83:21)
I searched a bit and found that I can debug spawned process errors in NodeJS like so:
spawn('loadmill-agent', ['start', '-t', token], {
env: { NODE_DEBUG: 'child_process', },
}
);
Now instead of the error popup dialog, I get the actual error output:
/bin/sh: loadmill-agent: command not found
Which means the command is either not installed, or not on the PATH, or not executable without a shell.
Furthermore, the @loadmill/agent
node module was not even packed by webpack as a dependency. I know this because I don't see it in the dependencies of the packaged electron app contents/resources.
To recap:
- The
loadmill-agent
node module is not being packed bywebpack
. - The
spawn
ed process outputs/bin/sh: loadmill-agent: command not found
My assumption of a solution:
- Get webpack to somehow package
@loadmill/agent
. - figure out how to spawn
@loadmill/agent
with-the-right-path-to-binary-file. This issue can probably be resolved by configuring the PATH env var or by using the fix-path npm package.
CodePudding user response:
You can force the inclusion of files in the packaged app using the electron-forge/electron-packager option packagerConfig.extraResource
.
There are alternatives to using extraResource
if you want to get fancier, like manually scripting the copy of the files using one of Forge's hooks. Regardless of what method you use, you may need to include some logic in your app for where to find the binary depending on whether in development mode or packaged mode (e.g. using app.isPackaged
and process.resourcesPath
).
As to the larger question of why the binary is not being included in your app package, it must be a configuration error. Are you using .asar in your app package? Are you sure the loadmill-agent module is installed as a regular (non-dev) local dependency to your app? Meaning it's listed in your package.json under dependencies
and located inside node_modules
?
CodePudding user response:
To those who are interested, I solved my problem. Here is how:
Inspired by this answer to a similar question, I changed a few things in my code for it to work.
- I switched to using @loadmill/agent's programmatic api, instead of their cli script. I mean, I wrote a code that looks something like this:
const { start } = require('@loadmill/agent');
const stop = start({
token: 'INSERT_TOKEN_HERE'
});
// Stop the agent at a later time
in a file I named loadmill-agent.ts
.
- Instead of
spawn
, I used NodeJS'sfork
.fork
requires a path to file, instead of a terminal command. So I gavefork
the path to my new file. Something like this (in main process code):
const childProcess = fork('loadmill-agent', // args & options ...
This obviously still didn't work because of the relative problems in electron apps and in webpack bundled apps
The problem was that after bundling with webpack, I had only 1 index.js
file and it was calling a non-existing loadmill-agent.js
file in the fork
statement above.
- To fix that, I added an entry just for the
loadmill-agent.ts
file, inwebpack.main.config.js
file:
module.exports = {
...
entry: {
index: './src/index.ts',
'loadmill-agent': './src/loadmill-agent.ts',
},
...
}
Now the output of webpack had 2 files, index.js
and loadmill-agent.js
. Nice.
But still the path was wrong for some reason
- I did some debugging, and found out that the path that works for BOTH DEV AND PROD is this:
app.getAppPath() '/.webpack/main/' 'loadmill-agent'
WHY?
Well,
app.getAppPath()
simply
Returns string - The current application directory.
'/.webpack/main/'
is the output path of this built-in webpack configuration.'loadmill-agent'
is the file name (.js
) is implicit here.
- The final result looks something like this:
const childProcess = fork(app.getAppPath() '/.webpack/main/' 'loadmill-agent', // args & options ...
Hope this helps someone in the future, Cheers