アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

npm-scripts で自前の環境変数を利用する方法と問題点

Electron を試す 7 - Electron v1.0 対応で npm-scripts に環境変数を定義してそれを scripts 内から参照する方法を書いたのだが、この方法は Windows 環境だと利用できない。

と、これだけで話を終えるのはもったいないので、私がおこなった調査や見解についてまとめる。

package.json の config

package.jsonconfig の説明がある。

A "config" object can be used to set configuration parameters used in package scripts that persist across upgrades. For instance, if a package had the following: { "name" : "foo" , "config" : { "port" : "8080" } } and then had a "start" command that then referenced the npmpackageconfig_port environment variable, then the user could override that by doing npm config set foo:port 8001.

config に設定した値は npm_package_config_NAME として参照可能。config にはプログラム内から参照する例として以下のサンプルが掲載されている。

http.createServer(...).listen(process.env.npm_package_config_port)

npm-scripts から config の環境変数を参照する

冒頭の記事にも書いた方法。package.jsonconfig に定義した環境変数を npm-scripts から参照する。

{
  "config": {
    "app_name": "MyApp"
  },
  "scripts": {
    "var": "echo $npm_package_config_app_name"
  }
}

環境変数は $npm_package_config_NAME という書式。$npm_package_config_ までが固定、以降が config へ定義したプロパティ名となる。この状態で npm run してみると環境変数が展開されることを確認できるはず。

$ npm run var

> electron-starter@1.2.1 var .../use-env-var
> echo $npm_package_config_app_name

MyApp

この機能を利用することで npm-scripts 内の CLI へ指定する値を config へ分離できる。例えば Electron をパッケージ化するために electron-packager を利用するとして、その CLI へ指定する Electron のバージョンやアプリ名をハードコードせず config で抽象化してみよう。

{
  "config": {
    "app": "MyApp",
    "electron": "1.1.0"
  },
  "scripts": {
    "release:pack-osx": "electron-packager ./dist/src $npm_package_config_app --out=dist/bin --cache=dist/cache --platform=darwin --arch=x64 --version=$npm_package_config_electron --overwrite --asar --icon=res/app.icns",
    "release:pack-win": "electron-packager ./dist/src $npm_package_config_app --out=dist/bin --cache=dist/cache --platform=win32 --arch=x64 --version=$npm_package_config_electron --overwrite --asar --icon=res/app.ico",
    "release:pack-linux": "electron-packager ./dist/src $npm_package_config_app --out=dist/bin --cache=dist/cache --platform=linux --arch=x64 --version=$npm_package_config_electron --overwrite --asar"
  }
}

release:pack-osx/win/linuxconfig appelectron を共有。app がアプリの実行ファイル名など、electron がパッケージ化に使用する Electron のバージョン。electron の値を CLI 側にハードコードしていたら Electron が更新されるたびに 3 箇所の修正が必要となる。面倒なうえ CLI 引数の書き換えは複雑なためミスを誘発しやすい。このように厄介な作業を避けられるのは実にありがたい。

この npm-scripts を他のプロジェクトに流用したくなったらアプリ名を書き換えることになる。その際も configapp を修正するだけで済むだろう。プロジェクトに依存する変更箇所を外部化することで汎用性が高まった。

gulp のように Node としてタスク定義するプラットフォームの魅力は手続き型の処理と変数の利用にある。これらの内、タスク処理については CLI の工夫や npm-run-all のような補助 npm を利用することで十分に運用可能。不便を感じるとしたらプロジェクト構成が複雑などの問題があるだろうから、それを見直すことになるだろう。

一方、変数についてはどうにもならずハードコードやむなしとしていたが、これも代替できたので npm-scripts の利便性が大きく向上した。

はずだったのだが、しかし...

環境変数の展開におけるプラットフォーム依存

npm run は実行される shell により環境変数の参照記法が異なる。OS X や Linux などで利用されている bash なら $variable で Windows の cmd.exe や PowerShell だと %variable%。挙動を試すため package.json へ configscripts を以下のように定義する。

{
  "config": {
    "app_name": "MyApp"
  },
  "scripts": {
    "var:bash": "echo $npm_package_config_app_name"
    "var:win": "echo %npm_package_config_app_name%"
  }
}

Windows とそれ以外の環境で var:bashvar:win を実行してみよう。すると一方は環境変数が展開され、他方は環境変数の名前がそのまま出力されることを確認できる。この調査をしている時に見つけた記事 How to Use npm as a Build Tool にも

The other downside to these configs is that they're not very Windows friendly - Windows uses % for variable substitution, while bash uses $. They work fine if you use them within a Node.js script, but if anyone knows of a way to get them working in Windows via the shell commands, let me know!

とある。なるほど、つまり config の定義は共通化できても npm-scripts 内の参照記法は区別しなければならないのだ。その他 cross-env ならゆけるかも?と試したものの環境変数の定義までは成功したけれど参照記法は統一できなかった。残念。

私は package.json だけで npm 依存とタスク処理を完結させたい派。なので config による共通化は諦めることにした。タスク運用として各プラットフォーム標準の環境に Node だけ入れたら動くことを理想としているから Windows に bash を入れるなどの案は採用しない。

将来 npm-scripts 自体が環境変数のプラットフォーム差を吸収してくれるか、そのような npm が登場したら改めて採用を検討する予定。

npm を実装する場合 cross-env みたいに自身の引数として対象となるコマンドを受け取り、自前で config を展開してから shell に受け渡せばよいのだろうか。気が向いたら開発してみるかもしれない。