🦁

PrettierとESLintが不要に!?噂のBiomeを触ってみた!

2024/01/19に公開

はじめに

Biome とは ESLint や Prettier を合体させたようなライブラリです。
今回は Nextjs で Biome を日本語公式ドキュメントに沿って触ってみました。
公式ドキュメント

インストール方法

インストールは npm,yarn,pnpm,bun からインストールできます
今回は pnpm を使用していくので pnpm 以外は割愛します。

pnpm add --save-dev --save-exact @biomejs/biome

設定方法

設定方法は biome.json ファイルを作成して設定していきます。
npm なら npx,yarn なら yarn で
@biomejs/biome initをコマンドで打ち込むと
biome.json とデフォルトのコードが生成されます。

pnpm dlx @biomejs/biome init

Biome は基本デフォルトで使えることを
強く謳っていますが、
細かい設定もできるのでそちらも紹介していきます。

使い方

formatコマンドで--writeオプションをし、
<files>の中にフォーマットしたいファイルを指定することで、
Prettier と同じようなフォーマットを行うことができます。

pnpm dlx @biomejs/biome format <files> --write

lintコマンドで ESLint と同じようなことを効かせる
ことができます。
--applyオプションすることにより、できそうな範囲を
自動で修正してくれるようになります。

pnpm dlx @biomejs/biome lint <files>

checkコマンドでは先程の2つのことを
実行することができます。
こちらも--applyオプションで自動で修正してくれるようになります。

pnpm dlx @biomejs/biome check --apply <files>

大きなプロジェクトでの使用方法

設定ファイルをいくつか置くことができます。
その設定ファイルとかを extends したりとか
それぞれのプロジェクトごとに設定を行うことができます。

ディレクトリー構造

backend は backend で front は front で Biome を
効かせることができます。
frontend ディレクトリ内のlegacy-appnew-app
biome.json ファイルが無いですが
Biome は設定ファイルが見つかるまで上へ上へとディレクトリを移動します。

なのでlegacy-appnew-appの biome 設定ファイルは
frontend 内の biome.json ファイルが適応されます。

設定の共有

extends オプションで設定ファイルを共有することができます。

{
  "extends": ["../biome.json"],
  "formatter": {
    "indentStyle": "space"
  }
}

上記は extends で 1 つ手前のディレクトリ内の
biome.json の設定を引き継いでかつ、
上書きしたい設定を書いています。

設定

biome.jsonはプロジェクトのルートディレクトリに置く必要があります。
通常package.jsonが置かれているところがルートディレクトリです。

設定方法としてformatterlinterに分かれており
わかりやすく設定が行えます。

{
  "formatter": {
    //有効にするか
    "enabled": true,
    //インデント
    "indentStyle": "tab",
    //文字の折返し
    "lineWidth": 120
  },
  "linter": {
    //有効にするか
    "enabled": false
  }
}

package.json や lock ファイル tsconfig.json などは
Biome の設定から自動で外されるようになっています。

VSCode で使う際

VScode では Biome の拡張機能があるので入れておきましょう
Biome VS Code extension

拡張機能を入れることで以下のことができるようになります。

・ファイル保存やコマンドを実行したさいに format を実行する。
・ファイルの静的解析をし、コードの修正を適用してくれます。

Analyzer Import

import の順番

Biome は import 文を距離によってソートします。
ユーザーから遠いモジュールが上に配置され、
ユーザーに近いモジュールが下に配置されます。
以下の順番で整理されるようになります。

  1. bun: Bun によって実行されるコード

  2. node: assert などの一般的な Node ビルドイン

  3. npm: Deno で実行されるコード

  4. URL を介してインポートされたもの

  5. ライブラリからインポートされたもの

  6. absolute import(絶対インポート) を使用してインポートされたもの

  7. #の接頭辞が付いた名前からインポートされたもの

  8. relative import(相対インポート)を使用してインポートされたもの

  9. 前述の基準で識別できなかったモジュール

import uncle from "../uncle";
import sibling from "./sibling";
import express from "npm:express";
import imageUrl from "url:./image.png";
import assert from "node:assert";
import aunt from "../aunt";
import { VERSION } from "https://deno.land/std/version.ts";
import { mock, test } from "node:test";
import { expect } from "bun:test";
import { internal } from "#internal";
import { secret } from "/absolute/path";
import React from "react";

このコードが次のようにソートされます。

import { expect } from "bun:test";
import assert from "node:assert";
import { mock, test } from "node:test";
import express from "npm:express";
import { VERSION } from "https://deno.land/std/version.ts";
import React from "react";
import { secret } from "/absolute/path";
import { internal } from "#internal";
import aunt from "../aunt";
import uncle from "../uncle";
import sibling from "./sibling";
import imageUrl from "url:./image.png";

グループインポート

import "../styles/reset.css";
import "../styles/layout.css";
import { Grid } from "../components/Grid.jsx";

上記のコードはよくある reset.css をした後に layout.css を 宣言して、
Grid コンポーネントを読み込んでいます。
自動ソートで順番が変わってしまうのはよくないです。
こういった場合に import 文のグループを作成することができます。

やり方は簡単で改行することによりグループを作成することができます。

// グループ 1, この2つのファイルがソートされます。
import "../styles/reset.css";
import "../styles/layout.css";

// グループ 2, この1つのファイルがソートされます。
import { Grid } from "../components/Grid.jsx";

ソートさせたくないものをグループ化する以外で
import type等のモジュールを分けたい場合などにも
グループ化することができます。
見栄えがよくなりますね

CLI でも import のソートを変更することができます。

biome check --apply ./path/to/src

VSCode の拡張機能では settings.json をいじって設定を行います

settings.json
{
  "editor.codeActionsOnSave": {
    "source.organizeImports.biome": true
  }
}
{
	"editor.defaultFormatter": "biomejs.biome",
	"editor.formatOnSave": true,
	"editor.codeActionsOnSave": {
		//常にオーガナイズインポートを保存時に効かせることができる
		"source.organizeImports.biome": "always"
	}
}

Formatter

Pritter相当のことをしてくれます。
例えばインデントの整理を行ってくれます。

export const metadata: Metadata = {
             title: 'Create Next App',
  description:        'Generated by create next app',
}

このようにインデントがめちゃくちゃでも

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

このようにインデントをきれいにしてくれます。

細かい設定

Formatterをbiome.jsonで細かい設定を行うことができます。
以下はデフォルトです。

{
  "formatter": {

    //フォーマッタを有効にするか
    "enabled": true,

    //エラーのあるドキュメントをフォーマット
    "formatWithErrors": false,

    //インデントのスタイル
    "indentStyle": "tab",

    //インデントの大きさ
    "indentWidth": 2,

    //一行に何文字まで書けるか
    "lineWidth": 80,

    //行末のタイプ
    "lineEnding": "lf",

    //スタイル パターンの配列 ["scripts/*.js"]など
    "ignore": []
  }
}

formatの無効化

// biome-ignore format: <説明文>
とすることでコードのフォーマットを無効かすることができます。
説明文の部分は必須です

Pritterとの違い

  1. 全ての有効な引用符を外してくれます。
const obj = {
  a: true,
  b: true,
-  "𐊧": true,
+  𐊧: true,
};
  1. 計算プロパティでの代入で一貫性のない挙動をしない
a = {
-  [(x = 0)]: 1,
+  [x = 0]: 1,
};

class C {
  [x = 0] = 1;
}
  1. 必須でない場合はアロー関数の
    型パラメータの末尾にコンマを追加しない
- <T = unknown,>() => {};
+ <T = unknown>() => {};
  1. 括弧で囲まれた非 null アサートの
    オプション チェーンに対して1貫制をもたせる
  a.?.b!
- (a.?.b)!
+ a.?.b!
- (a.?.b)!
+ a.?.b!
  1. Prettierにはいくつかの無効なフォーマット構文があります。

  2. TypeScript と Babel の解析の間に矛盾がある
    PrettierではisEqualを呼び出すけど
    Biomeではbabel及びbabel-tsを照合します。

function someFunctionName(
  someLongBreakingParameterName,
  anotherLongParameterName,
) {
-  return isEqual(a?.map(([t, _]) => t?.id), b?.map(([t, _]) => t?.id));
+  return isEqual(
+    a?.map(([t, _]) => t?.id),
+    b?.map(([t, _]) => t?.id),
+  );
}

Biomeの方が見やすいよねってことです。

Linter

ESLint相当のことをしてくれます。
Linterとはコードの指針みたいなものです。
例えばfunctionじゃなくarrow関数使いましょう
みたいな感じです。

例えばapp直下のlayout.tsxにあるMetadataの
import文でtypeを消すと

import type { Metadata } from 'next'import  { Metadata } from 'next'
//All these imports are only used as types.
//タイプをつけろよと怒られます。

設定したLinterで下記のコードでチェックを行うことができます。

pnpm biome lint ./app

✖ All these imports are only used as types.

1 │ import { Metadata } from "next";

先程のMetadataにtypeを入れましょうねと言われています。
Biomeが自動でこのコードを修正しても
問題ないだろうと判断したものを修正するコマンドがあります。

pnpm biome check --apply ./app

上記のコマンドを打つことにより
Layout.tsxのMetadataに自然とtypeが
付与されています。

Linterでチェックを行った際にpage.tsxにも
エラーが出ていると思います。

- <h2 className={`mb-3 text-2xl font-semibold`}>
+ <h2 className={"mb-3 text-2xl font-semibold"}>

クラスネームの中はバッククォートではなく
ダブルクォートを使ってくださいねと言われています。

ただし、checkコマンドでは修正できず
修正したら問題がおきるかもとBiomeが判断しています。

ぶっ壊れてもいいから修正してねのコマンドがあります。

pnpm biome check --apply-unsafe ./app

こちらを打つことによりpage.tsx内の
コードも修正されました。
あんまりやらないでねと公式には書いてあるので
手動で直していきましょう。

linterの無効化

// biome-ignore lint: <explanation>

formaterの時と同じく無効化することができます。
やり方はほとんど一緒です。

lintルールを設定する。

biome.jsonファイルでrulesを指定することにより
細かい設定を行うことができます。

biome.json
{
  "linter": {
    "enabled": true,
    "rules": {
      "style": {
        //ブロックにステートメントが1しかなくても中括弧つける
        "useBlockStatements": "error",
        //T[]の配列型で短縮表現する
        "useShorthandArrayType": "error",
        //定数の大文字はだめ
        "noShoutyConstants": "warn"
      }
    }
  }
}

rulesの下にあるstyleって何を指してるかと言うと

style

lint/nurseryのnurseryを指しています。
jsonファイルはstyleを指定しているので
styleのルールを指定していることになります。

対応言語

Biomeの言語サポートは以下の通りになっています。

対応言語

HTMLやCSSなどはまだ実装されておらず
これから実装されていくかと思われます。

なので現段階では、対応されていない言語などに
関してはPrettierを指定することをおすすめしています。

まとめ

ESLintとPritterを設定するの大変なのですが
Biomeはlinterとformaterを設定が行えて
同時に構文解析ができる利点もあるので
僕自身は今後Biomeでプロジェクトを作成して
いこうと思いました!

Discussion