8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Biome v2がリリースされ遂に俺たちが待ち望んだimportがやってきた

Last updated at Posted at 2025-07-07

Biome v2がリリースされ、ついに我々が待ち望んだ本当のimportの自動整列が使えるようになりました!!!!やったぜ!
Biome最大の不満の一つがこのimport周りで、お世辞にも快適なものとは言えませんでした。

例えば

  • 最上位はnpmパッケージ群
  • 空行を挟んで自作のモジュール群、/libディレクトリを優先的に上位に表示
  • 空行を挟んで型定義ファイル

という設定はESLintでもよく行われる自動修正です。

自動整形前のimport
import { foo } from './foo'
import { lib1 } from './lib/lib1';
import { lib2 } from './lib/lib2';
import type { type1 } from './types/type1';
import { useState } from 'react';

これがv1までだと

v1の自動整形
import { useState } from 'react';
import { foo } from './foo';
import { lib1 } from './lib/lib1';
import { lib2 } from './lib/lib2';
import type { type1 } from './types/type1';

こんな感じに自動修正するのが限界でした。
つまり空行を任意の場所で自動挿入してグルーピングするということができず、基本的にアルファベット順にソートされてしまうので/libディレクトリ配下を優先的に上位にしたいというソートもできなかったわけです。
これがv2ならこうなります。

v2の自動整形
import { useState } from 'react';

import { lib1 } from './lib/lib1';
import { lib2 } from './lib/lib2';
import { foo } from './foo';

import type { type1 } from './types/type1';

自動でnpmパッケージと自作モジュールの間に空行挿入、fooよりも/libが優先的に上位表示、import typeの型定義ファイルとの間にも空行挿入、これこそ俺たちが求めていたものです!!!!!!!!!

ちなみにv1までは手動で空行を挿入することでグルーピング自体は可能でした。
が、どうしても手動挿入が必要だったのに加えて、まだimportを書いていない状態で関数名等を書いてIDEが自動補完した場合基本的にimportの最後に追加されます。

新たにIDEの自動補完でbarが追加された
import { foo } from './foo';

import type { fooType } from './types/fooType';
import { bar } from './bar'; //新たにIDE補完で自動追加

ここで自動修正が走ると空行によってfooTypeとグルーピングされてしまいます。

v1で自動整形すると空行で自動グルーピングされてしまう
 // 本当はfooとグルーピングされてほしいのに、fooとfooTypeの間に空行があるため
 // barとfooTypeが一つのグループと見なされて修正されてしまう
import { foo } from './foo';

import { bar } from './bar';
import type { fooType } from './types/fooType';

なのでいちいちfoobarの空行を削除しbarfooTypeの間に空行を足さなければならないという極めて不便な仕様でした。

Biome v2のImport設定

BiomeではorganizeImportsという設定項目でimportの設定を行います。

organizeImportsは今まで独立した設定項目でした。

biome.jsonc(v1までのorganizeImportsの書き方)
{
  "organizeImports": {
    "enabled": true
  }
}

これが今後はassistブロックの配下に変更されます。

biome.jsonc(v2の書き方)
{
  "assist": {
    "actions": {
      "source": {
        "organizeImports": {
          "level": "on",
          "options": {
            // ここに設定を書いていく
          }
        }
      }
    }
  }
}

options内に色々設定を書いていくことになります。

ちなみに単純に"organizeImports": "on"と書くこともできますが、以下の順でソートする挙動になります。

  1. npmパッケージ群
  2. ユーザー定義モジュール

type-only importが区別なく名前順で並び替えられるのはv1と同じ挙動ですが、なんと空行によるグルーピングが無くなってv1以下の性能になっています。

なおここでは詳細に触れませんが、assistにはorganizeImports以外にも項目があります。

キー 内容 公式マニュアル
organizeImports importの自動ソート(今回の内容) organizeImports
useSortedKeys オブジェクト/jsonのキーを自動ソート useSortedKeys
useSortedAttributes propsを自動ソート useSortedAttributes
useSortedProperties CSSのプロパティを自動ソート useSortedProperties

当然organizeImportsと併用可能です。

Biomeの設定ファイルはjsonjsoncが使用できますが、ここでは統一してjsoncを採用しています。
一部jsonではエラーになる記述(コメント等)があるのでコピペの際は注意してください。

最終的な完成形

最終的に以下のような形で自動整形できるようにします。

最終的にこうなってほしい
// React関連パッケージを優先しnpmパッケージ群をグルーピング、その後空行
import { useState } from 'react';
import axios from 'axios';

// /libディレクトリのモジュールを優先しユーザー定義モジュールをグルーピング、その後空行
import { lib1 } from './lib/lib1';
import { constant } from './constant';
import { utils } from './utils';

// type-only importはまとめてグルーピング
import type { propsType } from '../types/props';

biome.jsoncの設定

上記を満たすbiome.jsoncは以下の通りです。

biome.jsonc
{
  "assist": {
    "actions": {
      "source": {
        "organizeImports": {
          "level": "on",
          "options": {
            "groups": [
              // ===== react packages =====
              // react関連パッケージを優先してimport
              { "type": false, "source": ["react*", "react/**"] },

              // ===== npm packages =====
              // react*,react/**以外のnpmパッケージを全てキャッチ
              { "type": false, "source": ":PACKAGE:" },
              ":BLANK_LINE:",

              // ===== lib modules =====
              // ./libディレクトリを優先してimport
              { "type": false, "source": "./lib/**" },

              // ===== user modules =====
              // ./libディレクトリ以外のユーザー定義モジュールをまとめてキャッチ
              { "type": false, "source": ":PATH:" },
              ":BLANK_LINE:",

              // ===== other imports =====
              // 上記に引っかからない全てのimportをここでキャッチ
              { "type": false },
              ":BLANK_LINE:",

              // ===== import types =====
              // 最後にtype-only importをキャッチ
              { "type": true }
            ]
          }
        }
      }
    }
  }
}

VSCodeで自動修正させる

.vscode/settings.json
{
  "editor.codeActionsOnSave": {
    "source.organizeImports.biome": "explicit",
    "source.fixAll.biome": "explicit"
  }
}

source.organizeImports.biomeexplicitにすることで、BiomeのorganizeImportsが走るようになります。

解説

groups配列の中にマッチ条件を記述します。

マッチ条件には以下の種類があります。

マッチ条件 意味 種類
定義済みキー :PACKAGE::PATH:等、Biomeが定義済みのキー 文字列 or 配列
globパターン ***!(否定)\(エスケープ用) 文字列 or 配列
オブジェクトマッチャー { "type": boolean, "source": string|string[] } オブジェクト

例えば"react"のような指定は完全一致のglobパターンとなります。

定義済みのキー

途中に:PACKAGE::PATH:という特殊な文字列が出てきますが、これはBiomeが元々用意してくれている定義済みのキーです。

キー 意味
:ALIAS: #@/~$%で始まるエイリアス
:BUN: Bunの組み込みモジュール
:NODE: Node.jsの組み込みモジュール
:PACKAGE: npmパッケージ
:PACKAGE_WITH_PROTOCOL: npm:jsr:のようなプロトコル付きパッケージ
:PATH: 相対・絶対パス
:URL: URLでimportするパッケージ
:BLANK_LINE: 空行

これらの定義済みのキーを使えばほとんどのimportを自動整理することが可能です。

例えばnpmパッケージの後空行、その後自作モジュールをimportしたい場合は以下のように指定すればOK。

biome.jsonc
{
  "options": {
    "groups": [
      // まずは:PACKAGE:を指定してnpmパッケージをimport
      ":PACKAGE:",
      // :BLANK_LINE:を指定してnpmパッケージの後に必ず空行を挿入する
      ":BLANK_LINE:",

      // 次に:PATH:を指定して自作モジュール群をimport
      ":PATH:",
    ]
  }
}

これだけでかなり見やすいimportの自動整理が実現します(ただし上記の場合、import type(type-only import)は区別されません)。

:ALIAS:は非常に便利で、よくあるコンポーネントをまとめた@/componentsやモノレポでsharedディレクトリを切った時の@/sharedなどに使えます。
が、:ALIAS:がカバーするのはあくまで以下のようなpathの場合です。

  • #
  • @/
  • ~
  • $
  • %

見て分かる通り@/だけアットマーク+スラッシュで一つの塊で、@はヒットしません。

tsconfig.json
{
  "paths": {
    "@/shared/*": ["../shared/*"]
  }
}

こんな感じでエイリアスを@/としていれば:ALIAS:が使用可能ですが、以下のように/をつけ忘れると機能しません。

tsconfig.json
{
  "paths": {
    "@shared/*": ["../shared/*"]
  }
}

@単体だとnpmパッケージの@scopeなのか判別ができないからこういう形になっているんだと思います。
僕は最初この仕様に気づかず30分ぐらい格闘しました。

globパターン

残念ながら正規表現は使用できませんが、代わりにglobパターンである程度柔軟にimportの並び替えが可能です。
先述した通り"react""hono"のように単純にパッケージ名だけを指定して完全一致のglobパターンを使う機会は多いと思います。

また、reactから始まるパッケージを一括指定したい場合は"react*"のように指定します。(reactreact-domreact-routerなど)
ディレクトリ階層を超えて指定したい場合は"react/**"です(react/serverreact/jsx-runtimeなど)。

否定!は例えばReact Nativeプロジェクト等頻繁にreactというワードが出てくるプロジェクトで便利です。

最後に残念ながら今のところ使用できませんがBiomeで予約されているため使用できないメタ文字として?[]{}があります。
あまりないとは思いますが、これらのメタ文字がファイル名等に含まれている場合バックスラッシュ\を使ってエスケープが必要です。

これらのglobパターンは配列マッチャーを使って複数指定可能です。

例えばreactで始まるパッケージを優先的に上位表示したいが、react-nativeだけはその後にグループ化したい場合は以下のように記述します。

biome.jsonc
{
  "options": {
    "groups": [
      // まずは配列マッチャーでreact、もしくはreact/かつreact-native以外のパッケージをimport
      ["react*", "react/**", "!react-native*"],

      // 次にreact-nativeで始まるパッケージをimport
      ["react-native*", "react-native/**"]
    ]
  }
}

オブジェクトマッチャー

オブジェクトマッチャーは先述した定義済みキーやglobパターンをimportimport typetype-only import)も組み合わせてオブジェクト形式で指定する方法です。
キーはtypesourceの2つです。

typesourceも省略可能です、つまり先程の定義済みキーやglobパターンを使用した指定は「typeを省略した同じ意味の文字列マッチャー」です。
ちなみにsourceだけの場合は先程のように定義済みキーやglobパターンを文字列マッチャーとして指定可能ですが、typeだけの場合は必ずオブジェクト形式である必要があります。

biome.jsonc
{
  "options": {
    "groups": [
      // typeとsourceを両方指定する場合
      { "type": true, "source": "react" },

      // sourceだけを指定する場合
      { "source": ":PACKAGE:" },
      // これでもOK
      ":PACKAGE:",

      // typeだけを指定する場合
      { "type": false },
      // これはダメ
      false
    ]
  }
}

type

importが通常のimportか、import typeかを指定します。

現状TypeScriptのimportは以下のimportもしくはtype-only importのいずれかです。

// よく使うimport
import { OpenAPIHono } from '@hono/zod-openapi';

// 型ファイルをimportで書く場合
import { type Handler } from 'hono';

// 型ファイルをimport type(type-only import)で書く場合
import type { Handler } from 'hono';

僕は型のimportはimport typeで書いた方が視認性が高いので全てimport typeで統一しています。

biome.jsonc
{
  "options": {
    "groups": [
      // 通常のimportを指定したい場合
      { "type": false },

      // import type(type-only import)を指定したい場合
      { "type": true }
    ]
  }
}

typeを省略した場合import typeimportの順番になり、例えばhonoのように関数と型定義を両方提供しているライブラリを使用して以下のように指定します。

biome.jsonc
{
  "options": {
    "groups": [
      ["hono", "hono/*", "@hono/**"],
      ":BLANK_LINE:",

      ":PACKAGE:"
    ]
  }
}

この場合、以下のように並び替えられます。

// import typeが先にimportされる
import type { Handler } from 'hono';
import { Hono } from 'hono';

// その他のnpmパッケージは:PACKAGE:に分類
import { z } from 'zod';

先に提示した完成形のように、「import typeはとにかく一番最後にまとめてグルーピングすればOK」の場合はoptionsの一番最後に以下のように書いておけば良いです。

biome.jsonc
{
  "options": {
    "groups": [
      // 色々な設定を書いた一番最後にimport typesをまとめてグルーピング

      // ===== import types =====
      { "type": true }
    ]
  }
}

もちろん"type": trueを指定した場合もsourceを組み合わせて自由に並び替え可能です。

例えばtypes/footypes/barがあった場合通常アルファベット順でbarが先に並びますが以下のように指定すればfooが先に並びます。

biome.jsonc
{
  "options": {
    "groups": [
      // この場合barが最上位、ついでnpmパッケージ、自作型定義と続く
      { "type": true, "source": "./types/bar" },
      { "type": true }
    ]
  }
}

単に"type": trueとだけ書く場合全てのimport typeがここに吸収されるので、「npmパッケージは最上位、その次にbar、次にそれ以外」の場合だともう少し記述が必要になります。

biome.jsonc
{
  "options": {
    "groups": [
      // この場合はnpmパッケージが最上位、次いでbar、その他の自作型定義の順
      // npmパッケージとbarの間に空行を挿入したい場合は先程と同じく:BLANK_LINE:を指定する
      { "type": true, "source": ":PACKAGE:" },
      { "type": true, "source": "./types/bar" },
      { "type": true }
    ]
  }
}

source

sourceは具体的にどのパターンを指すかを定義します。
先述した定義済みキー、globパターン、配列マッチャーがそのまま利用できます。

biome.jsonc
{
  "options": {
    "groups": [
      // 配列マッチャーをsourceに指定する例
      // この場合は「react-nativeから始まるものを除外した以外のreactから始まる」という意味
      { "type": false, "source": ["react*", "!react-native*"] }
    ]
  }
}

typeを省略した場合、オブジェクトマッチャーではなく文字列マッチャーとして指定しても良いです。

biome.jsonc
{
  "options": {
    "groups": [
      // この2つは同じ意味
      { "source": ":PACKAGE:" },
      ":PACKAGE:"
    ]
  }
}

とりあえずのテンプレート

ここまでつらつら書いてきましたが、多くの方にとって

  1. type-only importを除いたnpmパッケージ+空行
  2. type-only importを除いたユーザー定義モジュール+空行
  3. type-only import

で並び替えされれば十分だと思います。

その場合は以下のように指定しておいてください。

biome.jsonc
{
  "assist": {
    "actions": {
      "source": {
        "organizeImports": {
          "level": "on",
          "options": {
            "groups": [
              // ===== npm packages =====
              { "type": false, "source": ":PACKAGE:" },
              ":BLANK_LINE:",

              // ===== user modules =====
              { "type": false, "source": ":PATH:" },
              ":BLANK_LINE:",

              // ===== import types =====
              { "type": true }
            ]
          }
        }
      }
    }
  }
}

ここに「Nextjsプロジェクトなので関連パッケージが上位だと嬉しいな」とか、「モノレポプロジェクトでsharedディレクトリに定義した型定義は分かりやすくグルーピングしたいな」みたいな要望が出てきた時に追加すればいいかなあと思います。

BiomeがESLintを本当に置き換える日も近いかも

今回のimportの改善で個人的にはBiomeの最大の不満がようやく解消された感があります。

とはいえまだTailwindのclass自動ソートはβ版、細かい調整はエコシステムの充実度から言ってもまだまだESLintに軍配が上がります。
RubyにおけるRails的な意味合いで「Biomeさえ入れとけば後はそのルールに従ってれば良い」というならともかく、ここからはBiomeそのものももちろんコミュニティがいかに成長できるかが鍵かなあという感想です。

僕のような小〜中規模プロジェクトを作る個人開発者は十分Biomeを採用しても良いかなと思っています。
現に現在新規プロジェクトでv2を採用していますが全く不満なく開発を進められています。
逆に大規模プロジェクト、特にプラグインを入れまくってたり独自ルールを書きまくってるプロジェクトはまだまだ厳しいでしょう。

ですがフラットコンフィグへの移行やそもそもESLint自体の設定が複雑すぎる点などで脱ESLintが求められているのも事実で、プラグインがESLintのアップデートに追いつかずその影響でESLint自体をアップデートできないなんて声もちょくちょく目にします。

ここでは触れていませんが特にv2になりモノレポサポート、プラグインシステム導入などその下地は出来つつあるので今後もBiomeの動きはぜひ注目しておきたいところです。

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?