Taskfileを使ってみよう

2024/06/03に公開

株式会社FLINTERSで業務委託としてお世話になっています、フロントエンドエンジニアのれみゅーです。

この記事は FLINTERSブログ祭り🏮 の記事です。テーマは「技術」でお送りします。

FLINTERSブログ祭り🏮 についてはこちら

はじめに

今回は、最近業務で使用して便利だと感じた Taskfile というツールの紹介と、どのような活用方法があるかを書きます。

対象読者

  • Taskfile の便利な使い方を知りたい人
  • (おまけ) Makefile との比較をしたい人

Taskfile とは

Go言語で実装されたタスクランナーです。

設定ファイルをyamlで記述でき、タスク間の依存関係の定義なども簡単に行えます。

インストール方法

macOS ユーザーで brew が導入済みなら

brew install go-task

が一番楽です。

あるいはGo言語の環境が構築済みであれば、

go install github.com/go-task/task/v3/cmd/task@latest

でインストールすることもできます。

他にも色々なインストール方法があるので、公式のinstallationを見てみると良いでしょう。

使い方

Taskfile を使用するためには、まず Taskfile.yml を作成する必要があります。

Taskfile.yml
version: '3'

tasks:
  hello:
    cmds:
      - echo 'Hello World from Task!'
    silent: true

作成後、同じディレクトリで task hello を実行すると Hello World from Task! と表示されます。

Taskfile では、 tasks 以下のキーがタスク名になり、さらにその下の cmds にコマンドを書いていきます。
そのため、今回の例では hello がタスク名となり、 task helloecho コマンドが実行されました。

さらに Taskfile は通常、以下のように実行するコマンド自体も表示してくれます。

echo 'Hello World from Task!'
Hello World from Task!

しかし、 silent: true をつけることでコマンドの出力のみを見ることができます。
実行されるコマンドとコマンドの出力が混じって見づらいという問題を回避することができるため、私は基本的に silent: true を有効化しています。

次はもう少し実用的な例を見ていきましょう。

Taskfile.yml
version: '3'
  
silent: true

tasks:
  default:
    aliases:
      - list
    desc: List all tasks
    cmd: task -l

  dev:
    desc: Start dev server
    deps:
      - task: dev:backend
      - task: dev:frontend

  dev:backend:
    desc: Start backend server
    dir: ./backend
    cmd: docker compose up

  dev:frontend:
    desc: Start frontend dev server
    dir: ./frontend
    deps:
      - frontend:install-deps
    cmd: pnpm run dev

  frontend:install-deps:
    desc: Install dependencies
    dir: ./frontend
    cmd: pnpm install --frozen-lockfile

雰囲気でも何をやっているのかわかると思いますが、タスク定義を一つ一つ見ていきましょう。

default

default というタスク名を使ってタスクを定義することで、タスク名を指定しなかった場合に実行されるタスクを定義することができます。
また、 aliases でタスクのエイリアスを設定することができ、今回は list というタスク名をエイリアスとして設定しています。

最初のサンプルでは cmds で実行するコマンドを定義していましたが、コマンドが1つだけの場合 cmd で定義することもできます。

cmd に記載している task -l というコマンドは、定義されているタスクのうち説明(desc)が記入されているものを一覧表示するというコマンドです。
そのため、 task/task default/task list のいずれかを実行することで、以下の出力が得られます。

task: Available tasks for this project:
* default:                     List all tasks      (aliases: list)
* dev:                         Start dev server
* dev:backend:                 Start backend server
* dev:frontend:                Start frontend dev server
* frontend:install-deps:       Install dependencies

dev

dev というタスクでは、このあと定義する dev:backend/dev:frontend という2つのタスクを並列実行しています。

deps に複数のタスクを指定することで、それらを並列に実行することができます。
つまり、今回の例ではバックエンドサーバーとフロントエンド環境の両方を1つのコマンドで同時に起動することができるということです。

また、今回は指定していませんが、 cmd/cmds を指定していた場合は deps がすべて終了したあとにコマンドを実行することができます(dev:frontend で紹介しています)。

dev:backend

Taskfile.yml と同じディレクトリにある backend ディレクトリの中で docker compose up を実行します。

このように、Taskfile では実行ディレクトリを簡単に切り替えることができます。

dev:frontend / frontend:install-deps

dev:frontend では pnpm run dev を実行しますが、実行前に frontend:install-deps というタスクを実行します。

通常フロントエンド系のプロジェクトでは、 pnpm install を実行した後に pnpm run dev などを実行してフロント側のサーバーや vite などを起動する必要があります。
しかしこのように設定を書くことで、開発サーバーの起動前に自動的に依存ライブラリのインストールを行うことができます。

git submodule の初期化

上記のサンプルには記載していませんが、最近関わっているプロジェクトで非常に便利な使い方を発見したので、併せて紹介します。

git submodule は何かと面倒が多いと思います。
たとえば、通常のリポジトリと違い submodule を持つリポジトリはclone時に --recursive をつけるか、clone後に git submodule update --init を実行する必要があります。
しかし Taskfile を用いることで、このような手間を減らすことができます。

以下のような Taskfile.yml を作成します(一部省略)。

Taskfile.yml
tasks:
  init-submodule:
    desc: Initialize submodule
    status:
      # `vendors/some-module` ディレクトリが空の場合にサブモジュールの初期化を実行
      - test -n "$(ls vendors/some-module)"
    cmd: git submodule update --init vendors/some-module

status というキーがタスク定義の中にありますね。
これは、 status 以下に列挙されたコマンドの終了ステータスが 0 でない場合に cmd/cmds を実行するという意味になります ( 0 の場合にスキップすると考えても良い)。

test -n "$(ls vendors/some-module)" を実行することで、 vendors/some-module ディレクトリが空かどうかを判定することができます。
そのため、上記のタスク定義で「サブモジュールが初期化されていない場合に初期化を実行する」というタスクが定義できます。

これを使うことで、例えば開発サーバーの実行にサブモジュールが必要な場合のタスクも以下のように書くことができます。

Taskfile.yml
tasks:
  dev:
    desc: Start dev server
    # 必要な場合はサブモジュールの初期化を行う
    deps:
      - task: init-submodule
    # サブモジュールの初期化後に開発サーバーを起動する
    cmds:
      - task: dev:server

  dev:server:
    deps:
      - task: dev:backend
      - task: dev:frontend

タスクの実行条件をシンプルに書けるのも Taskfile の良い点ですね。

Taskfile.yml の分割

さて、 Taskfile.yml の書き方を色々と紹介しましたが、プロジェクトの規模が大きくなるにつれ、 Taskfile.yml が肥大化することが容易に想像できます。
そんなときのために、 Taskfile はタスク定義ファイルの分割にも対応しています。

上記のサンプルを Taskfile.yml taskfiles/Taskfile.backend.yml taskfiles/Taskfile.frontend.yml に分割してみましょう。

Taskfile.yml
version: '3'

silent: true

includes:
  backend:
    taskfile: taskfiles/Taskfile.backend.yml
    # dir を指定しない場合、該当 Taskfile 内で指定する dir の起点がこのファイルと同じディレクトリになってしまう
    dir: taskfiles

  frontend:
    taskfile: taskfiles/Taskfile.frontend.yml
    # 同上
    dir: taskfiles

tasks:
  default:
    aliases:
      - list
    desc: List all tasks
    cmd: task -l

  dev:
    desc: Start dev server
    # 必要な場合はサブモジュールの初期化を行う
    deps:
      - task: init-submodule
    # サブモジュールの初期化後に開発サーバーを起動する
    cmds:
      - task: dev:server

  init-submodule:
    desc: Initialize submodule
    status:
      # `vendors/some-module` ディレクトリが空の場合にサブモジュールの初期化を実行
      - test -n "$(ls vendors/some-module)"
    cmd: git submodule update --init vendors/some-module

  dev:server:
    deps:
      - task: backend:dev
      - task: frontend:dev
Taskfile.backend.yml
version: '3'

silent: true

tasks:
  dev:
    desc: Start backend server
    dir: ../backend
    cmd: docker compose up
Taskfile.frontend.yml
version: '3'

silent: true

tasks:
  dev:
    desc: Start frontend dev server
    dir: ../frontend
    deps:
      - install-deps
    cmd: pnpm run dev

  install-deps:
    desc: Install dependencies
    dir: ../frontend
    cmd: pnpm install --frozen-lockfile

メインの Taskfile.ymlincludes という設定を追加しました。
includes 直下のキーは、読み込むタスクの名前空間のようなものになり、 (キー):(読み込み先のタスク名) という名前でタスクが定義されたのと同じような扱いになります。
そのため、例えば Taskfile.frontend.yml 内で定義されている install-depsfrontend:install-deps というタスクで実行できます。

参考までに上記の Taskfile.yml たちを用意した上で task list を実行してみると以下のような結果が得られます。

task: Available tasks for this project:
* default:                     List all tasks      (aliases: list)
* dev:                         Start dev server
* init-submodule:              Initialize submodule
* backend:dev:                 Start backend server
* frontend:dev:                Start frontend dev server
* frontend:install-deps:       Install dependencies

GNU make との比較

さて、「プロジェクトでよく使うタスクを書いておく」という目的で GNU make を使用する方も多いと思います。
GNU make も似たようなことができるため、筆者も Taskfile を知るまでは Makefile に色々書いて make dev で開発サーバーを立ち上げられるようにしたりしていました。
そこで、筆者が感じた Taskfile の優位性を書いていこうと思います。

マニュアルが読みやすい

GNU makeのドキュメントに比べて、Taskfileのドキュメントの方が読みやすく感じました。

「それってあなたの感想ですよね?」と言われたらそれまでですが、長大な1ページのHTMLより複数ページに分かれている方がページを開いたときの「こんなに長いのか…」感が少なくて読みやすい気がしました。
あとサイト内検索もあるのがポイント高いです。

タスク管理に特化したファイル構造

GNU make はどちらかというと「成果物」と「成果物が依存するファイル」にフォーカスしたシンタックスを持っています。
.PHONY というキーワードでタスク管理のようなこともできますが、タスク管理用に GNU make を使おうと思うと、ほとんどすべてのターゲットに .PHONY をつける必要が出てきてしまいます。

一方 Taskfile はその名の通りタスク管理に特化しており、以下のようなタスク管理に必要な情報をyamlベースで記述できる点がわかりやすいと感じました。

  • タスク名
  • タスクの説明
  • タスクの依存関係
  • タスクの実行条件
  • タスク定義の分割

特に、別ファイルに分割したタスク定義を読み込む際に名前空間をつける必要があるところが特徴的で気に入りました。

リンク

まとめ

本記事では、タスクランナーである Taskfile の紹介をしました。
簡易的かつある程度実用性のありそうなサンプルの紹介を通して、少しでも Taskfile の便利さが伝わると嬉しいです。
この記事を読んで気になった方はぜひ使ってみてください!

GitHubで編集を提案

Discussion