execa の使い方 — Node.jsの子プロセス実行をシンプルにするライブラリ
一言でいうと
execa は、Node.js の child_process モジュールをラップし、Promise ベースのモダンなAPIとテンプレートリテラル構文で子プロセスの実行を劇的にシンプルにするライブラリです。シェルインジェクションのリスクを排除しつつ、パイプ・ストリーム・IPC などの高度な機能も備えています。
どんな時に使う?
- ビルドスクリプトやCLIツールの開発 —
npm run buildやgitコマンドなどの外部コマンドをNode.jsスクリプトから安全に実行したいとき - CI/CDパイプラインやタスクランナー — 複数のコマンドをパイプで繋いだり、並列実行したりする自動化スクリプトを書くとき
- アプリケーション内でのバイナリ呼び出し — FFmpeg、ImageMagick、ESLint などのローカルインストールされたバイナリをプログラムから制御したいとき
インストール
# npm
npm install execa
# yarn
yarn add execa
# pnpm
pnpm add execa
注意: execa v9 は ESM (ES Modules) 専用です。CommonJS (
require) では使用できません。package.jsonに"type": "module"を設定するか、.mjs拡張子を使用してください。
基本的な使い方
最も基本的なパターンは、テンプレートリテラル構文でコマンドを実行し、結果を await で受け取る方法です。
import { execa } from 'execa';
// テンプレートリテラル構文(推奨)
const { stdout } = await execa`ls -la`;
console.log(stdout);
// 引数を変数で渡す(自動エスケープされるので安全)
const dirName = 'my folder with spaces';
const { stdout: result } = await execa`ls -la ${dirName}`;
console.log(result);
// 従来の関数呼び出し構文も使える
const { stdout: gitLog } = await execa('git', ['log', '--oneline', '-5']);
console.log(gitLog);
実行結果はオブジェクトで返され、主要なプロパティは以下の通りです。
import { execa } from 'execa';
const result = await execa`echo hello`;
console.log(result.stdout); // "hello" — 標準出力
console.log(result.stderr); // "" — 標準エラー出力
console.log(result.exitCode); // 0 — 終了コード
console.log(result.command); // "echo hello" — 実行されたコマンド文字列
console.log(result.failed); // false — 失敗したかどうか
よく使うAPI — execa の主要機能と使い方
1. $ — スクリプトモード
$ はシェルスクリプトのような書き心地でコマンドを連鎖的に実行できるインターフェースです。zx に似た使用感を提供します。
import { $ } from 'execa';
// パイプで繋ぐ
const { stdout: name } = await $`cat package.json`.pipe`grep name`;
console.log(name);
// 変数の埋め込み(自動エスケープ)
const branch = await $`git branch --show-current`;
await $`dep deploy --branch=${branch}`;
// 並列実行
await Promise.all([
$`sleep 1`,
$`sleep 2`,
$`sleep 3`,
]);
2. pipe — 複数サブプロセスのパイプ
シェルの | に相当する機能を、型安全かつ中間結果も取得可能な形で提供します。
import { execa } from 'execa';
const { stdout, pipedFrom } = await execa`npm run build`
.pipe`sort`
.pipe`head -n 2`;
// 最終出力: npm run build | sort | head -n 2
console.log(stdout);
// 中間結果も取得可能
console.log(pipedFrom[0].stdout); // sort の出力
console.log(pipedFrom[0].pipedFrom[0].stdout); // npm run build の出力
3. オプションによる入出力制御
ファイル入出力、行分割、インターリーブ出力など、オプションで柔軟に制御できます。
import { execa } from 'execa';
// ファイルからの入力(stdin リダイレクト相当)
await execa({ stdin: { file: 'input.txt' } })`sort`;
// ファイルへの出力(stdout リダイレクト相当)
await execa({ stdout: { file: 'output.txt' } })`npm run build`;
// 行単位で分割して取得
const { stdout: lines } = await execa({ lines: true })`npm run build`;
console.log(lines.slice(0, 10).join('\n')); // 最初の10行
// stdout と stderr をインターリーブ(混合)して取得
const { all } = await execa({ all: true })`npm run build`;
console.log(all);
// 文字列を標準入力に渡す
const { stdout: sorted } = await execa({ input: 'banana\napple\ncherry' })`sort`;
console.log(sorted); // "apple\nbanana\ncherry"
4. ストリーミングとイテレーション
for await...of で出力を1行ずつリアルタイムに処理できます。大量の出力を扱う場合に便利です。
import { execa } from 'execa';
// 行単位のイテレーション
for await (const line of execa`npm run build`) {
if (line.includes('WARN')) {
console.warn(line);
}
}
// Transform 関数で出力をフィルタ・加工
let count = 0;
const transform = function* (line: string) {
if (!line.includes('secret')) {
yield `[${count++}] ${line}`;
}
};
await execa({ stdout: transform })`npm run build`;
5. エラーハンドリングとデバッグ
コマンドが失敗した場合、詳細な情報を含むエラーオブジェクトがスローされます。
import { execa, ExecaError } from 'execa';
try {
await execa`exit 1`;
} catch (error) {
const execaError = error as ExecaError;
console.log(execaError.message);
// Command failed with exit code 1: exit 1
console.log(execaError.exitCode); // 1
console.log(execaError.stdout); // 標準出力
console.log(execaError.stderr); // 標準エラー出力
console.log(execaError.command); // "exit 1"
console.log(execaError.shortMessage);// 短縮エラーメッセージ
console.log(execaError.failed); // true
}
// verbose モードでデバッグ情報を出力
await execa({ verbose: 'full' })`npm run build`;
// ローカルにインストールされたバイナリを npx なしで実行
await execa({ preferLocal: true })`eslint .`;
共通オプション一覧(よく使うもの)
| オプション | 型 | 説明 |
|---|---|---|
stdin | string | object | 標準入力の設定(ファイル、パイプなど) |
stdout | string | object | Function | 標準出力の設定 |
stderr | string | object | Function | 標準エラー出力の設定 |
input | string | Uint8Array | 標準入力に渡す文字列/バイナリ |
all | boolean | stdout + stderr を混合して all プロパティに格納 |
lines | boolean | 出力を行の配列として返す |
shell | boolean | string | シェル経由で実行(通常は不要) |
cwd | string | URL | 作業ディレクトリ |
env | object | 環境変数 |
timeout | number | タイムアウト(ミリ秒) |
preferLocal | boolean | node_modules/.bin のバイナリを優先 |
verbose | 'none' | 'short' | 'full' | デバッグ出力レベル |
類似パッケージとの比較
| 特徴 | execa | child_process (標準) | zx (Google) | shelljs |
|---|---|---|---|---|
| Promise ベース | ✅ | ❌(コールバック) | ✅ | ❌ |
| テンプレートリテラル | ✅ | ❌ | ✅ | ❌ |
| シェルインジェクション防止 | ✅(自動エスケープ) | △(手動対応) | △(シェル経由) | ❌(シェル経由) |
| TypeScript サポート | ✅(優秀) | ✅(基本的) | ✅ | △ |
| ストリーミング | ✅ | ✅ | △ | ❌ |
| パイプの中間結果取得 | ✅ | ❌ | ❌ | ❌ |
| Windows サポート | ✅(強化済み) | △ | △ | ✅ |
| 依存関係 | 少ない | なし | 多い | 少ない |
| ユースケース | 汎用 | 低レベル制御 | シェルスクリプト代替 | シェルコマンド模倣 |
選定の目安:
- プログラムから安全にコマンドを実行したい → execa
- シェルスクリプトをそのまま JS に移植したい → zx
- 依存ゼロで最小限の制御がしたい → child_process
注意点・Tips
ESM 専用(v9以降)
execa v9 は ESM のみ対応です。CommonJS プロジェクトで使いたい場合は v5 系(npm install execa@5)を検討してください。
// package.json
{
"type": "module"
}
シェルモードは基本的に不要
execa はデフォルトでシェルを介さずにコマンドを実行します。shell: true を設定するとシェルインジェクションのリスクが生まれるため、特別な理由がない限り使わないでください。
// ❌ 避ける
await execa({ shell: true })`echo $HOME && ls`;
// ✅ パイプは .pipe() で
const { stdout } = await execa`echo hello`.pipe`grep hello`;
テンプレートリテラルでの変数埋め込みは安全
テンプレートリテラル内に埋め込んだ変数は自動的にエスケープされます。手動でクォートする必要はありません。
const userInput = '; rm -rf /'; // 悪意のある入力
// シェルインジェクションは発生しない(引数として安全に渡される)
await execa`echo ${userInput}`;
reject: false でエラーを抑制
コマンドの失敗時に例外をスローさせたくない場合は reject: false を使います。
const { failed, exitCode, stderr } = await execa({ reject: false })`exit 1`;
if (failed) {
console.error(`Exit code: ${exitCode}, stderr: ${stderr}`);
}
グレースフルターミネーション
execa はサブプロセスの終了を適切に処理します。SIGTERM を送った後、プロセスが応答しない場合は強制終了されます。
const subprocess = execa`long-running-task`;
// 5秒後にキャンセル
setTimeout(() => {
subprocess.kill();
}, 5000);
try {
await subprocess;
} catch (error) {
console.log('Process was terminated');
}
まとめ
execa は Node.js での子プロセス実行における事実上のスタンダードライブラリです。テンプレートリテラル構文による直感的な記述、自動エスケープによるセキュリティ、パイプ・ストリーミング・IPC などの豊富な機能を備えており、child_process を直接使う理由はほとんどなくなります。
v9 で ESM 専用になった点だけ注意すれば、ビルドスクリプトからプロダクションアプリケーションまで、あらゆる場面で安心して採用できるパッケージです。