cac の使い方 — 軽量で強力なCLIアプリ構築フレームワーク
一言でいうと
cac(Command And Conquer)は、依存パッケージゼロ・単一ファイルで構成された超軽量なCLIアプリ構築ライブラリです。Vite や Vitest など著名プロジェクトでも採用されており、4つの基本APIだけでCLIツールが作れるシンプルさと、サブコマンド・バリデーション・自動ヘルプ生成などの高機能を両立しています。
どんな時に使う?
- 自作CLIツールの構築 — プロジェクト固有のビルドスクリプトやデプロイツールなど、コマンドライン引数のパース・サブコマンド分岐が必要な場面
- npm scriptsの拡張 — オプション付きのタスクランナーを手軽に作りたいとき。commander や yargs ほど大げさにしたくない場合に最適
- Vite/Vitest プラグインやツールチェーンの開発 — これらのエコシステムと同じCLIフレームワークを使うことで、一貫した開発体験を得たいとき
インストール
# npm
npm install cac
# yarn
yarn add cac
# pnpm
pnpm add cac
基本的な使い方
最もシンプルなパターンとして、引数パースとコマンド実行の例を示します。
import cac from 'cac'
const cli = cac('my-tool')
cli
.command('greet <name>', 'Greet someone')
.option('--loud', 'Greet in uppercase')
.action((name: string, options: { loud?: boolean }) => {
const message = `Hello, ${name}!`
console.log(options.loud ? message.toUpperCase() : message)
})
cli.help()
cli.version('1.0.0')
cli.parse()
# 実行例
$ my-tool greet Alice
Hello, Alice!
$ my-tool greet Alice --loud
HELLO, ALICE!
$ my-tool --help
# 自動生成されたヘルプメッセージが表示される
よく使うAPI
1. cli.option() — グローバルオプションの定義
すべてのコマンドで共通して使えるオプションを定義します。
import cac from 'cac'
const cli = cac()
cli.option('--verbose', 'Enable verbose output', { default: false })
cli.option('--config <path>', 'Path to config file', { default: './config.json' })
const parsed = cli.parse()
console.log(parsed.options)
// { verbose: false, config: './config.json', '--': [] }
<path> は値が必須、[path] は値が省略可能(省略時は true)です。
2. cli.command() — サブコマンドの定義
git のようなサブコマンド体系を構築できます。
import cac from 'cac'
const cli = cac()
// 必須引数: <dir>、オプション引数: [env]
cli
.command('deploy <dir> [env]', 'Deploy a directory to the specified environment')
.option('--dry-run', 'Simulate deployment without making changes')
.action((dir: string, env: string | undefined, options: { dryRun?: boolean }) => {
console.log(`Deploying ${dir} to ${env ?? 'production'}`)
if (options.dryRun) {
console.log('(dry run mode)')
}
})
// デフォルトコマンド(コマンド名を省略)
cli
.command('[...files]', 'Process files')
.action((files: string[]) => {
console.log('Processing:', files)
})
cli.help()
cli.parse()
3. cli.help() / cli.version() — ヘルプとバージョン表示
import cac from 'cac'
const cli = cac('my-cli')
cli.option('--type [type]', 'Choose a project type', { default: 'node' })
cli.option('--name <name>', 'Provide your name')
cli
.command('lint [...files]', 'Lint files')
.option('--fix', 'Auto-fix problems')
.action((files: string[], options: { fix?: boolean }) => {
console.log('Linting:', files, options)
})
// -h / --help で自動生成されたヘルプメッセージを表示
cli.help()
// -v / --version でバージョンを表示(ヘルプメッセージにも使われる)
cli.version('2.1.0')
cli.parse()
4. command.action() — コマンド実行時の処理
action のコールバック引数は、コマンド定義の引数順 → 最後に options オブジェクトの順で渡されます。
import cac from 'cac'
const cli = cac()
cli
.command('build <entry> [...otherFiles]', 'Build your app')
.option('--minify', 'Minify output')
.option('--out-dir <dir>', 'Output directory')
.action((entry: string, otherFiles: string[], options: { minify?: boolean; outDir?: string }) => {
// 第1引数: <entry> に対応
console.log('Entry:', entry)
// 第2引数: [...otherFiles] に対応(可変長引数は配列で渡される)
console.log('Other files:', otherFiles)
// 第3引数: パースされたオプション(kebab-case → camelCase に自動変換)
console.log('Options:', options)
})
cli.help()
cli.parse()
$ node build.js src/index.ts src/utils.ts src/types.ts --minify --out-dir dist
Entry: src/index.ts
Other files: [ 'src/utils.ts', 'src/types.ts' ]
Options: { minify: true, outDir: 'dist' }
5. command.allowUnknownOptions() / エラーハンドリング
未定義のオプションを許可したい場合や、エラーを自前でハンドリングしたい場合のパターンです。
import cac from 'cac'
const cli = cac()
cli
.command('dev', 'Start dev server')
.allowUnknownOptions() // 未定義オプションでもエラーにしない
.action((options: Record<string, unknown>) => {
console.log('All options:', options)
})
cli.help()
// エラーハンドリング: parse と実行を分離する
try {
cli.parse(process.argv, { run: false })
await cli.runMatchedCommand()
} catch (error) {
console.error(`Error: ${(error as Error).message}`)
process.exit(1)
}
類似パッケージとの比較
| 特徴 | cac | commander | yargs |
|---|---|---|---|
| 依存パッケージ数 | 0 | 0 | 多数 |
| インストールサイズ | ~100KB | ~200KB | ~1MB |
| TypeScript対応 | ネイティブ(TSで記述) | 型定義同梱 | @types/yargs が必要 |
| サブコマンド | ✅ | ✅ | ✅ |
| 自動ヘルプ生成 | ✅ | ✅ | ✅ |
| dot-nestedオプション | ✅ | ❌ | ✅ |
| 学習コスト | 低い(API 4つ) | 中程度 | やや高い |
| 採用実績 | Vite, Vitest, VuePress | Express CLI, Vue CLI | webpack CLI, mocha |
選定の目安:
- 軽量さ・シンプルさ重視 → cac
- 大規模CLI・豊富なプラグイン → commander
- 対話的プロンプト・複雑なバリデーション → yargs
注意点・Tips
kebab-case は自動で camelCase に変換される
--clear-screen オプションは options.clearScreen でアクセスします。--clear-screen と --clearScreen のどちらで渡しても同じプロパティにマッピングされます。
否定オプション(--no-xxx)の定義方法
--no-config のような否定オプションを使うには、明示的に定義が必要です。
cli
.command('build', 'Build project')
.option('--config <path>', 'Use a custom config file')
.option('--no-config', 'Disable config file')
これにより config のデフォルト値が true になり、--no-config で false に設定できます。
配列オプションは同じフラグを繰り返す
# 単一値
$ node cli.js --include project-a
# → { include: 'project-a' }
# 複数値(同じオプションを繰り返す)
$ node cli.js --include project-a --include project-b
# → { include: ['project-a', 'project-b'] }
import の書き方に注意
// デフォルトエクスポート
import cac from 'cac'
// 名前付きエクスポートでも可
import { cac } from 'cac'
TypeScript で使う場合は @types/node を devDependencies に追加してください。
parse() の戻り値を直接使う
コマンドを定義せず、単純な引数パーサーとしても使えます。
const cli = cac()
cli.option('--port <port>', 'Server port')
const { options, args } = cli.parse()
// options.port, args にアクセス可能
まとめ
cac は「依存ゼロ・API 4つ」という圧倒的なシンプルさでありながら、サブコマンド・可変長引数・自動ヘルプ生成など実用的な機能を備えたCLIフレームワークです。Vite や Vitest といったモダンなツールチェーンでの採用実績が信頼性を裏付けています。軽量なCLIツールを素早く構築したい場面では、まず検討すべき選択肢と言えるでしょう。