commander vs yargs 徹底比較

commander の詳細yargs の詳細
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

commander vs yargs ― Node.js CLIフレームワーク徹底比較

1. 結論

シンプルで軽量なCLIツールを素早く作りたいなら commander、複雑なサブコマンド体系やインタラクティブなヘルプを備えた本格的なCLIを構築したいなら yargs を選ぶのがおすすめです。どちらも成熟したパッケージであり、プロダクション利用に十分耐えうる品質を持っています。プロジェクトの規模と求める機能の複雑さで判断してください。


2. 比較表

項目commanderyargs
npm 週間DL数約 1.8 億約 7,500 万
GitHub Stars約 27k約 11k
バンドルサイズ (unpacked)約 190 KB(依存ゼロ)約 300 KB+(複数の依存あり)
依存パッケージ数0約 7〜10
TypeScript 対応同梱型定義ファイル同梱型定義ファイル
サブコマンド✅ ネイティブ対応.command() で対応
自動ヘルプ生成✅(より詳細)
自動バリデーション最小限(手動補完が必要)✅ 豊富(型・必須・排他など)
補完 (Shell Completion)プラグインで対応✅ 組み込み対応
ミドルウェア.middleware()
Strict モード.strict()
学習コスト🟢 低い🟡 やや高い
API スタイルメソッドチェーン(宣言的)メソッドチェーン(ビルダー的)
ライセンスMITMIT

3. それぞれの強み

commander の強み

  • ゼロ依存node_modules を汚さず、インストールが高速です。CI/CD パイプラインでも有利に働きます。
  • 直感的な API — POSIX 規約に忠実で、-p, --port <number> のような記法をそのまま文字列で定義できます。学習コストが非常に低く、README を読むだけで使い始められます。
  • 圧倒的な採用実績 — webpack CLI、create-react-app、Vue CLI など、メジャーなツールが内部で採用しています。週間ダウンロード数は npm 全体でもトップクラスです。
  • 軽量設計思想 — 「必要十分」を追求した設計で、余計な抽象化がありません。小〜中規模の CLI に最適です。

yargs の強み

  • 強力なバリデーション — 型チェック(string, number, boolean)、必須チェック(.demandOption())、排他オプション(.conflicts())、依存オプション(.implies())など、宣言的にルールを定義できます。
  • ミドルウェア機構 — コマンド実行前に共通処理(認証チェック、設定ファイル読み込みなど)を挟めます。Express のミドルウェアに近い感覚です。
  • シェル補完の組み込みサポート.completion() を呼ぶだけで bash/zsh の補完スクリプトを自動生成できます。
  • 高度なサブコマンド管理 — コマンドごとにビルダー関数とハンドラーを分離でき、大規模 CLI の構造化に向いています。
  • Strict モード — 未定義のオプションが渡された場合にエラーを出す .strict() が組み込まれており、ユーザーのタイプミスを即座に検出できます。

4. コード例で比較

以下では 「ファイルを指定ディレクトリにコピーする CLI」 を両パッケージで実装します。

要件

mycli copy --src ./a.txt --dest ./backup/ --verbose

commander 版

// src/cli-commander.ts
import { Command } from "commander";
import { copyFileSync, mkdirSync, existsSync } from "fs";
import { basename, join } from "path";

const program = new Command();

program
  .name("mycli")
  .description("ファイル操作ユーティリティ")
  .version("1.0.0");

program
  .command("copy")
  .description("ファイルを指定ディレクトリにコピーします")
  .requiredOption("-s, --src <path>", "コピー元ファイルパス")
  .requiredOption("-d, --dest <dir>", "コピー先ディレクトリ")
  .option("-v, --verbose", "詳細ログを出力する", false)
  .action((options) => {
    const { src, dest, verbose } = options as {
      src: string;
      dest: string;
      verbose: boolean;
    };

    // コピー先ディレクトリがなければ作成
    if (!existsSync(dest)) {
      mkdirSync(dest, { recursive: true });
    }

    const target = join(dest, basename(src));
    copyFileSync(src, target);

    if (verbose) {
      console.log(`✅ コピー完了: ${src} → ${target}`);
    }
  });

program.parse();

yargs 版

// src/cli-yargs.ts
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { copyFileSync, mkdirSync, existsSync } from "fs";
import { basename, join } from "path";

yargs(hideBin(process.argv))
  .scriptName("mycli")
  .usage("$0 <command> [options]")
  .command(
    "copy",
    "ファイルを指定ディレクトリにコピーします",
    (yargs) =>
      yargs
        .option("src", {
          alias: "s",
          type: "string",
          demandOption: true,
          describe: "コピー元ファイルパス",
        })
        .option("dest", {
          alias: "d",
          type: "string",
          demandOption: true,
          describe: "コピー先ディレクトリ",
        })
        .option("verbose", {
          alias: "v",
          type: "boolean",
          default: false,
          describe: "詳細ログを出力する",
        })
        // src が指定されたら dest も必須、という依存関係を宣言
        .implies("src", "dest"),
    (argv) => {
      const { src, dest, verbose } = argv;

      if (!existsSync(dest)) {
        mkdirSync(dest, { recursive: true });
      }

      const target = join(dest, basename(src));
      copyFileSync(src, target);

      if (verbose) {
        console.log(`✅ コピー完了: ${src} → ${target}`);
      }
    }
  )
  .strict() // 未定義オプションをエラーにする
  .demandCommand(1, "コマンドを1つ指定してください")
  .help()
  .parse();

コード比較のポイント

観点commanderyargs
オプション定義-s, --src <path> の文字列1行で完結オブジェクトで type, demandOption 等を明示
型安全性action の引数を手動キャストビルダーから型推論が効く
バリデーションrequiredOption 程度strict(), implies(), conflicts() 等が豊富
コード量やや少ないやや多いが宣言的で堅牢

5. どちらを選ぶべきか ― ユースケース別ガイド

commander を選ぶべきケース

ユースケース理由
小〜中規模の社内ツールゼロ依存で導入が手軽。学習コストが低く、チームへの展開が容易です
npm パッケージとして配布する CLI依存ツリーが浅いため、利用者の node_modules を肥大化させません
プロトタイプ / スクリプト的な CLI最小限のコードで動くものが作れます
既存プロジェクトへの後付け CLI軽量なので既存の依存と衝突するリスクが低いです

yargs を選ぶべきケース

ユースケース理由
サブコマンドが多い大規模 CLIミドルウェア・ビルダーパターンで構造化しやすいです
ユーザー入力のバリデーションを厳密にしたいstrict(), conflicts(), implies(), check() で宣言的に制約を定義できます
シェル補完を提供したい.completion() で bash/zsh 補完スクリプトを自動生成できます
設定ファイル + 環境変数 + CLI 引数を統合したいyargs は .config().env() で多層的な設定ソースをマージできます

第三の選択肢も検討する場合

近年は citty(unjs 製・軽量)や clipanion(Yarn Berry 内部で使用)なども注目されています。ただし、エコシステムの成熟度・情報量の観点では commander / yargs が依然として安定した選択です。


6. まとめ

シンプルさ・軽量さ重視  →  commander
堅牢性・拡張性重視      →  yargs

commander は「UNIX 哲学的にシンプルなことをシンプルに」を体現したパッケージです。依存ゼロという潔さは、ライブラリ作者やツールチェーンの一部として CLI を組み込む場面で大きなアドバンテージになります。

yargs は「CLI フレームワーク」と呼ぶにふさわしい機能の厚みを持っています。バリデーション、ミドルウェア、シェル補完、設定ファイル統合など、エンタープライズ級の CLI を構築する際に真価を発揮します。

どちらも長年にわたりメンテナンスが継続されており、TypeScript 対応も万全です。まずはプロジェクトの規模感と「どこまで CLI の入力を厳密に制御したいか」を基準に選択するのが、最も後悔の少ない判断になるでしょう。