对于如何将第三方预编译的二进制文件(如imagemagick)包含到电子应用程序中,是否有一个很好的解决方案?有node.js模块,但它们都是包装器或本机绑定到系统范围内安装的库。我想知道是否可以在分发中捆绑预编译的二进制文件。
答案 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方法
要获取捆绑的应用程序路径(这样我可以使用绝对路径来实现我的二进制...相对路径似乎无论我做什么都没有用)我安装了npm包app- root-dir通过从我的app目录中的命令提示符运行以下命令:
npm i -S app-root-dir
在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" %*
)
接下来,您可以在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目录添加任何内容。
将您的文件放在 resources / $ os /下,其中 $ os 是" mac" ," linux" ,或" win" 。构建过程将根据构建目标操作系统从这些目录中复制文件。
在构建配置中添加extraFiles
选项,如下所示:
的package.json
"build": {
"extraFiles": [
{
"from": "resources/${os}",
"to": "Resources/bin",
"filter": ["**/*"]
}
],
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';
}
};
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 路径。
'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'));
'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'; } };
"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,因此配置必须放在稍有不同的位置。
resources
目录。将所有文件放在此处。vue.config.js
:module.exports = {
pluginOptions: {
electronBuilder: {
builderOptions: {
...
"extraResources": [
{
"from": "resources",
"to": ".",
"filter": "**/*"
}
],
...
}
}
}
}
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
在那些平台的资源目录中(我认为是这样),它应该可以工作。
import { resourcesPath } from '../resources'; // Path to resources.ts
...
loadFromFile(resourcesPath + '/your_file');