bundling precompiled binary into electron app

Toby picture Toby · Oct 15, 2015 · Viewed 14.1k times · Source

Is there a good solution on how to include third party pre compiled binaries like imagemagick into an electron app? there are node.js modules but they are all wrappers or native binding to the system wide installed libraries. I wonder if it's possible to bundle precompiled binaries within the distribution.

Answer

UltrasoundJelly picture UltrasoundJelly · Jul 14, 2016

See UPDATE below (this method isn't ideal now).

I did find a solution to this, but I have no idea if this is considered best practice. I couldn't find any good documentation for including 3rd party precompiled binaries, so I just fiddled with it until it finally worked with my ffmpeg binary. Here's what I did (starting with the electron quick start, node.js v6):

Mac OS X method

From the app directory I ran the following commands in Terminal to include the ffmpeg binary as a module:

mkdir node_modules/ffmpeg
cp /usr/local/bin/ffmpeg node_modules/ffmpeg/
cd node_modules/.bin
ln -s ../ffmpeg/ffmpeg ffmpeg

(replace /usr/local/bin/ffmpeg with your current binary path, download it from here) Placing the link allowed electron-packager to include the binary I saved to node_modules/ffmpeg/.

Then to get the bundled app path (so that I could use an absolute path for my binary... relative paths didn't seem to work no matter what I did) I installed the npm package app-root-dir by running the following command:

npm i -S app-root-dir

Now that I had the root app directory, I just append the subfolder for my binary and spawned from there. This is the code that I placed in renderer.js:.

var appRootDir = require('app-root-dir').get();
var ffmpegpath=appRootDir+'/node_modules/ffmpeg/ffmpeg';
console.log(ffmpegpath);

const
    spawn = require( 'child_process' ).spawn,
    ffmpeg = spawn( ffmpegpath, ['-i',clips_input[0]]);  //add whatever switches you need here

ffmpeg.stdout.on( 'data', data => {
     console.log( `stdout: ${data}` );
    });
   ffmpeg.stderr.on( 'data', data => {
console.log( `stderr: ${data}` );
    });

Windows Method

  1. Open your electron base folder (electron-quick-start is the default name), then go into the node_modules folder. Create a folder there called ffmpeg, and copy your static binary into this directory. Note: it must be the static version of your binary, for ffmpeg I grabbed the latest Windows build here.

  2. To get the bundled app path (so that I could use an absolute path for my binary... relative paths didn't seem to work no matter what I did) I installed the npm package app-root-dir by running the following command from a command prompt in my app directory:

     npm i -S app-root-dir
    
  3. Within your node_modules folder, navigate to the .bin subfolder. You need to create a couple of text files here to tell node to include the binary exe file you just copied. Use your favorite text editor and create two files, one named ffmpeg with the following contents:

    #!/bin/sh
    basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
    
    case `uname` in
        *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
    esac
    
    if [ -x "$basedir/node" ]; then
      "$basedir/node"  "$basedir/../ffmpeg/ffmpeg" "$@"
      ret=$?
    else
      node  "$basedir/../ffmpeg/ffmpeg" "$@"
      ret=$?
    fi
    exit $ret
    

And the the second text file, named ffmpeg.cmd:

    @IF EXIST "%~dp0\node.exe" (
     "%~dp0\node.exe"  "%~dp0\..\ffmpeg\ffmpeg" %*
    ) ELSE (
       @SETLOCAL
     @SET PATHEXT=%PATHEXT:;.JS;=;%
     node  "%~dp0\..\ffmpeg\ffmpeg" %*
    )

Next you can run ffmpeg in your Windows electron distribution (in renderer.js) as follows (I'm using the app-root-dir node module as well). Note the quotes added to the binary path, if your app is installed to a directory with spaces (eg C:\Program Files\YourApp) it won't work without these.

var appRootDir = require('app-root-dir').get();
var ffmpegpath = appRootDir + '\\node_modules\\ffmpeg\\ffmpeg';

const
    spawn = require( 'child_process' ).spawn;
    var ffmpeg = spawn( 'cmd.exe', ['/c',  '"'+ffmpegpath+ '"', '-i', clips_input[0]]);  //add whatever switches you need here, test on command line first
ffmpeg.stdout.on( 'data', data => {
     console.log( `stdout: ${data}` );
 });
ffmpeg.stderr.on( 'data', data => {
     console.log( `stderr: ${data}` );
 });

UPDATE: Unified Simple Method

Well, as time as rolled on and Node has updated, this method is no longer the easiest way to include precompiled binaries. It still works, but when npm install is run the binary folders under node_modules will be deleted and have to be replaced again. The below method works for Node v12.

This new method obviates the need to symlink, and works similarly for Mac and Windows. Relative paths seem to work now.

  1. You will still need appRootDir: npm i -S app-root-dir

  2. Create a folder under your app's root directory named bin and place your precompiled static binaries here, I'm using ffmpeg as an example.

  3. Use the following code in your renderer script:

const appRootDir = require('app-root-dir').get();
const ffmpegpath = appRootDir + '/bin/ffmpeg';
const spawn = require( 'child_process' ).spawn;
const child = spawn( ffmpegpath, ['-i', inputfile, 'out.mp4']);  //add whatever switches you need here, test on command line first
child.stdout.on( 'data', data => {
    console.log( `stdout: ${data}` );
});
child.stderr.on( 'data', data => {
    console.log( `stderr: ${data}` );
});