web-dev-qa-db-ja.com

プリコンパイルされたバイナリをelectronアプリにバンドル

Imagemagickのようなサードパーティのコンパイル済みバイナリを電子アプリに含める方法についての良い解決策はありますか? node.jsモジュールがありますが、それらはすべて、システム全体にインストールされたライブラリへのラッパーまたはネイティブバインディングです。ディストリビューション内にプリコンパイルされたバイナリをバンドルすることは可能ですか?.

34
Toby

私はこれに対する解決策を見つけましたが、これがベストプラクティスと見なされるかどうかはわかりません。サードパーティのプリコンパイルされたバイナリを含めるための適切なドキュメントが見つからなかったため、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メソッド

  1. Electronベースフォルダー(electron-quick-startがデフォルト名です)を開き、node_modulesフォルダーに移動します。そこでffmpegという名前のフォルダーを作成し、静的バイナリーをこのディレクトリーにコピーします。注:ffmpegが最新のWindowsビルド here を取得したため、これはバイナリの静的バージョンである必要があります。
  2. バンドルされたアプリパスを取得するには(バイナリに絶対パスを使用できるように...相対パスが何をしても機能しないように思われました)次のコマンドを実行して、npmパッケージapp-root-dirをインストールしました私のアプリディレクトリのコマンドプロンプトから:

     npm i -S app-root-dir
    
  3. 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}` );
 });
14
UltrasoundJelly

これは、これまでにMacとWindowsでテストされた別の方法です。 「app-root-dir」パッケージが必要です。node_modulesdirに手動で何かを追加する必要はありません。

  1. ファイルをresources/$ os/に配置します。ここで、$ os "mac" "linux"、または "win"。ビルドプロセスは、ビルドターゲットOSに従って、これらのディレクトリからファイルをコピーします。

  2. 次のように、ビルド構成にextraFilesオプションを追加します。

package.json

  "build": {
    "extraFiles": [
      {
        "from": "resources/${os}",
        "to": "Resources/bin",
        "filter": ["**/*"]
      }
    ],
  1. このようなものを使用して、現在のプラットフォームを判別します。

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と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
});

私は electron-builder をベースとして使用していたと思いますが、envファイルの生成にはそれが付属しています。基本的には、それは単なるJSON構成ファイルです。

12
tsuriga

上記の回答は、それがどのように行われるかを理解するのに役立ちました。しかし、バイナリファイルを配布するための非常に効率的な方法があります。

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という追加のパッケージが必要です

  • つりがの答えは、(env = production)buildまたはpre-packedバージョンを適切に処理しません。彼/彼女は、開発版とポストパッケージ版のみを扱いました。

6

tl; dr:

はい、できます!ただし、システムライブラリを想定しない独自の自己完結型アドオンを作成する必要があります。さらに、場合によっては、アドオンが目的のOS用にコンパイルされていることを確認する必要があります。


この質問をいくつかの部分に分けましょう:

- アドオン (ネイティブモジュール)

アドオンは動的にリンクされた共有オブジェクトです。

言い換えれば、必要なすべてのコードを含むシステム全体のライブラリに依存せずに(たとえば、必要なモジュールを静的にリンクすることによって)独自のアドオンを作成するだけで済みます。

そのようなアプローチはOS固有であると考える必要があります。つまり、サポートしたいOSごとにアドオンをコンパイルする必要があります。 (使用できる他のライブラリに応じて)

- ネイティブモジュール 電子用

ネイティブNodeモジュールはElectronでサポートされていますが、Electronは公式ノードとは異なるV8バージョンを使用しているため、ネイティブモジュールをビルドするときにElectronのヘッダーの場所を手動で指定する必要があります

つまり、ノードヘッダーに対してビルドされたネイティブモジュールは、electron内で使用するには再構築する必要があります。あなたはelectron docsでその方法を見つけることができます。

-electronアプリを含むバンドルモジュール

ユーザーがマシンにelectronをインストールすることを必要とせずに、スタンドアロンの実行可能ファイルとしてアプリを作成したいと思います。もしそうなら、私は electron-packager の使用を提案できます。

6
Yan Foto

ガネーシャの回答に続いて、これは本当に素晴らしい助けでした。私の場合、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()は、アプリがパッケージ化されたときにクラッシュを生成していました。お役に立てれば幸いです。

0
fred foc

ガネーシャの回答に大きく基づいていますが、多少簡略化されています。また、私は Vue CLI Electron Builderプラグイン を使用しているため、構成は少し異なる場所に移動する必要があります。

  1. resourcesディレクトリを作成します。そこにすべてのファイルを配置します。
  2. これをvue.config.jsに追加:
module.exports = {
  pluginOptions: {
    electronBuilder: {
      builderOptions: {
        ...
        "extraResources": [
          {
            "from": "resources",
            "to": ".",
            "filter": "**/*"
          }
        ],
        ...
      }
    }
  }
}
  1. 次の内容で、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がそれらのプラットフォームのリソースディレクトリにあると想定して動作するはずです(私はそう想定しています)。

  1. 次のように使用します。
import { resourcesPath } from '../resources'; // Path to resources.ts

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