ngコマンドの実装を追う
動機
Angular.jsを使った開発を1年ぐらいしていながら、いつも使っているng serveの実装について追ったことがなかったので追う
たまたまng-japan OnAirのアーカイブを見ていたら、ng buildの実装を追う話をしていて参考になったので、試す
そもそもcliツールがどう動いているか
npm scriptsに書いてあるng 〇〇(global installでない)がなぜ動くか、 angular-cliのpackageのpackage.jsonにbinを指定する項目があり、 インストールした時に指定されたパスに対して、シンボリックリンクが作られる
node_modules/@angular/cli/package.json
"bin": { "ng": "./bin/ng.js" },
どこに作られるかというと、node_modules/.bin
内で
node_modules/.bin/ng
の実態はpackage内の@angular/cli/bin/ng.js
ということになる
なぜシンボリックリンクが作られると動くのかというと、
npm runは、実行したPATHに加えて、node_modules/.bin
をnpm scriptsの実行PATHに追加するという仕様になっているからである
ng.js
ng.jsを見ていくと、nodeの対応versionによってwarnやerrorを出した後、
同じpackage内のbootstrap.js
を読み込み、bootstrap.js
は../lib/init.js
を読み込む
init.js
はAngularのバージョンチェックなどを行なった後、
./cli/index.js
からdefault exportされているdefault_1
という関数にng 〇〇の〇〇の部分を渡している
このdefault_1
という関数でng newしたangularのプロジェクト(ワークスペース)のangular.jsonファイルを読み込み
コマンド名とcommand/xxx.jsonを照らし合わせてCommandDescriptionクラスを作っている
@angular/cli/models/command-runner.js
description = await loadCommandDescription(commandName, commands[commandName], registry);
command/xxx.json
の$implにはコマンド実態へのパスcommand/xxx-impl
が記述されていて、
これを読み込んでimplに入れる(今回はServeCommandクラス)
angular-cli/serve-impl.ts at master · angular/angular-cli · GitHub
その後CommandDescriptionクラスのimpl関数を使うことでCommandConstructor(ServeCommand)クラスを作る
const command = new description.impl(context, description, logger);
ServeCommandクラスの実態は、Commandクラスを継承したArchitectCommandクラスなので、Commandクラスの関数であるvalidateAndRunを実行
const result = await command.validateAndRun(parsedOptions);
その関数内でServeCommandのrunを実行する
result = await this.run(options);
これで各コマンドのrun関数によって実行されていることがわかった