トップページへ

S.A.G.A佐賀!!出身者のプログラミングブログ

フォーマッターとリンターをPrettier, ESLintからBiomeへ移行した

フォーマッターとリンターをPrettier, ESLintからBiomeへ移行した

みなさんはJS/TS環境でフォーマットやリンターは何を使用しているでしょうか。 おそらくフォーマッターはPrettierをリンターはESLintを使用している方が多いのではないでしょうか。 ESLintのv9ではFlat Config(eslint.config.js)がデフォルトとなり、従来の方法は非推奨となっています。 v8も2024年10月5日でサポートが切れるとESLintのブログでお知らせされています。 私も所持しているいくつかあるリポジトリのうち、ひとつだけFlat Configに対応させましたが結構躓いてしまいました。大変でしたのでこの際、前から気になっていたBiomeへ移行してみたいと思います。 また今回はBiomeへの移行がメインのため、各設定値の説明は書きません。

Biomeとは

Biomeはフォーマッターとリンターを兼ね備えたツールです。 もともとはRomeというプロジェクトでJavaScriptの開発に必要なツールチェーンを統一し、開発者体験を改善するプロジェクトでした。 Biomeのブログに経緯が書かれていますので詳しくはこちらを確認してみてください。 では、さっそく移行してみたいと思います。

Biomeのインストール

今回Biomeをインストールするにあたっての環境は以下です。 Node.jsはバージョン14.18以降が必要だそうなので注意してください。

  • Node.js 20.10.0
  • npm 10.2.3

今回エディターはVSCodeを使用します。 BiomeをインストールするディレクトリをVSCodeで開いてターミナルを起動します。 ドキュメントのインストールガイドに沿ってインストールしていきます。

npm install --save-dev --save-exact @biomejs/biome

今回インストールしたBiomeのバージョンは以下です。

  • @biomejs/biome 1.9.0

インストールするとこのままでも使用することができますが、設定をカスタムしたいので設定ファイルを作成します。 下記コマンドで設定ファイルのひな型を作成することができます。

npx @biomejs/biome init

これでルートにbiome.jsonが作成されたかと思います。 現時点でのbiome.jsonの中身は下記のようになっているかと思います。

biome.json

{
	"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
	"vcs": {
		"enabled": false,
		"clientKind": "git",
		"useIgnoreFile": false
	},
	"files": {
		"ignoreUnknown": false,
		"ignore": []
	},
	"formatter": {
		"enabled": true,
		"indentStyle": "tab"
	},
	"organizeImports": {
		"enabled": true
	},
	"linter": {
		"enabled": true,
		"rules": {
			"recommended": true
		}
	},
	"javascript": {
		"formatter": {
			"quoteStyle": "double"
		}
	}
}

Biomeへ移行

今回移行する対象のリポジトリはEJS/Sass/TypeScriptで構成されている静的なWebサイトになります。 まずはPrettierから移行します。Prettierの設定は以下になります。

.prettierrc

{
  "singleQuote": true,
  "plugins": [],
  "overrides": [
    {
      "files": [],
      "options": {}
    }
  ]
}

ほぼデフォルトの設定になっていますね。 下記コマンドでPrettierの設定をBiomeへ移行することができます。

npx @biomejs/biome migrate prettier --write

これでPrettierのBiomeへの移行ができました。 biome.jsonは以下のようになっているかと思います。

biome.json

{
  "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
  "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },
  "files": { "ignoreUnknown": false, "ignore": [] },
  "formatter": {
    "enabled": true,
    "useEditorconfig": true,
    "formatWithErrors": false,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineEnding": "lf",
    "lineWidth": 80,
    "attributePosition": "auto",
    "bracketSpacing": true
  },
  "organizeImports": { "enabled": true },
  "linter": { "enabled": true, "rules": { "recommended": true } },
  "javascript": {
    "formatter": {
      "jsxQuoteStyle": "double",
      "quoteProperties": "asNeeded",
      "trailingCommas": "all",
      "semicolons": "asNeeded",
      "arrowParentheses": "always",
      "bracketSameLine": false,
      "quoteStyle": "single",
      "attributePosition": "auto",
      "bracketSpacing": true
    }
  },
  "overrides": [{ "include": [] }]
}

移行できましたのでこのままでも良いですが、デフォルト値だけど明示されてしまっているところがあるので、Biomeのフォーマッターの設定値を確認し修正した後の設定が以下です。

biome.json

{
  "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
  "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },
  "files": { "ignoreUnknown": false, "ignore": [] },
  "formatter": {
    "enabled": true,
    "useEditorconfig": true,
    "indentStyle": "space"
  },
  "organizeImports": { "enabled": true },
  "linter": { "enabled": true, "rules": { "recommended": true } },
  "javascript": {
    "formatter": {
      "quoteStyle": "single"
    }
  },
  "overrides": [{ "include": [] }]
}

formatter.enabledはあえてtrueと明示しています。 つづいてESLintの設定を移行してみたいと思います。 現在のESLintの設定は以下のようになっています。

.eslintrc.js

module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint'],
  env: {
    browser: true,
    es2022: true,
    node: true,
    jest: true,
  },
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
    project: './tsconfig.eslint.json',
    tsconfigRootDir: __dirname,
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:@typescript-eslint/recommended-requiring-type-checking',
    'prettier',
  ],
  rules: {
    'no-console': 'error',
    'dot-notation': 'error',
    eqeqeq: 'error',
    'no-new-func': 'error',
    semi: ['error', 'always'],
    camelcase: ['error', { properties: 'never' }],
    'no-var': 'error',
    'prefer-const': 'error',
    'prefer-exponentiation-operator': 'warn',
    'no-unused-vars': [
      'error',
      {
        argsIgnorePattern: '^_',
        varsIgnorePattern: '^_',
        caughtErrorsIgnorePattern: '^_',
        destructuredArrayIgnorePattern: '^_',
      },
    ],
  },
  ignorePatterns: ['dist'],
};

Biomeのドキュメントを読むと、ほぼ移植は完了しているようですが、Biomeに実装されていないルールもあるようです。(Xにて観測) ESLintは設定して以来、一切見直していなかったので移行した後、せっかくなのでルールを改めて見直してみたいと思います。 下記コマンドでESLintをBiomeへ移行します。

npx @biomejs/biome migrate eslint --write

コマンドでESLintを移行した時点での設定ファイルは以下のようになっています。

biome.json

{
  "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
  "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },
  "files": { "ignoreUnknown": false, "ignore": [] },
  "formatter": {
    "enabled": true,
    "useEditorconfig": true,
    "indentStyle": "space"
  },
  "organizeImports": { "enabled": true },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": false,
      "complexity": {
        "noExtraBooleanCast": "error",
        "noMultipleSpacesInRegularExpressionLiterals": "error",
        "noUselessCatch": "error",
        "noUselessTypeConstraint": "error",
        "noWith": "error",
        "useLiteralKeys": "error"
      },
      "correctness": {
        "noConstAssign": "error",
        "noConstantCondition": "error",
        "noEmptyCharacterClassInRegex": "error",
        "noEmptyPattern": "error",
        "noGlobalObjectCalls": "error",
        "noInnerDeclarations": "error",
        "noInvalidConstructorSuper": "error",
        "noNewSymbol": "error",
        "noNonoctalDecimalEscape": "error",
        "noPrecisionLoss": "error",
        "noSelfAssign": "error",
        "noSetterReturn": "error",
        "noSwitchDeclarations": "error",
        "noUndeclaredVariables": "error",
        "noUnreachable": "error",
        "noUnreachableSuper": "error",
        "noUnsafeFinally": "error",
        "noUnsafeOptionalChaining": "error",
        "noUnusedLabels": "error",
        "noUnusedVariables": "error",
        "useArrayLiterals": "off",
        "useIsNan": "error",
        "useValidForDirection": "error",
        "useYield": "error"
      },
      "style": {
        "noNamespace": "error",
        "noVar": "error",
        "useAsConstAssertion": "error",
        "useBlockStatements": "off",
        "useConst": "error",
        "useExponentiationOperator": "warn"
      },
      "suspicious": {
        "noAsyncPromiseExecutor": "error",
        "noCatchAssign": "error",
        "noClassAssign": "error",
        "noCompareNegZero": "error",
        "noConsole": "error",
        "noControlCharactersInRegex": "error",
        "noDebugger": "error",
        "noDoubleEquals": "error",
        "noDuplicateCase": "error",
        "noDuplicateClassMembers": "error",
        "noDuplicateObjectKeys": "error",
        "noDuplicateParameters": "error",
        "noEmptyBlockStatements": "error",
        "noExplicitAny": "error",
        "noExtraNonNullAssertion": "error",
        "noFallthroughSwitchClause": "error",
        "noFunctionAssign": "error",
        "noGlobalAssign": "error",
        "noImportAssign": "error",
        "noMisleadingCharacterClass": "error",
        "noMisleadingInstantiator": "error",
        "noPrototypeBuiltins": "error",
        "noRedeclare": "error",
        "noShadowRestrictedNames": "error",
        "noSparseArray": "error",
        "noUnsafeDeclarationMerging": "error",
        "noUnsafeNegation": "error",
        "useAwait": "error",
        "useGetterReturn": "error",
        "useNamespaceKeyword": "error",
        "useValidTypeof": "error"
      }
    },
    "ignore": ["**/dist"]
  },
  "javascript": { "formatter": { "quoteStyle": "single" } },
  "overrides": [{ "include": [] }]
}

コマンドで移行したときはrecommendedがfalseになるようです。 recommendedをtrueに変更し、有効化になるルールで明示されてしまっているルールは設定から省こうと思います。 Biomeの設定値を確認し、ルールの見直しをして修正した結果が以下です。

biome.json

{
  "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
  "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },
  "files": { "ignoreUnknown": false, "ignore": [] },
  "formatter": {
    "enabled": true,
    "useEditorconfig": true,
    "indentStyle": "space"
  },
  "organizeImports": { "enabled": true },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "complexity": {
        "noForEach": "off"
      },
      "correctness": {
        "noNewSymbol": "error",
        "noUndeclaredVariables": "error"
      },
      "style": {
        "noNamespace": "error",
        "useBlockStatements": "off"
      },
      "suspicious": {
        "noConsole": "error",
        "noEmptyBlockStatements": "error",
        "useAwait": "error"
      }
    },
    "ignore": []
  },
  "javascript": { "formatter": { "quoteStyle": "single" } },
  "overrides": [{ "include": [] }]
}

ではここまででPrettierとESLintの移行ができましたのでコマンドでフォーマットしてみたいと思います。 今回のリポジトリはsrc/tsに全て.tsがあるため、ディレクトリの指定はsrc/tsにします。 Biomeのコマンドを確認し、以下のコマンドでリントとフォーマットの両方実行します。

npx @biomejs/biome check --write src/ts/

CSSのリントとフォーマット

今回使用しているBiomeのバージョンからCSSのフォーマッターとリンターが安定版になりデフォルトで有効になっています。 しかし今回移行しているリポジトリはSassを用いており、Sassに関しては現在のところ対応していないのでSassに関してはStyleLintで行ないます。 そのため今回の記事ではCSSに関しては移行しません。 またコマンドで実行するときはsrc/ts/に対して行なっているのでCSSのフォーマッターとリンターは有効になっていても問題ないためbiome.jsonで無効化しないでおこうと思います。

VSCodeの設定

今回はエディターはVSCodeを使用しています。 VSCodeで保存したときにBiomeでフォーマットされるように拡張機能のインストールと設定を行なっていきます。 VSCodeの拡張機能を開いて検索ボックスに「biomejs.biome」と入れて出てきた拡張機能をインストールします。 全ての開発環境をBiomeへ移行する訳ではないので、今回はワークスペースで設定をします。 editor.formatOnSave等はユーザー設定でやっているので、Biomeで追加が必要なもののみワークスペースで指定しています。 ワークスペースのsetting.jsonを開き下記のように設定します。

.vscode/setting.json

{
  "editor.codeActionsOnSave": {
    "quickfix.biome": "explicit",
    "source.organizeImports.biome": "explicit"
  },
  "[typescript]": {
    "editor.defaultFormatter": "biomejs.biome"
  }
}

試しにコードを記述して変更を保存したとき、CLIで動かしたときにフォーマットできれば成功です。 ここまでの変更でBiomeがリントとフォーマットをしてくれるようになりました。

追加:Biomeのアップデート

当記事を書き始めたときのBiomeの最新バージョンは1.9.0でしたが、記事を書いている途中で1.9.1がリリースされていたため、アップデート方法もメモとして残しておきたいと思います。 まずは最新バージョンをインストールします。

npm install --save-dev --save-exact @biomejs/biome@latest

最新バージョンをインストールしたら下記コマンドでマイグレーションします。

npx @biomejs/biome migrate --write

これでbiome.jsonが更新されます。


いかがでしたでしょうか。 ひとつのリポジトリだけESLintのFlat Configへ移行させましたが結構時間がかかってしまったため、思い切ってBiomeを触ってみたのですがセットアップや移行がとても簡単でした。 時間の測定は行なっていないのですが体感でESLintよりも早く動作しました。 新規でやるときはBiomeでいいかもしれません。 ただし全ての言語をサポートしているわけではないので、言語サポートページを確認し問題なければ導入してみて良いかと思います。 また今回初めてBiomeを触ってみたので設定値なども後々変更するかもしれません。 その時は当記事で追記したり修正したりするつもりです。 また、今回試したリポジトリと同じ構成のリポジトリがあったのでサンプルコードとして置いておきます。