Angular 8 の Angular CLI Builders について学ぶ


この記事は Angular Advent Calendar 2019 の 17日目の記事です。

私は普段 AngularJS と戯れているので自前の webpack config をメンテしているのですが、 Angular は @angular/cli という素晴らしいビルドツールがよしなにやってくれるそうです。私の居る世界からは天国に見えますね。

一方、今のプロジェクトを移行しようとするとデフォルトの挙動では満足できないことがありそうです。Angular が用意していくれている仕組みに乗っかっていくように修正していくのが完全に正しいのですが、念のために Angular が用意してくれている拡張機能である Angular CLI builders を学んでいこうと思います。 builders 知ってる/書いたことあるという方はゴメンナサイ。

早速、ガイドをなぞっていきます。下記コードには https://angular.jp/guide/cli-builder からの抜粋が含まれます。

Angular CLI Builders とは

Angular CLI をカスタマイズするための機能が提供されています。なお、 schematics は ng generate コマンドで利用される scaffold を提供する機能で、別物とのことです。使い分けましょう。

Angular CLI Builders の本体は引数に JsonObjectBuilderContext, 戻り値に結果が入る関数です。

メインパート

import { BuilderOutput, createBuilder } from '@angular-devkit/architect';

export default createBuilder(_commandBuilder);

function _commandBuilder(
  options: JsonObject,
  context: BuilderContext,
  ): Promise {
  ...
}

JsonObject は JsonObject なので自明として、 BuilderContext が何者か気になります。

BuilderContext とは何者か

定義はここにあります。

ディレクトリへのファイルパス、ターゲット(development とか production とか)、進捗をコンソールに出すための reportStatus(status: string)reportProgress(current: number, total?: number, status?: string) という便利そうな関数や、ロガーへのアクセスなどが提供されています。
ここからいろいろな情報を取得したり、連携したりしながら、処理をして結果を返せば良さそうです。

戻り値については、同期的に処理するなら結果そのもの、非同期的で処理するなら Promise, 変更などを監視して継続的に処理するなら Observable を返すようにします。

ガイドには関数内で childProcess を spawn して非同期処理をさせるコードが掲載されていますので、参考にしてみてください。

options は何が渡せるのか/どうやって渡すのか

BuilderContext から target や project へアクセスできることが分かったので、じゃあ options は何のためにあるんだろうという気持ちになってきました。引き続きガイドの Builder input を読み進めてみます。

options の schema は自分で定義できます。やりたい放題です。ガイドの任意のコマンドを渡せるサンプル builder の schema はこれ。

{
  "$schema": "http://json-schema.org/schema",
  "type": "object",
  "properties": {
    "command": {
      "type": "string"
    },
    "args": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  }
}

定義した options に応じた値を渡すには、 angular.json を使います。
angular.json に自分が定義した builder を使うタスクを定義し、含まれるライブラリの参照を渡します。

{
  "projects": {
    "builder-test": {
      "architect": {
        "touch": {
          "builder": "@example/command-runner:command",
          "options": {
            "command": "touch",
            "args": [
              "src/main.ts"
            ]
          }
        },

急にここで「ライブラリ」というのが出てきたんですが、ライブラリは Angular を拡張するための便利な仕組みです。再利用な可能なコードはどんどんライブラリに追い出すことでコードの見通しがよくなったり、ビルド時間が削減されて便利という話を勉強会で聞いたことがありますので、臆さず使っていきましょう。使い方はこちらにあります。脇道にそれてしまうのでこれ以上は触れません。

ライブラリは npm package になるので、これをインストールしましょう。Github Packagesで private npm package registry の敷居が下がって嬉しいです。

どうやって実行するのか

あとは ng run project-name:command-name で実行できます。このサンプルだと、ng run builder-test:touch となります。
options はコマンドラインから上書きすることもできます。

builder をテストする仕組みも提供されています。至れり尽くせりです。

補足その1: watch mode について

ファイルの変更などを監視してタスクを実行する watch mode の builder 実装について言及があります。
ガイドで解説するには高度な内容なようで、さらっと書かれています。webpack を拡張している @angular/client ですが、作法が異なるので注意せよとのことです。

補足その2: builder の chain について

説明しやすくするために途中で飛ばしましたが、BuilderContextscheduleBuilder() を使うことで builder から他の builder を呼びだすことができます。

ここまで読み進めましたが・・・

私は ng build を拡張したいと思っていたのですが、どうやらこのガイドは別のコマンドを作るところがゴールでした。。
ちなみに Angular In Depth にも Angular CLI を使って custom builder を作る手順が紹介されていますので読んでみてください。

じゃあどうやって ng build や ng serve を拡張すれば良いのか

改めて angular.json を見ると、それぞれのタスクは @angular-devkit/build-angular:browser@angular-devkit/build-angular:dev-server という builder に移譲されています。実は、カスタマイズが webpack の configuration で完結する場合には @angular-builders/custom-webpack に置き換えることで自作の webpack config をマージすることができます。以前は ng eject して書き換える方式でしたが、この仕組みに置き換わりました。

それ以上の事をしようと思ったら、@angular-devkit/build-angular を fork するという険しい道を歩むしかないのでしょうか。Readme が WIP なので震えますね。以前、component の annotation で定義されている css や template がどうやってトランスパイルされているのかに興味を持って build-angular が生成する webpack config を覗き見たことがあるのですが、凄まじいことになっていました。そのすべてがここに記されているのでしょう。

ちなみに、私が扱っているビルドスクリプトでは gulp で前処理をしているので、webpack でできない前処理を今まで通り gulp で行ってから 、custom-webpack で拡張した ng コマンドを gulp スクリプトから呼び出す方式を想定しています。それが駄目だったら考えます。

まとめ

新しい何かを試したり、作ったりする記事でなくて物足りない方もいらっしゃると思います。 良いネタが何も思いつかなかったのですゴメンナサイ

個人的には、今回ガイドを丁寧に読み下してみて、良い勉強になりました。少し面倒に思っても、ガイドは読むべきですね。せっかく最後まで読みましたし、日本語翻訳のプロジェクトがあるようなので挑戦してみようと思います。(本当はこの記事と一緒に PR 出そうと思ってたんですが、時間が足りませんでした。)

おわりに

早いもので 2019年もあと残すところ2週間です。

普段は IoT 通信プラットフォーム開発 の傍ら、 AngularJS で書いたアプリを Angular に移行するお仕事を同僚と進めています。今年は Angular 移行をどんどんやっていくぞ!という気持ちが高まり、Angular 日本ユーザー会 のイベントや ng-japan に初めて参加させていただいて、いろいろな刺激を受けた 1年となりました。勉強会でLTさせていただいたこともあるので、ご興味がある方は当時の資料 その1 その2 をご笑覧ください。

2015年から AngularJS を使っていた身としては、もっと早くから参加しとけば良かったなぁと後悔しきりですので、これから Angular を触ろうという方や、触っているけどまだ参加されていない方は、ぜひご参加されてみると良いのではと思います。

いまだに AngularJS を触っている方がどれだけ居るかわかりませんが、移行やっていくぞという方はぜひ勉強会などで苦労を分かち合いましょう。2020年も Angular の発展を祈念しつつ、何らかお手伝いができればいいなと思っています。