Imagemagickのようなサードパーティのコンパイル済みバイナリを電子アプリに含める方法についての良い解決策はありますか? node.jsモジュールがありますが、それらはすべて、システム全体にインストールされたライブラリへのラッパーまたはネイティブバインディングです。ディストリビューション内にプリコンパイルされたバイナリをバンドルすることは可能ですか?.
私はこれに対する解決策を見つけましたが、これがベストプラクティスと見なされるかどうかはわかりません。サードパーティのプリコンパイルされたバイナリを含めるための適切なドキュメントが見つからなかったため、ffmpegバイナリで最終的に機能するまで、いじくり回しました。これが私がしたことです(electronクイックスタート、node.js v6から始めます):
Mac OS Xメソッド
Appディレクトリから、ターミナルで次のコマンドを実行して、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
を現在のバイナリパスに置き換えて、ここからダウンロードしてください)リンクを配置すると、electron-packagerは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をインストールしました私のアプリディレクトリのコマンドプロンプトから:
npm i -S app-root-dir
Node_modulesフォルダー内で、.binサブフォルダーに移動します。コピーしたバイナリexeファイルを含めるようにノードに指示するには、ここでいくつかのテキストファイルを作成する必要があります。お好みのテキストエディタを使用して、次の内容のffmpeg
という名前の2つのファイルを作成します。
#!/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
という名前の2番目のテキストファイル:
@IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\ffmpeg\ffmpeg" %*
) ELSE (
@SETLOCAL
@SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\ffmpeg\ffmpeg" %*
)
次に、次のように(renderer.jsの)Windows電子ディストリビューションで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}` );
});
これは、これまでにMacとWindowsでテストされた別の方法です。 「app-root-dir」パッケージが必要です。node_modulesdirに手動で何かを追加する必要はありません。
ファイルをresources/$ os/に配置します。ここで、$ osは "mac"、 "linux"、または "win"。ビルドプロセスは、ビルドターゲットOSに従って、これらのディレクトリからファイルをコピーします。
次のように、ビルド構成に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構成ファイルです。
上記の回答は、それがどのように行われるかを理解するのに役立ちました。しかし、バイナリファイルを配布するための非常に効率的な方法があります。
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という追加のパッケージが必要です
つりがの答えは、(env = production)buildまたはpre-packedバージョンを適切に処理しません。彼/彼女は、開発版とポストパッケージ版のみを扱いました。
tl; dr:
はい、できます!ただし、システムライブラリを想定しない独自の自己完結型アドオンを作成する必要があります。さらに、場合によっては、アドオンが目的のOS用にコンパイルされていることを確認する必要があります。
この質問をいくつかの部分に分けましょう:
- アドオン (ネイティブモジュール):
アドオンは動的にリンクされた共有オブジェクトです。
言い換えれば、必要なすべてのコードを含むシステム全体のライブラリに依存せずに(たとえば、必要なモジュールを静的にリンクすることによって)独自のアドオンを作成するだけで済みます。
そのようなアプローチはOS固有であると考える必要があります。つまり、サポートしたいOSごとにアドオンをコンパイルする必要があります。 (使用できる他のライブラリに応じて)
- ネイティブモジュール 電子用:
ネイティブNodeモジュールはElectronでサポートされていますが、Electronは公式ノードとは異なるV8バージョンを使用しているため、ネイティブモジュールをビルドするときにElectronのヘッダーの場所を手動で指定する必要があります
つまり、ノードヘッダーに対してビルドされたネイティブモジュールは、electron内で使用するには再構築する必要があります。あなたはelectron docsでその方法を見つけることができます。
-electronアプリを含むバンドルモジュール:
ユーザーがマシンにelectronをインストールすることを必要とせずに、スタンドアロンの実行可能ファイルとしてアプリを作成したいと思います。もしそうなら、私は electron-packager の使用を提案できます。
ガネーシャの回答に続いて、これは本当に素晴らしい助けでした。私の場合、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()は、アプリがパッケージ化されたときにクラッシュを生成していました。お役に立てれば幸いです。
ガネーシャの回答に大きく基づいていますが、多少簡略化されています。また、私は Vue CLI Electron Builderプラグイン を使用しているため、構成は少し異なる場所に移動する必要があります。
resources
ディレクトリを作成します。そこにすべてのファイルを配置します。vue.config.js
に追加:module.exports = {
pluginOptions: {
electronBuilder: {
builderOptions: {
...
"extraResources": [
{
"from": "resources",
"to": ".",
"filter": "**/*"
}
],
...
}
}
}
}
src
フォルダにresources.ts
というファイルを作成します。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');