今まで知らずにいたnpmスクリプトでできる3つのこと

March 03, 2020
執筆者

Three Things You Didn't Know You Could Do with npm Scripts

この記事はTwilio Developer AdvocateのDominik Kundelこちらで執筆した記事(英語)を日本語化したものです。

 

Node.jsエコシステムには便利なCLIツールが多く含まれ、その多くで設定を変更でき目的に応じた調整ができます。ただし、時には非常にカスタマイズされた構成やスクリプトが必要になります。このような時に役立つのが、「npmスクリプト」です。これを使用し、「build」、「dev」、または「start」スクリプトを設定したことがあるかもしれませんが、他にも多くのことができます。このブログ記事では、非常に便利な隠れた機能についてお話します。

作業に入る前に、最新バージョンのnpmがインストールされていることを確認してください。これらの多くは、yarnberrypnpmでも機能しますが、この記事ではnpmに焦点を置きます。この記事の内容はすべてnpmバージョン6.10でテスト済みです。

npmスクリプトとは?

「npmスクリプト」について話すということは、package.jsonscriptsフィールドのエントリについて話すことを意味します。scriptsフィールドには、さまざまなコマンドやスクリプトを指定するオブジェクトを保持します。これらのスクリプトはnpm run <script-name>を使用し実行できます。

例えば、package.jsonが以下のような場合:

{
  "name": "demo",
  "scripts": {
    "example": "echo 'hello world'"
  }
}

以下を実行できます。

npm run example

多くの引数をCLIコマンドに渡したいが、毎回入力し直したくはない、という場合に、この方法は特に便利です。さらに、依存関係により公開されたスクリプトにアクセスすることもできます。この場合、グローバルに依存関係をインストールする必要はありません。

例えば、TypeScriptを使用する場合、全員にnpm install -g typescriptを使用してグローバルにインストールしてもらう代わりに、npm install --save-dev typescriptを使用してdev依存関係としてインストールし、そのあとで"scripts"セクションにbuildスクリプトを追加できます。

  "scripts": {
    "build": "tsc"
  }

このプロジェクトを使用する人はTypeScriptをグローバルでインストールする必要がありません。代わりに、npm installを実行した後にnpm run buildを実行できます。これはインストールされた同じコマンドのさまざまなバージョンを持つ複数のプロジェクトを使用できることも意味します。

このようなコマンドのエイリアスは、npmスクリプトの最も有名な部分でしょう。しかし、npmスクリプトを活用する方法はその他にも多数あります。

pre-/post-が付いたスクリプト

このブログで取り扱うヒントやトリックの中でも、これが一番よく知られているかもしれませんが、非常に強力であるためここで取り扱います。

以下のスクリプトセクションがあるとします。

  "scripts": {
    "prebuild": "rimraf dist",
    "build": "tsc",
    "postbuild": "npm run test",
    "test": "jest"
  }

ここでnpm run buildを実行すると、以下のものが自動的にトリガされます。

  1. prebuildが呼び出され、rimrafツールを実行し、distフォルダを削除
  2. buildが実行され、TypeScriptコンパイラが実行される
  3. postbuildが呼び出され、npm run testが実行される
  4. testが実行され、jest test runnerが実行される

これが機能するのは、npmが、スクリプトに、同じ形式で名前が付けられていて、preまたはpostが前に付いた他のスクリプトがないかを自動的に検出し、その順序で実行するためです。これは、スクリプトを複雑にすることなくコマンドを繋げる便利な方法です。以下は、これに適したユースケースです。

  • ビルドのアーティファクトを削除する
  • テスト前にリンターを実行する
  • アプリケーションの構築前にデータをダウンロードする

同じ動作がビルトインコマンドにも適用されます。例えば、preinstallprepackです。ここで変則的なのは、versionです。これはpreversionversionpostversionを提供します。versionpostversionの違いは、npmがpreversionversionで実行された変更がコミットされた後でpostversion呼び出されることです。これらのライフサイクルスクリプトの詳細については、npmドキュメントをご覧ください

環境変数

次に、私がこの事実を初めて発見したときに、嬉しい驚きを感じたことについてお話します。npm run…を通じてコマンドやスクリプトを実行する際、環境変数はnpmからの変数を使用し自動的に拡張されます。

すべての環境変数にはnpm_のプレフィックスが付き、これらは2つのタイプにグループ分けできます。

  • npm_config_で始まるものは、グローバルnpm構成またはプロジェクト特有の.npmrcファイルからの一般的なnpm構成
  • npm_package_で始まるものは、プロジェクト特有のもの

プロジェクトでスクリプトに渡される値に興味がある場合には、以下のエントリをスクリプトに追加します。

{
  "scripts": {
    "check-env": "node -e 'console.log(process.env)' | grep npm"
  }
}

次に、npm run check-envをコマンドラインで実行すると、npmにより設定されたすべての環境変数がリスト形式で表示されます。以下は、私が特に注目した点です。

  • package.jsonのすべてのシングルエントリを環境変数として検索できる。JSONでのプロパティへのアクセスに類似しているが、セパレータとして「_」を使用する点が異なる。例えば、npm_package_dependencies_twilioは、インストールされたtwilioのバージョンを提供し、npm_package_author_emailは、authorプロパティのメールフィールドを提供する。配列の値にアクセスするには、「_」を前に付けたインデックス値(npm_package_keywords_0など)を使用し、最初のキーワードを取得する。
  • npmバージョン、nodeバージョン、オペレーティングシステムは、npm_config_user_agentで取得できる。形式は、npm/6.10.0 node/v10.19.0 darwin x64のようになる。darwinはmacOS、x64はプロセッサアーキテクチャである。
  • HEADのハッシュは、npm_package_gitHeadで取得できる。

便利な変数が多数あるため、皆さんもご確認ください。特にオートメーションスクリプトを作成する際に便利です。

また、一般的なNode.jsの環境変数の詳細についてもご覧ください。

引数を渡し、解析する

ここまでは、スクリプトの作成方法、設定される環境変数、スクリプトの呼び出し方について話してきました。ただし、時には、引数をスクリプトに渡し、より動的に処理できるようにしたい場合があります。npmスクリプトに引数を渡す方法は2通りあります。

第1の方法は、実際のコマンドに直接引数を渡す方法です。例:

npm run build -- --watch

これは以下のように実行されます。

tsc --watch

ここで重要なのは、「--」とその後に続く1個のスペースです。それ以降の部分は、スクリプトの実際のコマンドに一対一で渡されます。これは、例えば上記の例のように、tscのようなベースコマンドを公開し、コマンドに追加の引数を渡す場合に便利です。

第2の方法は、npmのビルトインの引数パーサーを使用することです。これは、あまり知られていないnpmスクリプトの機能だと思います。私は初見のときにとても感激しました。基本的に、npmは、スクリプトに渡されたあらゆる引数を解析します(引数が「--」とそれに続く1個のスペースに続いて渡された場合を除く)。npmが解析を終えると、それらは環境変数のなかのnpm_config_の下で使用できるようになります。

これをテストするため、新しいscriptsエントリを作成します。

{
  "scripts": {
    "demo": "echo \"Hello $npm_config_first $npm_config_last\""
  }
}

そして、次を実行します。

npm run demo --last=Kundel --first=Dominik

これは、Hello Dominik Kundelを出力します。ただし、私達がこの引数パーサーを構成しているのではないため、引数の構文はあまり柔軟ではないことに注意してください。例えば、「=」記号を削除し、同じコマンドをもう一度実行します。

npm run demo --last Kundel --first Dominik

この場合、出力はHello true true Kundel Dominikになります。これは、--last--firstがブール値フラグとして解釈され、値がtrueに設定され、残りの引数が未解析の引数としてスクリプトに渡されるためです。その結果、echo "Hello $npm_config_first $npm_config_last" "Kundel" "Dominik"が呼びだされます。

上記のように引数パーサーが非常に厳格だとしても、シンプルなスクリプトをいくつか作成し、引数の解析に労力を割かない場合、これは非常に強力なツールとなります。

便利なツール

npmスクリプトを使用し、その力を解放する方法をいくつか見てきましたが、npmスクリプトを次のレベルへと導く、私のお気に入りのツールをご紹介します。

これはほんの数例であり、他にも多くの便利なツールがあると思います。他の便利なツールをご存知の場合には、お気軽にdkundel@twilio.comまでメールで、またはTwitterでダイレクトメッセージを送信してください。ここに追加させていただきます。

まとめ

npmスクリプトは、プロジェクトの作業をしているあらゆる人を支援し、開発経験を向上させる便利な方法です。npmスクリプトを使用すれば、簡単なコマンド作成によるコマンドタスクの再実行や、明確なインターフェイスの作成による内部実装の抽象化が可能になり、また、簡単なスクリプトインターフェイスとして機能させることもできます。

皆さんが構築したスクリプトや、npmスクリプトを構築中に発見した他のヒントなどについてお聞かせください。