将预编译的二进制文件捆绑到电子应用程序中

Tob*_*oby 29 imagemagick node.js electron

有关如何将第三方预编译的二进制文件(如imagemagick)包含在电子应用程序中的解决方案吗?有node.js模块,但它们都是包装器或本机绑定到系统范围内安装的库.我想知道是否可以在分发中捆绑预编译的二进制文件.

Ult*_*lly 14

我确实找到了解决方案,但我不知道这是否是最佳实践.我无法找到包括第三方预编译的二进制任何好的文档,所以我只是拨弄着它,直到它最后用我的ffmpeg的二进制工作.这就是我所做的(从电子快速启动开始,node.js v6):

Mac OS X方法

在app目录中,我在Terminal中运行了以下命令,将ffmpeg二进制文件包含为模块:

mkdir node_modules/ffmpeg
cp /usr/local/bin/ffmpeg node_modules/ffmpeg/
cd node_modules/.bin
ln -s ../ffmpeg/ffmpeg ffmpeg
Run Code Online (Sandbox Code Playgroud)

(替换/usr/local/bin/ffmpeg为您当前的二进制路径,从此处下载)放置链接允许electron-packager包含我保存到的二进制文件node_modules/ffmpeg/.

然后获取捆绑的应用程序路径(这样我可以使用我的二进制文件的绝对路径......无论我做什么,相对路径似乎都不起作用)我通过运行以下命令安装了npm包app-root-dir命令:

npm i -S app-root-dir
Run Code Online (Sandbox Code Playgroud)

现在我有了根应用程序目录,我只是为我的二进制文件追加子文件夹并从那里生成.这是我放在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}` );
    });
Run Code Online (Sandbox Code Playgroud)

Windows方法

  1. 打开电子基础文件夹(electron-quick-start是默认名称),然后进入node_modules文件夹.创建一个名为ffmpeg的文件夹,并将静态二进制文件复制到此目录中.注意:它必须是二进制文件的静态版本,对于ffmpeg,我在这里抓住了最新的Windows版本.
  2. 要获得捆绑的应用程序路径(以便我可以使用绝对路径来实现我的二进制...相对路径似乎无论我做什么都不起作用)我通过运行以下命令安装了npm包app-root-dir从我的app目录中的命令提示符:

     npm i -S app-root-dir
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在node_modules文件夹中,导航到.bin子文件夹.您需要在此处创建几个文本文件,以告知节点包含刚刚复制的二进制exe文件.使用您喜欢的文本编辑器创建两个文件,其中一个文件名为ffmpeg以下内容:

    #!/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
    
    Run Code Online (Sandbox Code Playgroud)

    第二个文本文件名为ffmpeg.cmd:

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

接下来,您可以在Windows电子分发中(在renderer.js中)运行ffmpeg,如下所示(我也使用app-root-dir节点模块).请注意添加到二进制路径的引号,如果您的应用程序安装到带有空格的目录(例如C:\Program Files\YourApp),如果没有这些,它将无法工作.

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}` );
 });
Run Code Online (Sandbox Code Playgroud)


tsu*_*iga 13

这是另一种方法,到目前为止已经使用Mac和Windows进行了测试.需要'app-root-dir'包,不需要手动向node_modules dir添加任何内容.

  1. 将您的文件放在resources/$ os /下,其中$ os"mac","linux""win".构建过程将根据构建目标OS从这些目录中复制文件.

  2. extraFiles选择在您的构建CONFIGS如下:

的package.json

  "build": {
    "extraFiles": [
      {
        "from": "resources/${os}",
        "to": "Resources/bin",
        "filter": ["**/*"]
      }
    ],
Run Code Online (Sandbox Code Playgroud)
  1. 使用这样的东西来确定当前的平台.

得到-platform.js

import { platform } from 'os';

export default () => {
  switch (platform()) {
    case 'aix':
    case 'freebsd':
    case 'linux':
    case 'openbsd':
    case 'android':
      return 'linux';
    case 'darwin':
    case 'sunos':
      return 'mac';
    case 'win32':
      return 'win';
  }
};
Run Code Online (Sandbox Code Playgroud)
  1. 根据env和OS调用应用程序中的可执行文件.这里我假设构建版本处于生产模式,源代码版本处于其他模式,但您可以创建自己的调用逻辑.
import { join as joinPath, dirname } from 'path';
import { exec } from 'child_process';

import appRootDir from 'app-root-dir';

import env from './env';
import getPlatform from './get-platform';

const execPath = (env.name === 'production') ?
  joinPath(dirname(appRootDir.get()), 'bin'):
  joinPath(appRootDir.get(), 'resources', getPlatform());

const cmd = `${joinPath(execPath, 'my-executable')}`;

exec(cmd, (err, stdout, stderr) => {
  // do things
});
Run Code Online (Sandbox Code Playgroud)

我认为我使用电子生成器作为基础,env文件生成随之而来.基本上它只是一个JSON配置文件.


Gan*_*vel 8

上面的答案帮助我弄清楚了它是如何完成的。但是,有一种非常有效的方式来分发二进制文件。

tsuriga的答案中获取线索,这是我的代码:

注意:相应地替换或添加操作系统路径。

  • 创建目录./resources/mac/bin
  • 将您的二进制文件放在此文件夹中
  • 创建文件./app/binaries.js并粘贴以下代码:
'use strict';

import path from 'path';
import { remote } from 'electron';
import getPlatform from './get-platform';

const IS_PROD = process.env.NODE_ENV === 'production';
const root = process.cwd();
const { isPackaged, getAppPath } = remote.app;

const binariesPath =
  IS_PROD && isPackaged
    ? path.join(path.dirname(getAppPath()), '..', './Resources', './bin')
    : path.join(root, './resources', getPlatform(), './bin');

export const execPath = path.resolve(path.join(binariesPath, './exec-file-name'));
Run Code Online (Sandbox Code Playgroud)
  • 创建文件./app/get-platform.js并粘贴以下代码:
'use strict';

import { platform } from 'os';

export default () => {
  switch (platform()) {
    case 'aix':
    case 'freebsd':
    case 'linux':
    case 'openbsd':
    case 'android':
      return 'linux';
    case 'darwin':
    case 'sunos':
      return 'mac';
    case 'win32':
      return 'win';
  }
};
Run Code Online (Sandbox Code Playgroud)
  • ./package.json文件中添加以下代码:
"build": {
....

 "extraFiles": [
      {
        "from": "resources/mac/bin",
        "to": "Resources/bin",
        "filter": [
          "**/*"
        ]
      }
    ],

....
},
Run Code Online (Sandbox Code Playgroud)
  • 导入二进制文件路径为:
import { execPath } from './binaries';

#your program code:
var command = spawn(execPath, arg, {});
Run Code Online (Sandbox Code Playgroud)

为什么这样更好?

  • 上面的答案需要一个名为app-root-dir的附加程序包

  • tsuriga的答案无法正确处理(env = production)构建预打包的版本。他/她只负责开发和后期打包版本。


Yan*_*oto 6

TL;博士:

是的你可以!但是它要求你编写自己独立的插件,它不对系统库做任何假设.此外,在某些情况下,您必须确保为所需的操作系统编译插件.


让我们分几个部分来解决这个问题:

- 插件(原生模块):

插件是动态链接的共享对象.

换句话说,您可以编写自己的插件,而不依赖于系统范围的库(例如,通过静态链接所需的模块),其中包含您需要的所有代码.

您必须考虑这种方法是特定于操作系统的,这意味着您需要为每个要支持的操作系统编译插件!(取决于您可能使用的其他库)

- 电子原生模块:

Electron支持本机节点模块,但由于Electron使用的是官方节点不同的V8版本,因此在构建本机模块时必须手动指定Electron标头的位置

这意味着必须重建一个针对节点头构建的本机模块,以便在电子内部使用.你可以在电子文档中找到它.

- 使用电子应用捆绑模块:

我想您希望将您的应用程序作为独立的可执行文件,而无需用户在其计算机上安装电子邮件.如果是这样,我可以建议使用电子封装器.


Amr*_*hak 2

2024 年更新

根据所提供的答案,

这是一个更简洁的解决方案,不需要任何外部依赖项

  1. 在项目的根目录 ( /resources/$os)中创建一个文件夹,或者将$os所需的二进制文件复制到那里。假设我们有一个二进制文件并希望将其与电子打包。maclinuxwinffmpeg
  2. 更新package.json并将extraFiles选项放入构建配置中,如下所示:
 "build": {
    "extraFiles": [
      {
        "from": "resources/${os}", // $os => "mac/linux/win"
        "to": "Resources/bin",     // for Linux => "resources/bin"
        "filter": ["**/*"]
      }
    ],
  }
Run Code Online (Sandbox Code Playgroud)
  1. utils.ts使用一些辅助函数创建一个文件(例如:)
// utils.ts

import path from 'path';
import { platform } from 'os';
import { app } from 'electron';

export function getPlatform() {
  switch (platform()) {
    case 'aix':
    case 'freebsd':
    case 'linux':
    case 'openbsd':
    case 'android':
      return 'linux';
    case 'darwin':
    case 'sunos':
      return 'mac';
    case 'win32':
      return 'win';
    default:
      return null;
  }
}

export function getBinariesPath() {
  const IS_PROD = process.env.NODE_ENV === 'production';
  const { isPackaged } = app;

  const binariesPath =
    IS_PROD && isPackaged
      ? path.join(process.resourcesPath, './bin')
      : path.join(app.getAppPath(), 'resources', getPlatform()!);

  return binariesPath;
}

// "ffmpeg" is the binary that we want to package
export const ffmpegPath = path.resolve(path.join(getBinariesPath(), './ffmpeg'));
Run Code Online (Sandbox Code Playgroud)
  1. 现在,无论您需要什么地方,都可以使用该二进制文件。(例如:main.ts
// main.ts

import { exec, spawn } from 'child_process';
import { ffmpegPath } from './utils';

// option 1: using exec
exec(`"${ffmpegPath}" -version`, (err, stdout, stderr) => {
    console.log('err, stdout, stderr :>> ', err, stdout, stderr);
});

// option 2: using spawn
const ffmpeg = spawn(ffmpegPath, ['-version']);
ffmpeg.stdout.on('data', (data) => {
  console.log(`spawn stdout: ${data}`);
});

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

ffmpeg.on('close', (code) => {
  console.log(`spawn child process exited with code ${code}`);
});
Run Code Online (Sandbox Code Playgroud)