Ionic CLIのソースコードを追ってみよう

三連休初日に、日本中で1人ぐらいにニーズがあるかもしれない記事を書こうと思いたち、せっかくIonic CLIのコントリビューションしてるので（[ionic-team/ionic-cli Contributors](https://github.com/ionic-team/ionic-cli/graphs/contributors)）、Ionic CLIのコードの簡単な解説をしてみます。

```bash
$ npm i -g ionic
```

でインストールして、それ以降、`ionic` コマンドを使えるあいつです。Ionic Teamはソースコードを

https://github.com/ionic-team/ionic-cli

で公開しており、CLIの中身をここで確認することができます。

# Ionic CLIを手元でコンパイルする
GitHubからcloneしたファイルを使って、Ionic CLIを動かすことができます。試してみる場合は、以下の手順を実行してください。

1. https://github.com/ionic-team/ionic-cli をローカルにcloneする
2. `npm i` で依存パッケージをインストール
3. `npm run bootstrap` で、`packages/`以下のモノレポ毎の依存パッケージをインストールして、それぞれをリンクさせる
4. `npm run link` で、グローバルにパスを通します

これで、あなたの`ionic`コマンドは、cloneしたパッケージから実行されるようになりました。ちなみに、ソースコードの変更を行う時は `npm run watch` を実行すると即時反映されます（[参考](https://github.com/ionic-team/ionic-cli/blob/develop/CONTRIBUTING.md)）


## CLIの中身
では、CLIの中身についてみていきましょう。

### ionic start
`ionic start --type=angular` というコマンドを例に、簡単に処理を追っていきます。
このコマンドが実行されると、 `packages/ionic/src/commands/start.ts`が呼び出されます。オプションである`--type=angular`については[L96-L100](https://github.com/ionic-team/ionic-cli/blob/develop/packages/ionic/src/commands/start.ts#L96-L100)で定義されています。

```packages/ionic/src/commands/start.ts
{
  name: 'type',
  summary: `Type of project to start (e.g. ${lodash.uniq(starterTemplates.map(t => t.type)).map(type => chalk.green(type)).join(', ')})`,
  type: String,
},
```

ちなみに、ここを削除したら、`type`オプションが使えなくなります。オプションを増やす時は、まずここにオプションを定義します。

処理をおおざっぱに説明します。まず、`type`オプションがある場合はその文字列を、ない場合はデフォルト値として`ionic-angular`が変数`projectType`に代入され、`this.validateProjectType`で、「入力された文字列が正しいか」の検証を受けます。[L202-203](https://github.com/ionic-team/ionic-cli/blob/develop/packages/ionic/src/commands/start.ts#L202-L203)行目です。

```packages/ionic/src/commands/start.ts
const projectType = options['type'] ? String(options['type']) : 'ionic-angular'; await this.validateProjectType(projectType); 
```

現在Ionicのtypeは、Ionic v1を意味する `ionic1`、 Ionic v2-3の `ionic-angular`、 Ionic v4の `angular` の3通りがあり、それ以外はエラーがでます。ここで、CLIではじめるIonicのバージョンを決定しています。

次に、プロジェクト名とスターターテンプレートについて入力済みかどうかの判定があったあと（入力がなかった場合は、入力を求める）、ファイルの生成を行います。[L403-408](https://github.com/ionic-team/ionic-cli/blob/develop/packages/ionic/src/commands/start.ts#L403-L408)です。

```packages/ionic/src/commands/start.ts
if (this.schema.cloned) {
  await this.env.shell.run('git', ['clone', this.schema.url, projectDir, '--progress'], { stdio: 'inherit' });
} else {
  const starterTemplate = await this.findStarterTemplate(this.schema.template, this.schema.type, tag);
  await this.downloadStarterTemplate(projectDir, starterTemplate);
}
```

スターターテンプレートがURL指定だった場合は、そこを`git clone`することを試みます。文字列の場合は、[公式のstertar template](https://github.com/ionic-team/starters)で存在確認をしたあと、`git clone`します。

ですので 

```bash
$ ionic start projectName https://github.com/rdlabo/ionic-wpcom
```

とか入力すると、ネット上で公開されているパッケージを使ってそのまま`ionic start`することができるわけです。ハンズオンに役立つ豆知識です。

### ionic serveをみてみる
続いて、`ionic serve`についてです。

実行されると、[packages/ionic/src/commands/serve.ts]
(https://github.com/ionic-team/ionic-cli/blob/develop/packages/ionic/src/commands/serve.ts)が呼び出されるのですが、ここに記述されている処理はあまりなく、`packages/@ionic/cli-utils/src/lib/serve.ts`にある`serve`メソッドを呼び出しています。

```packages/@ionic/cli-utils/src/lib/serve.ts
async run(inputs: CommandLineInputs, options: CommandLineOptions): Promise<void> {
  if (!this.project) {
    throw new FatalException(`Cannot run ${chalk.green('ionic serve')} outside a project directory.`);
  }

  // TODO: use runner directly
  await serve({ flags: this.env.flags, config: this.env.config, log: this.env.log, prompt: this.env.prompt, shell: this.env.shell, project: this.project }, inputs, options);
  await sleepForever();
}
```

この`serve`メソッドに受け渡されている`project: this.project`が重要で、これは`packages/@ionic/cli-utils/src/index.ts` の`getProject`メソッドで取得してきている「そのプロジェクトのIonicのバージョン」が格納されています。

```packages/@ionic/cli-utils/src/index.ts
projectFile = await readJsonFile(projectFilePath);
```

具体的には、プロジェクト直下の`ionic.config.json`の

```ionic.config.json
{
  "type": "ionic-angular"
}
```

の値を見に行っています。

そして、そのIonicのバージョンに従って、`@ionic/cli-utils/src/lib/project/`以下のファイルを呼び出して実行します。

例えば、Ionic v3（=`ionic-angular`）を使っている場合は[`packages/@ionic/cli-utils/src/lib/project/ionic-angular/serve.ts`](https://github.com/ionic-team/ionic-cli/blob/develop/packages/%40ionic/cli-utils/src/lib/project/ionic-angular/serve.ts)が呼び出されますし、v4(=`angular`)を使っている場合は[`packages/@ionic/cli-utils/src/lib/project/angular/serve.ts`](https://github.com/ionic-team/ionic-cli/blob/develop/packages/%40ionic/cli-utils/src/lib/project/angular/serve.ts)が呼び出されます。

内部サーバ立ち上げたり、IPアドレスで起動確認したりあたりはソースコード読んでいただけますと幸いです。

## パッケージの簡単な説明
`packages/@ionic/`をみると、様々なパッケージがあって混乱すると思うので、私がさわったことあるパッケージのみですが、軽く解説しておきます。

| パッケージ名 | 概要 |
|:-----------------|:------------------|
| cli-framework | CLIフレームワークです。ヘルプを呼び出すとか、ログに色つけるとか、そういうCLIっぽいメイン処理をします。 |
| cli-utils | CLIのコマンド毎のライブラリ、ヘルパーみたいなもの。Ionicのバージョンによって異なる処理を行う場合はここにメイン処理を書く感じになる。 |
| discover | さわったことないです |
| lab | `ionic serve --lab`でIonic Labを立ち上げる時に使う処理が書いてある。どういう機能かは[「ionic serve --lab」で、エミュレーターっぽく動かす](https://qiita.com/mucunwuxian/items/617ba35aaf2e66960978)参照。 |
| ng-toolkit | Ionic v4では、Angular CLIをラップしてIonic CLIを動かすために必要なファイル群。「Angular CLIでビルドしたあとに、Cordova CLIを実行する」みたいな処理が書かれてたはず。 |
| schematics-angular | Ionic v4で、`ionic g`コマンドを実行するためのもの。`ng g`コマンドをラップするのに必要なファイル。 |
| utils-fs | `fs`のラッパー。TypeScriptで書きやすく、かつちゃんとPromiseで返してくれるようになってます。voidやcallbackはもうやめよう。 |
| utils-network | `ionic serve` の時のPortまわりとか。DevAppsの疎通とか。 |
| v1-toolkit | Ionic v1まわり |

# まとめ
駆け足でしたが、Ionic CLIの中身に興味をもつきっかけになりましたら幸いです。それでは、また。