🎄

Biomeを使ったLintとフォーマット / TypeScript一人カレンダー

2024/12/24に公開
1

こんにちは、クレスウェア株式会社の奥野賢太郎 (@okunokentaro) です。本記事はTypeScript 一人 Advent Calendar 2024の24日目です。昨日は『tsupでバンドルする』を紹介しました。

ESLintというlintツール

ESLintという開発補助ツールがあります。JavaScript / TypeScriptでの開発においておなじみとなってきたこのツールは、ソースコードを静的に解析し、バグの可能性やスタイルの乱れを指摘してくれるLintツールです。

チーム全体のコーディング規約を統一でき、その違反を自動で指摘できるため、大人数での開発で生じがちな品質のぶれやコードレビュー時の負担を軽減できます。チーム開発において、可読性や保守性を高めるうえでも欠かせない存在になっています。

ESLintは長年、Webアプリケーション開発の現場で愛用されてきましたが、近年はFlat Config対応という非互換のアップデートの話題が盛り上がる一方で、既存のルール資産から移行が進まずESLint 8に置き去りにされてしまったという開発者の悲しい声も聞こえてきます。

Prettierというフォーマットツール

Prettierという開発補助ツールもあります。ソースコードのインデントや括弧の位置、改行場所などを一括で調整してくれるフォーマットツールとして、事実上のデファクトスタンダードのように広く使われています。

筆者も2018年頃から導入し利用していました。コードフォーマットは手作業では手間がとても多く、チーム全員が同じ設定を使って統一したスタイルを揃える操作を手動で徹底するのは、なかなか骨が折れます。Prettierは一瞬で自動フォーマットを実施するため、そうした煩雑さをなくしてくれる点で人気です。

Biomeの台頭

そんななかで登場してきたのが、LintとFormatを両方こなす統合ツールのBiomeです。 BiomeはRustで実装されており、これによって劇的に高速な処理を実現しているのが特徴です。

フォーマット整形のコマンドは以下のように実行します。

npx @biomejs/biome format --write <files>

Lintについては以下のように実行します。この際--writeを指定することで、ルールに違反している箇所が自動的に適合するように、検証だけでなく変換処理も同時に実施することができます。

npx @biomejs/biome lint --write <files>

Biomeは、ESLintやPrettierと完全に置き換えられるわけではないものの、実務上では困らないレベルで互換性があるため、筆者は直近の案件すべてでBiomeへ移行を済ませました。

乗り換えにあたりPrettierには大きな不満があったわけではありませんが、一方でESLintのFlat Config対応にともなう保守コストや将来的な負担を考慮すると、Biomeへの移行が自分たちにとってよりよい選択肢に思えたのです。 いざ試してみると想像以上の速度でLintとFormatが完了する魅力があり、即採用を決定しました。

Biomeへの乗り換え手順

プロジェクトをBiomeへ移行するとき、筆者のチームではあらかじめクオートやインデントスタイルをBiomeに合うように切り替えてから本格導入する方法を取りました。 当時、もともとシングルクオートとスペース2文字インデントを採用していましたが、それは過去に担当した案件の.editorconfigをそのまま引き継いでいた名残にすぎず、特にこだわりがあったわけではありません。

そこで、今後の保守コストを減らすためにも「チームのオピニオンを主張するよりもBiomeのデフォルトに寄り添うほうがよいのでは」という議論を経て、最終的にはダブルクオートとタブインデントへ変更することにしました。

差分が大量になることは明らかだったので、Biomeを一度インストールし、何も考えずに全てを変換した場合にどのような差分の規模になるかを確認しました。案の定大量だったため、そこから変換対象となる概念を分解し手順を組み立て、まずはimport文の整形から着手しました。

// Biome 導入前にプロジェクトで揃っていた書き方
import { type Something, type Foo, type Bar } from "something";

// Biome が変換する形
import type { Something, Foo, Bar } from "something";

import { typeの形式がimport type {の形式に変換されることがわかったため、Biomeをインストールする前に先にESLint consistent-type-importsの変換を使って統一させました。

続いてクオートの変更について、シングルクオートをダブルクオートへ変える作業を行い、PRを作成・マージします。その後、スペースインデントをタブインデントへ置換する作業を実施し、こちらもPRを作成・マージしておきます。

そうしてコード全体のスタイルがBiomeの期待する形式に揃った段階で、ようやくBiomeをインストールします。このとき、Biomeの設定biome.jsonではLintルールを全てオフにします。まずはフォーマット機能だけを適用させました。そこからPrettierをアンインストールし、さらに一旦全てオフにしていたBiomeのLintルールに対して推奨設定(Recommendation)を有効にしつつ、細かいbiome.jsonの調整を経て、最終的にESLintもアンインストールします。

マイグレーションの手順はここで紹介したものに限らず、公式にも案内されていますので、一度確認してみるとよいでしょう。マイグレーション完了時のbiome.jsonは次のような見た目をしています。

{
  "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
  "files": {
    "ignore": [
      ".next/**/*",
      ".vercel",
      "tsconfig.vitest-temp.json",
      "vitest-v8-coverage/**/*"
    ]
  },
  "organizeImports": {
    "enabled": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      // 以下、チームの需要に応じて recommended 以外のルールを有効化したり、厳しすぎるルールを無効化したり
    }
  }
}

結果として15万行規模のWebアプリケーション案件で、ESLint時代には3分はかかっていたLint処理が、Biomeに置き換えたことでわずか数秒で終わるようになりました。こういったタスクはCIで何度も走らせるものなので、この短縮効果はとても大きな効果です。

CIは実行時間に応じて課金されるプラットフォームもあります。高速化に励むことは、事業の維持費を削減することにも繋がりますので、効果次第では開発者以外の仲間を喜ばせることにつながるかもしれません。

タブインデントへの抵抗感

筆者は「タブインデントは嫌だ」という声を聞いたことがありますが、これは見た目だけを嫌っているケースをしばしば見てきました。

見た目については、エディタ側でtab width2(言語によってお好みの慣れた数字)に設定すると、意外と違和感がなくなるものです。デフォルトの48の幅のままでは、TypeScript利用者からすれば広すぎて抵抗感があるのも仕方ない話ですが、この大きさは変更できると知っておくと、タブインデントを嫌うことなく読みやすいサイズで落ち着くことができるかもしれません。

また、エディタはよくてもGitHubが読みづらくなるという意見もよく聞きます。実はこれも変更が可能です。GitHubの設定画面でSettings > Appearance > Tab size preferenceから変更でき、この値を2にしてしまえば、以前とほとんど変わらない感覚でコードを読めるはずです。

最終的にそれでも嫌な方は、Biomeを導入してもbiome.jsonに指定を加えてスペースインデントを続行すればよいでしょう。ただ、デフォルトとなっているルールから何かを変更するときは「なぜそのルールを変えるのか」という背景と意図をしっかり共有する必要があると考えています。強いこだわりがないなら、あえて現状のルールをいじらないほうがスムーズなケースも多いと思います。筆者もその考えで、Biomeのデフォルトフォーマットルールはすべて受け入れています。

5匹の猿の実験

余談ですが、ひとつ面白い話があります。

とある実験をイメージしてください。5匹の猿がいる柵の中には梯子、そして登った先にはバナナが設置されます。1匹の猿がバナナを手に入れるべく、梯子を登ると、残りの4匹の猿には水が浴びせられます。そして他の猿が梯子を登ろうとしても、また残りの猿は水を浴びます。これを学習した猿たちは、その後新入りの猿が梯子を登ろうとしても水を浴びたくないために、新入りを攻撃するようになります。

そうして1匹ずつ猿を置き換えていきすべての猿が置き換わって、水を浴びた経験のある猿が全ていなくなっても、梯子を登ろうとする猿はいなくなった——このような思考実験があります。

これは正式な論文の確認がないフィクションなのですが、とはいえ、これに近い状況は開発現場でも実際に起こったりします。

なぜかルールを変更し全員がそれに従っているうちに、ルールを変更した開発者がチームを脱退した。そしてルールが変更されたときに居合わせた開発者が順番に全員脱退しても、そのあと残り続けた開発者はルールを守り続け、その後の新人開発者が「なぜこのルールを厳守しているのか?」と聞いても理由はわからないものの、デフォルトに戻すことはなぜかためらう。

猿と揶揄したい意図は全くないですが「前提がわからないのになぜか従い続け、変化も拒む」という組織は意外とあるものです。心当たりがある方もいるんじゃないでしょうか。筆者がこのエピソードから伝えたいのは、あらゆる意思決定、前提条件の策定は、そこに至るまでの過程を残すべきであるということです。

Biomeのルールについても「ただ前の案件から設定をコピペしてきた」という手順はよくある流れです。ここで、なるべく「コピペした事実」も含めてよいですから、事実や経緯をログに積極的に残すように心掛けたほうが、長期的な開発チームの運営に結びつくと考えています。コピペと分かれば「じゃあ今のチームに合わせてゼロベースで考え直そう」という機運にも繋がりますね。

今後のBiomeに期待

Biomeの前進にRomeというツールがありました。これはBabelYarnの生みの親であるsebmck氏も立ち上げに関わっていた統合ツールで「すべての道はローマに通ず」の如く、Webアプリケーション開発におけるすべてのツールをRomeで集約するという野望が掲げられていました。最終的にこのプロジェクトは閉ざされてしまいましたが、その時の遺産の公式フォークがBiomeです。

Biomeも、まだ初期は導入を悩むアーリーアダプター向けという側面が漂っていましたが、2024年12月現在で1.9.4が提供されるなど、今では1.0のStable releaseを経ています。npmでも週100万ダウンロード以上を記録しており、その魅力に気付き始めた開発者は多い印象です。

筆者もすっかりBiomeを愛用していますが、現状ESLintとの決定的な違いとしてプラグイン機構を備えていない点は注意が必要です。カスタムルールを実装したいという要望は当然存在しており、メンテナによると「哲学的な理由でカスタムルールを受け入れていないわけではなく、単にその機能を提供するだけの時間が取れていない」と回答されていました。2024年12月現在でもまだカスタムルールは採用されていませんが、ESLintのようにプラグイン機構が備わったときにはさらに既存のESLintエコシステムからの移植が増え、よりエコシステム全体が盛り上がっていくのではないかと期待しています。

明日は『今後のTypeScriptに期待すること』

本日は『Biomeを使ったLintとフォーマット』を紹介しました。明日は最終日『今後のTypeScriptに期待すること』を紹介します。それではまた。

Discussion