How can I bundle ffmpeg in an Electron application - node.js

I'm building an Electron application starting from the electron-webpack boilerplate.
I found this node module #ffmpeg-installer/ffmpeg which installs a compatible precompiled binary into the /node_modules directory then makes the path of that executable accessible through.
const ffmpegPath = require('#ffmpeg-installer/ffmpeg').path
This works fine during development, but when I build the distributable and run it I get an error when attempting to spawn a child process with that path. Presumably, because the path does not point at the binary.
The path is set to the following when running the distributable.
/Users/me/project/dist/mac/AppName.app/Contents/Resources/app.asar/node_modules/#ffmpeg-installer/darwin-x64/ffmpeg
However, when looking in the AppName.app package contents I find the binary in the following path.
/Users/me/project/dist/mac/AppName.app/Contents/Resources/app.asar.unpacked/node_modules/#ffmpeg-installer/darwin-x64/ffmpeg
How should I include binary dependencies in an Electron application using electron-webpack and electron-builder?

From here:
Install: npm i ffmpeg-static ffprobe-static
Include in your package.json:
build{
...
"asarUnpack":[
"node_modules/ffmpeg-static/bin/${os}/${arch}/ffmpeg",
"node_modules/ffmpeg-static/index.js",
"node_modules/ffmpeg-static/package.json"
]
}
Set path in your JS:
const ffmpeg = require('fluent-ffmpeg');
//Get the paths to the packaged versions of the binaries we want to use
const ffmpegPath = require('ffmpeg-static').replace(
'app.asar',
'app.asar.unpacked'
);
const ffprobePath = require('ffprobe-static').path.replace(
'app.asar',
'app.asar.unpacked'
);
//tell the ffmpeg package where it can find the needed binaries.
ffmpeg.setFfmpegPath(ffmpegPath);
ffmpeg.setFfprobePath(ffprobePath);

It's likely because electron will bundle the app in an asar archive (something like a zip/tar/jar). Hence, the path to the executable can't be resolved. Try passing asar: false to electron-builder (in electron-builder.json).

Related

Relative file paths in a global npm package

I have a command line utility that's installed as a global NPM package (privately, within our org).
I've noticed that file paths aren't actually relative to the installed location, but to the current working directory. In CommonJS I used __dirname but that's not valid with ESM.
So, when a user installs the package globally, and runs it, the following code errors because it's trying to use the current working directory.
fs.readFileSync('./templates/controller.ejs', 'utf8')
It works fine when I run the script from within the npm package folder itself. How I can always be sure it points to its install location?
EDIT: So far, the only way to do this is:
import { fileURLToPath } from 'url';
const dirname = fileURLToPath(new URL('.', import.meta.url));
const file = path.resolve(dirname, 'templates/controller.ejs')
fs.readFileSync(file, 'utf8')

Loading Nodejs Module at runtime in electron app

Currently I am playing around with electron using vue-cli-plugin-electron-builder along side a simple vue project. This is the project https://github.com/nklayman/vue-cli-plugin-electron-builder .
vue create my-project
cd my-project
vue add electron-builder
npm run electron:serve
My goal is to add a simple plugin-like architecture. The app serves only base functionality but can be extended with "plugins". Those plugins therefore are not included in the built, but will be loaded at runtime by electron. I would prefere when those plugins just behave like node modules ( module.exports = ) with its own dependencies ( probably with a package.json file inside ). I would locate those plugins at app.getPath('userData') + '/Plugins.
I looked at a few approaches on how to tackle this problem :
1. Using Nodejs vm module
First, I tried using Nodejs vm module to read and execute a script from an external file, all at runtime. It works great so far, although I would not be able to use external dependencies inside those loaded scripts. If I want to use external dependencies inside the plugin scripts, those dependencies must have been included in the electron build beforehand. Somehow defeats the whole purpose of having plugins ... only vanilla js + nodejs base modules would be possible .
2. using global.require
I saw this solution in another SO answer.
Using node require with Electron and Webpack
Webpack/electron require dynamic module
They say to use global.require but it throws an error saying global.require is not a function. The solution looked promising first, but somehow I can't get it to work.
3. simply use require
Of course I had to try it. When I try to require an external module from a non-project location it won't find the module, even if the path is correct. Again, the path I am trying to locate the module should be at app.getPath("userData"), not in the projects root directory. When however, I locate the plugins inside the root directory of the project it gets included in the built. This again defeats the purpose of having plugins.
Goal
So far, I haven't found a viable solution to this. I simply want my electron app to be extendible with basic node modules at runtime ( following a pre-defined schema to simplify ) . Of course there is atom, made with electron, using their own apm manager to install and load plugins, but this seems way to overpowered. Its enough for me to only have plugin files located locally, to have a public "marketplace" is no goal. Also, it's ok if the app has to reload / restart to load plugins.
Any ideas ?
After more and more research I stumbled over 2 packages :
https://www.npmjs.com/package/live-plugin-manager
https://github.com/getstation/electron-package-manager
both integrating npm to programmatically handle package installation at runtime. I settled for live-plugin-manager for now since its better documented and even allow package installation from local file system.
Pro
I was able to integrate the system out-of-the-box into a vanilla electron app. Works like a charm.
Cons
.I was not able to use it inside a vue electron boilerplate (like the one I said I was using in OP), since webpack is interferring with the require environment. But there sure is a solution to this.
Update : I was able to get it to work eventually inside a webpack bundled electron vue boilerplate. I accidentally mixed import and require . The following code works for me using live-plugin-manager
// plugin-loader.js
const path = require('path');
const { PluginManager } = require('live-plugin-manager');
const pluginInstallFolder = path.resolve(app.getPath('userData'), '.plugins');
const pluginManager = new PluginManager();
module.exports = async (pkg) => {
// installs pkg from npm
await pluginManager.install(pkg);
const package = pluginManager.require(pkg);
return package
}
// main.js
const pluginLoader = require('./plugin-loader');
pluginLoader("moment").then((moment) => {
console.log(moment().format());
})
This will install "moment" package from npm during runtime into a local directory and load it into the app, without bundling it into the executable files.

how to require a .js file in node repl

so i usually use ruby irb, and I can pull .rb files I wrote into the console environment by running
load './script.rb'
and then all of the functions I wrote in script.rb will be available.
I cannot figure out for the life of me how to do this in the node "console" environment!
You can load JavaScript files using the require function. The following example assume that the Node.js process was started at the directory where your file is located.
require('./script.js');
This will execute the contents of the file.
If you have exported functions or objects, you can assign them to a variable and use them later.
const myFunction = require('./script.js').myFunction;
myFunction();
Like many other development frameworks/languages, Node has a Modules/Package System which, is a CommonJS variant. To load a Module use require(). The usage of require() is the same when running JavaScript files or running in the REPL.
You can require Node Core Modules, NPM Installed Packages or your own local modules. When loading NPM Packages specified in a package.json or a local module, Node will load them from the Current Working Directory(CWD), you can check this using process.cwd(). The CWD will be set to the absolute path of the directory you launched the REPL from.
You can launch the REPL via running node in your CLI and require your packages like below.
// Core Package
const os = require('os')`
console.log(os)
// NPM Package
const moment = require('moment')
console.log(moment)
// Local Package
const myPackage = require('./myPackage')
console.log(myPackage)
You can also pre-require module(s) using the -r flag when running node. The below will launch the Node REPL with the os package preloaded. You can then access the os package using the variable os
node -r os
console.log(os)
In the future, Node may also support ECMAScript Modules (ie. import). You can read more detailed info about that in the Enhancement Proposal.

Node global module getting current run directory

I'm trying to create a node module that has the ability to be installed globally using npm install -g mymodulename. I've got everything in the module working fine if I run node index.js in the directory of the module, but now I want to make it so that I can publish it to NPM, and it can be installed and run from any directory.
There is some code in my module that looks at certain files in the directory that it is run in. I'm finding that when I do npm install -g ./ and then go into a different directory for a test, then run my-module-command, the relative path that it is reading is from that of where my module got installed (i.e. /usr/local/bin/my-module), not the directory that I'm running it in.
How can my module that is installed globally know where it is being run from? To give an example, I am trying to read the package.json file in the directory I'm in. And it is reading the package.json file of /usr/local/bin/my-module/package.json
I've tried:
__dirname
process.args[1]
process.cwd()
And just calling straight to require('./package.json') directly and none of those work.
Edit here's some code that's breaking in index.js:
#!/usr/bin/env node
var fs = require('fs');
var path = require('path');
var currentDir = path.dirname(require.main.filename);
fs.exists(`${currentDir}/node_modules`, function(dir) {
if (!dir) throw 'node_modules does not exist';
// do stuff
});
In my package.json:
...
"bin": {
"my-module": "./index.js"
},
...
I try to do npm install -g ./ in the project directory, and then I cd into a different directory called /Users/me/Projects/different-project, where another npm project is, and run my-module, and I get node_modules does not exist. When I log out currentDir, I get /usr/local/lib/node_modules/my-module where I'm expecting is to see /Users/me/Projects/different-project.
Have you tried using ./ at the start of your file path? That should give you the current working directory (or calling process.cwd() would work too).
In your case, your code would look like:
fs.exists(`./node_modules`, function(dir) {
if (!dir) throw 'node_modules does not exist';
// do stuff
});
I can see some comments already mention this. Maybe I'm misunderstanding the question? I just had a case where I needed a global module to get the directory I'm running the script from and what I suggested above worked like a charm.

Determine command line working directory when running node bin script

I am creating a node command line interface. It is installed globally and uses a bin file to execute.
I plan to have a command window open at the root directory of the files I am working on and then just run the command however I have been unable to determine the current working directory as process.cwd() is returning the directory of the node package. I initially assumed that since the code is being executed using a batch file as a wrapper (that is how bin files can execute without node at the beginning) then it is impossible but coffee-script manages to do it. I took a look at the coffee-script source but couldn't follow it (not experienced enough).
To test it for yourself create a package with this package.json file:
{
"name": "test-package",
"version": "1.0.0",
"bin": {
"test-package": "./bin/test-package"
},
"main": "/lib/test"
}
this test-package file in bin:
#!/usr/bin/env node
var path = require('path');
var fs = require('fs');
var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
require(lib + '/test');
Could anyone shed some light onto this.
and then try and get the command line directory inside lib/test.
process.cwd() returns directory where command has been executed (not directory of the node package) if it's has not been changed by 'process.chdir' inside of application.
__filename returns absolute path to file where it is placed.
__dirname returns absolute path to directory of __filename.
If you need to load files from your module directory you need to use relative paths.
require('../lib/test');
instead of
var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
require(lib + '/test');
It's always relative to file where it called from and don't depend on current work dir.
Current Working Directory
To get the current working directory, you can use:
process.cwd()
However, be aware that some scripts, notably gulp, will change the current working directory with process.chdir().
Node Module Path
You can get the path of the current module with:
__filename
__dirname
Original Directory (where the command was initiated)
If you are running a script from the command line, and you want the original directory from which the script was run, regardless of what directory the script is currently operating in, you can use:
process.env.INIT_CWD
Original directory, when working with NPM scripts
It's sometimes desirable to run an NPM script in the current directory, rather than the root of the project.
This variable is available inside npm package scripts as:
$INIT_CWD.
You must be running a recent version of NPM. If this variable is not available, make sure NPM is up to date.
This will allow you access the current path in your package.json, e.g.:
scripts: {
"customScript": "gulp customScript --path $INIT_CWD"
}
path.resolve('.') is also a reliable and clean option, because we almost always require('path'). It will give you absolute path of the directory from where it is called.
Alternatively, if you want to solely obtain the current directory of the current NodeJS script, you could try something simple like this. Note that this will not work in the Node CLI itself:
var fs = require('fs'),
path = require('path');
var dirString = path.dirname(fs.realpathSync(__filename));
// output example: "/Users/jb/workspace/abtest"
console.log('directory to start walking...', dirString);
Warning if using ES Modules
According to the ECMAScript modules docs:
CommonJS variables __filename or __dirname are not available in ES modules.
Instead, use cases can be replicated via import.meta.url.
So according to why is __dirname not defined in node?, you can do the following:
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
Here's what worked for me:
console.log(process.mainModule.filename);

Resources