electronアプリnode cliアプリでのコマンドライン実行の種類とparseの共用について

必要なelectron,nodeのCLIについての周辺情報の収集・整理を行った。
(CLIというとユーザ入力のプロンプトを含むが、今回はプロンプトを含まないコマンドライン引数によるワンショット実行をCLIと呼びそれを扱う。)

# 要求仕様
daisy-sequenceおよびdaisy-cell-block-diagram(以下daisyシリーズ)は本物のシステム開発アプリケーションを志向している。
本物のシステム開発アプリケーションはCLI、CI連携可能なコマンドラインインタフェースを持つ。
本稿ではdaisyシリーズのCLIについて、以下の通り仕様と実装を検討し、必要なelectron,nodeのCLIについての周辺情報の収集・整理を行った。


## 画面仕様
usage: `app [input_filepath [-o output_filepath]]`
- guiとcli(コマンドライン)を提供する。コマンドラインインタフェースを共通とする。
    - GUI,CLIの区別は別バイナリ提供により実現する。
    - output指定の有無によるGUIとCLIそれぞれの挙動の違いについては未規定とする。
    - (GUIではoutput指定があると不正終了または書き出して終了、CLIではoutputが無ければ不正終了、を想定している。)
- helpとversionはあるに越したことはないが無くていい。
- win/mac/linux対応。
    - win,linuxのランチャに対応するため、GUIのオプション第一引数は渡されたファイルを開く。

## 機能仕様
- electronとnode(cli)で可能な限りコードを共有したい。せめて設定パラメタは共有したい。

# electorn, nodeのCLIの基礎
一般的なコマンドラインでは、先頭がアプリケーション実行ファイルの起動パスで後ろにコマンドライン引数が付く形で実行し、アプリに渡されるコマンドライン引数(argv)も同じである。
しかし、node, electronではそうでない場合がある。

### nodeから起動する場合と、npmパッケージのCLIコマンドとして実行する場合では引数の数が変わる
1. `node ./path/myapp.js myarg1 myarg2`
2. `npm run myapp -- myarg1 myarg2`
3. `node_modules/.bin/myapp`


`node`コマンドによる実行では、頭に`node`が付く。開発中などに用いる。
package.jsonコマンドによる`npm run`実行では、一般的なコマンドライン引数が渡される。
(`--`を付けることで以降の引数が`npm run`から実行アプリケーションに渡る。
 [npm run (npm run-script) コマンドで実行するスクリプトに引数を渡す。2017]( https://qiita.com/tiny-studio/items/ce28bf84c76aba53122f ) )
package.jsonのbin要素による実行ファイル実行では、一般的なコマンドライン引数が渡される。npm配布パッケージとしてインストールした後などに用いる。

```js
 10 >-------// コマンドライン引数を[1]からに調整(node実行の場合に必要)¬
 11 >-------let argv = process.argv;¬
 12 >-------if(argv[0].endsWith('node')){¬
 13 >------->-------argv.shift();¬
 14 >-------}¬
```

### electronで起動する場合と、ビルド後のアプリでは引数の数が変わる
1. `npm run myapp myarg1 myarg2`
2. `./myapp.exe myarg1 myarg2`


package.jsonコマンド起動では、頭に`electron .`と引数2個が付く。electronで起動するためである。開発中などに用いる。
ビルド後のアプリケーションでの実行は、一般的なコマンドライン引数が渡される。

```js
  63 >-------let argv = remote.process.argv;¬
  64 >-------if(argv[0].endsWith('electron') && argv[1] === '.'){¬
  65 >------->-------argv.shift(), argv.shift();¬
  66 >-------}else{¬
  67 >------->-------argv.shift();¬
  68 >-------}¬
```

### (備考) macosxの場合
本編ではないが。
macosxでファイルタイプに対するデフォルト起動アプリケーション登録によるファイルOpen(winのexplorerやLinuxのランチャ)は第一引数によらない。
`open-file`eventで受ける。念の為。
```js
app.on('open-file', openFileHandler);
```

### コマンドラインパーサの呼び出し位置
nodeのコマンドラインパーサパッケージを使う場合、そのうちのいくつかは、electronの'ready'イベントの前にMainProcessで使用する必要があると思われる。
基本的に、electronのRenderProcess側のわりと特殊な環境を想定していない模様。
具体的にはargsをremoteから取る部分、console.log()出力など。

[electronにおけるコマンドライン引数の位置とパーサ]( https://qiita.com/akameco/items/cfbde2f1b241e2babc6c )

command-line-args を使ってエラーで失敗していたのだが、RenderProcess側でやっていたのが問題であった可能性がある。

ただし、後述の通りglobalな変数を使用せずアプリケーションからargvを渡す形のインタフェースであればこれは問題とならないし、daisyシリーズではこれを採用する。


# 再度、要求仕様
- node,electronで共用するにあたり、node,electronおよび呼び出し方法によるCLIの違いを吸収できる必要がある。
  具体的には、これら先頭オプションの違いを取り除く前処理を行ったargvを渡せる必要がある。
  (例えば`argv`パッケージはargv引数を取らないので落選となる。)

minimistを用いるか、規模的に独自実装するのが適切と考える。


# vscodeの場合
vscodeではmain.jsでminimistをめちゃくちゃ単純なオプションで使用している。
後処理でもっと複雑なことをしているような気もするが、調査していない。

```js
/**
 * @returns {ParsedArgs}
 */
function parseCLIArgs() {
    const minimist = require('minimist');

    return minimist(process.argv, {
        string: [
            'user-data-dir',
            'locale',
            'js-flags',
            'max-memory'
        ]
    });
}
```

## 引き渡し
MainProcessからRenderProcessへの引き渡しは、ShareObjectを使えばよさそう。

main.js
```js
  8 global.sharedObject = {'osx_open_file': null};¬
  9 let openFileHandler = function(event, path) {¬
 10 >-------event.preventDefault();¬
 11 >-------global.sharedObject.osx_open_file = path;¬
 12 };¬
```

js/index.js
```js
  85 >-------if(typeof remote.getGlobal('sharedObject').osx_open_file === 'string'){¬
  86 >------->-------if(null === arg.open_filepath){¬
  87 >------->------->-------arg.open_filepath = remote.getGlobal('sharedObject').osx_open_file;¬
  88 >------->-------}¬
  89 >-------}¬
```

## 先頭ファイルパスの実現
minimistは本当にコマンドラインパーサをするだけ。help(usage)は作ってくれない。
よって、先頭ファイルパスを除いてそれより後ろのargvを渡せばよい。`argv.shift()`よりも`argv.slice(2)`がよくサンプルコードに書かれる。


0 件のコメント:

コメントを投稿

TIGORA(ティゴラ)のトレッキング シューズ

 TIGORA(ティゴラ)の トレッキング シューズを買いました。 メインの靴がアシックスウォーキングで街歩き用なのですが、これまではこれで高尾山などの軽い山も登っていました。 今回、靴底があまりに摩耗したこともあってアシックスウォーキングを買い替えたのですが、ついでに消耗が激...