komura-c.log

https://blog.komura-c.page/ に移動しました

ngコマンドの実装を追う

動機

Angular.jsを使った開発を1年ぐらいしていながら、いつも使っているng serveの実装について追ったことがなかったので追う

たまたまng-japan OnAirのアーカイブを見ていたら、ng buildの実装を追う話をしていて参考になったので、試す

youtu.be

そもそも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関数によって実行されていることがわかった