Electron アプリのコマンドライン引数
Electron アプリでコマンドライン引数を取得するのが案外面倒だったのでまとめる。
なお、開発環境が macOS なので macOS での動作に限定する。
Electron アプリには下記の起動方法がある。
- 開発時に使う
electron .
- パッケージ内のバイナリ
/path/to/APP_NAME.app/Contents/MacOS/APP_NAME
の実行 -
open -a APP_NAME
による実行 (Finder からの起動も同様)
また、単純に起動した場合と app.requestSingleInstanceLock
でシングルインスタンスを強制した場合に発生する second-instance
イベントでの違いについても記述する。
electron コマンド
electron . foo bar
のように指定する。
起動時
process.argv
で取得する。
- 第 1 引数は Electron のバイナリ
$PWD/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron
- 第 2 引数は
.
- 第 3 引数以降が取得したいコマンドライン引数となる
second-instance 起動時
second-instance
イベントのハンドラの第 2 引数で取得する。
- 第 1 引数は Electron のバイナリ
$PWD/node_modules/electron/dist/Electron.app/Contents/MacOS/Electron
- 第 2 引数は
--allow-file-access-from-files
- 第 3 引数は
--enable-avfoundation
- 第 4 引数は
.
- 第 5 引数以降が取得したいコマンドライン引数となる
Electron のオプションが追加されているが、いかにも Electron のアップデート等で変更されそう。
バイナリの実行
/path/to/APP_NAME.app/Contents/MacOS/APP_NAME foo bar
のように指定する
起動時
process.argv
で取得する。
- 第 1 引数はアプリのバイナリ
/path/to/APP_NAME.app/Contents/MacOS/APP_NAME
- 第 2 引数以降が取得したいコマンドライン引数となる
second-instance 起動時
second-instance
イベントのハンドラの第 2 引数で取得する。
- 第 1 引数はアプリのバイナリ
/path/to/APP_NAME.app/Contents/MacOS/APP_NAME
- 第 2 引数は
--allow-file-access-from-files
- 第 3 引数は
--enable-avfoundation
- 第 4 引数以降が取得したいコマンドライン引数となる
open コマンドによる実行
open -a /path/to/APP_NAME.app foo bar
または open -a APP_NAME foo bar
(/Applications
内にインストール済みの場合) のように指定する。
open コマンドによる実行時は、起動時の process.argv
はアプリのバイナリのパスのみ、 second-instance
は発生しない。
起動時は即座に (ready
イベント発生前に) open-file
イベントが発生、起動後の実行でも open-file
イベントが発生する。
また、 open-file
イベントで得られるファイルのパスは 1 つだけで、複数指定された場合は連続して open-file
イベントが発生する。
なお、open コマンドの --args
オプションで指定した引数は、 process.argv
の第 2 引数以降に入るが、シングルインスタンス強制時、起動中に実行した場合はこれを得るすべはない (たぶん)。この記事では扱わない。
上記に対応したコード
ファイルが渡された場合の処理はレンダラープロセスで open-file-from-main
メッセージのハンドラで行うこととする。
second-instance
時の Electron のオプションを無視するために commander
ライブラリを利用する。
const { app } = require("electron/mainl")
const { program } = require ("commander")
// second-instance イベント時につくオプションを parse できるようにしておく
program
.option("--allow-file-access-from-files")
.option("--enable-avfoundation");
// 実行バイナリの違い / 起動時か second-instance かの違いを吸収してコマンドライン引数をとる
function parseArguments(args: string[]) {
program.parse(args, {from: "user"})
const binary = args[0];
return path.basename(binary) === "Electron" ? program.args.slice(2) : program.args.slice(1)
}
async function createWindow() {
const w = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
})
await w.loadFile(path.join(__dirname, "index.html"))
return w
}
let currentWindow: any = null;
let initialOpenCommandArguments: string[] = [];
// シングルインスタンスの強制
if (!app.requestSingleInstanceLock()) app.exit();
// open コマンドによる起動時のイベントを handle するためここでハンドラ登録
app.on("open-file", (event: Event, path: string) => {
event.preventDefault();
if (currentWindow) {
// すでにウインドウがあればレンダラープロセスへファイルパスを送信
currentWindow.webContents.send("open-file-from-main", [path]);
} else {
// ウインドウがなければウィンドウ作成時まで保持
initialOpenCommandArguments.push(path);
}
});
app.whenReady().then(() => {
// 起動状態でバイナリ起動時
app.on("second-instance", (_e: Event, argv: string[]) => {
// レンダラープロセスへファイルパスを送信
currentWindow.webContents.send("open-file-from-main", parseArguments(argv));
});
createWindow().then((w) => {
// バイナリ起動時のコマンドライン引数を送信
w.webContents.send("open-file-from-main", parseArguments(process.argv));
// open コマンド起動時のコマンドライン引数を送信
for (const file of initialOpenFiles) {
w.webContents.send("open-file-from-main", [file]);
}
currentWindow = w;
})
});
Discussion