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

时间:2015-10-15 15:27:10

标签: node.js imagemagick electron

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

6 个答案:

答案 0 :(得分:13)

我确实找到了解决方案,但我不知道这是否是最佳做法。我无法找到任何包含第三方预编译二进制文件的好文档,所以我只是弄乱了它,直到最终使用我的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

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

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

npm i -S app-root-dir

现在我有了根应用程序目录,我只是为我的二进制文件追加子文件夹并从那里生成。这是我放在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方法

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

     npm i -S app-root-dir
    
  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
    

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

    @IF EXIST "%~dp0\node.exe" (
     "%~dp0\node.exe"  "%~dp0\..\ffmpeg\ffmpeg" %*
    ) ELSE (
       @SETLOCAL
     @SET PATHEXT=%PATHEXT:;.JS;=;%
     node  "%~dp0\..\ffmpeg\ffmpeg" %*
    )
    
  4. 接下来,您可以在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}` );
     });
    

答案 1 :(得分:12)

这是迄今为止使用Mac和Windows测试过的另一种方法。需要' app-root-dir'包,不需要手动向node_modules目录添加任何内容。

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

  2. 在构建配置中添加extraFiles选项,如下所示:

  3. 的package.json

      "build": {
        "extraFiles": [
          {
            "from": "resources/${os}",
            "to": "Resources/bin",
            "filter": ["**/*"]
          }
        ],
    
    1. 使用类似的东西来确定当前的平台。
    2. GET-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';
        }
      };
      
      1. 根据env和操作系统调用应用程序中的可执行文件。这里我假设构建版本处于生产模式,源代码版本处于其他模式,但您可以创建自己的调用逻辑。
      2. 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
        });
        

        我认为我使用electron-builder作为基础,env文件生成随附。基本上它只是一个JSON配置文件。

答案 2 :(得分:6)

<强> TL; DR:

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

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

- Addons(原生模块)

  

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

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

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

- Native modules代表电子

  

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

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

- 使用电子应用程序捆绑模块

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

答案 3 :(得分:5)

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

根据tsuriga's answer的提示,这是我的代码:

注意:相应地替换或添加 OS 路径。

  • 创建目录。/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'));
  • 创建文件 ./ 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';
  }
};
  • ./ package.json 文件内添加以下代码:
"build": {
....

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

....
},
  • 将二进制文件路径导入为:
import { execPath } from './binaries';

#your program code:
var command = spawn(execPath, arg, {});

为什么这样更好?

  • 以上答案需要一个名为 app-root-dir

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

答案 4 :(得分:0)

下面的Ganesh回答确实有很大帮助,在我的情况下,binaries.js(对于Mac构建-未测试Windows或Linux)的工作原理是:

"use strict";
import path from "path";
import { app } from "electron";

const IS_PROD = process.env.NODE_ENV === "production";
const root = process.cwd();
const { isPackaged } = app;

const binariesPath =
  IS_PROD && isPackaged
    ? path.join(process.resourcesPath, "./bin")
    : path.join(root, "./external");

export const execPath = path.join(binariesPath, "./my_exec_name");

考虑到my_exec_name在文件夹./external/bin中,并复制到./Resources/bin的应用程序包中。我没有使用get_platforms.js脚本(在我的情况下不需要)。打包应用程序时,app.getAppPath()生成崩溃。 希望能有所帮助。

答案 5 :(得分:0)

很大程度上基于Ganesh的答案,但有所简化。另外,我使用的是Vue CLI Electron Builder Plugin,因此配置必须放在稍有不同的位置。

  1. 创建一个resources目录。将所有文件放在此处。
  2. 将此添加到vue.config.js
module.exports = {
  pluginOptions: {
    electronBuilder: {
      builderOptions: {
        ...
        "extraResources": [
          {
            "from": "resources",
            "to": ".",
            "filter": "**/*"
          }
        ],
        ...
      }
    }
  }
}
  1. resources.ts文件夹中创建一个名为src的文件,其内容如下:
import path from 'path';
import { remote } from 'electron';

// Get the path that `extraResources` are sent to. This is `<app>/Resources`
// on macOS. remote.app.getAppPath() returns `<app>/Resources/app.asar` so
// we just get the parent directory. If the app is not packaged we just use
// `<current working directory>/resources`.
export const resourcesPath = remote.app.isPackaged ?
                             path.dirname(remote.app.getAppPath()) :
                             path.resolve('resources');

请注意,我尚未在Windows / Linux上对此进行测试,但是假设app.asar在那些平台的资源目录中(我认为是这样),它应该可以工作。

  1. 像这样使用它:
import { resourcesPath } from '../resources'; // Path to resources.ts

...
    loadFromFile(resourcesPath + '/your_file');