🍚

RemixでESLint&PrettierからBiomeに乗り換える

2024/09/09に公開

はじめに

Typescript のプロジェクトを始める時、リンターに ESLint、フォーマッタに Prettier を選ぶことが多いのではないでしょうか?

Remix のプロジェクトは、始めた時点で ESLint が含まれています。なので、Prettier をインストールして使う場合が多いかと思います。
今回は ESLint&Prettier が設定されている Remix プロジェクトから Biome に乗り換える方法を書いていきます。

Remix のプロジェクトを作成する

公式に書かれている方法でプロジェクトを作成します。(こちら

npx create-remix@latest

Prettier をインストールする

これまた公式に書かれているとおりの方法でインストールします。(こちら

npm install --save-dev --save-exact prettier

.prettierrcを作成します。

node --eval "fs.writeFileSync('.prettierrc','{}\n')"

.prettierignoreを作成します。

node --eval "fs.writeFileSync('.prettierignore','# Ignore artifacts:\nbuild\ncoverage\n')"

フォーマットされるファイルがあるか、確認してみます。4 ファイルフォーマットできるようです。

npx prettier . --check

Checking formatting...
[warn] .devcontainer/devcontainer.json
[warn] .github/dependabot.yml
[warn] app/entry.client.tsx
[warn] app/entry.server.tsx
[warn] Code style issues found in 4 files. Run Prettier with --write to fix.

ファイルをフォーマットしてみます。先ほどの 4 ファイルがフォーマットされました。

npx prettier . --write

.devcontainer/devcontainer.json 24ms
.github/dependabot.yml 12ms
app/entry.client.tsx 34ms
app/entry.server.tsx 33ms

ここでentry.client.tsxの差分を見てみると、</StrictMode>の後ろに,がついています。

entry.client.tsx
  hydrateRoot(
    document,
    <StrictMode>
      <RemixBrowser />
-    </StrictMode>
+    </StrictMode>,
  );

個人的に嫌なので、設定を変更します。

.prettierrc
{
  "trailingComma": "es5"
}

もう一度npx prettier . --writeを実行すると、app/entry.client.tsxapp/entry.server.tsxの末尾の,が削除されます。

ついでに他の設定も加えておきます。特に意味はないです。

.prettierrc
{
  "trailingComma": "es5",
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false
}

ESLint と Prettier のルールが競合しないようにeslint-config-prettierをインストールします。詳しい設定方法はこちらです。

npm install --save-dev eslint-config-prettier

.eslintrc.cjsを修正します。

.eslintrc.cjs
  // Base config
-  extends: ["eslint:recommended"],
+  extends: ["eslint:recommended", "prettier"],

ここまででようやく Prettier と関連パッケージのインストール、諸々の設定が完了です。

Biome をインストールする

公式に書かれているとおりの方法でインストールします。(こちら

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

Biome の設定ファイルであるbiome.jsonを作成します。

npx @biomejs/biome init

lint と format を確認してみます。すると結構差分が発生します。

  • lint と format 両方実行

    npx @biomejs/biome check
    
  • lint のみ実行

    npx @biomejs/biome lint
    
  • format のみ実行

    npx @biomejs/biome format
    

biome に書き換えてもらいます。

  • lint と format 両方実行

    npx @biomejs/biome check --write
    
  • lint のみ実行

    npx @biomejs/biome lint --write
    
  • format のみ実行

    npx @biomejs/biome format --write
    

lint でエラーが出ます。関数の引数であるresponseStatusCodeに 500 を割り当てようとしていて怒られているようです。
一旦無視して先に進めます。

/workspaces/remix-biome/app/entry.server.tsx:77:11 lint/style/noParameterAssign ━━━━━━━━━━━━━━━━━━━━

  ✖ Reassigning a function parameter is confusing.

    75 │         },
    76 │         onError(error: unknown) {
  > 77 │           responseStatusCode = 500;
       │           ^^^^^^^^^^^^^^^^^^
    78 │           // Log streaming rendering errors from inside the shell.  Don't log
    79 │           // errors encountered during initial shell rendering since they'll

  ℹ The parameter is declared here:

    42 │ function handleBotRequest(
    43 │   request: Request,
  > 44 │   responseStatusCode: number,
       │   ^^^^^^^^^^^^^^^^^^^^^^^^^^
    45 │   responseHeaders: Headers,
    46 │   remixContext: EntryContext

  ℹ Use a local variable instead.


/workspaces/remix-biome/app/entry.server.tsx:127:11 lint/style/noParameterAssign ━━━━━━━━━━━━━━━━━━━

  ✖ Reassigning a function parameter is confusing.

    125 │         },
    126 │         onError(error: unknown) {
  > 127 │           responseStatusCode = 500;
        │           ^^^^^^^^^^^^^^^^^^
    128 │           // Log streaming rendering errors from inside the shell.  Don't log
    129 │           // errors encountered during initial shell rendering since they'll

  ℹ The parameter is declared here:

    92 │ function handleBrowserRequest(
    93 │   request: Request,
  > 94 │   responseStatusCode: number,
       │   ^^^^^^^^^^^^^^^^^^^^^^^^^^
    95 │   responseHeaders: Headers,
    96 │   remixContext: EntryContext

  ℹ Use a local variable instead.


Checked 12 files in 4ms. No fixes applied.
Found 2 errors.
lint ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Some errors were emitted while running checks.

ESLint からの移行

こちらに書かれている手順に従って、移行を進めます。

まずは ESLint から移行します。--include-inspiredをつけることで、eslint からヒントを得てルールを追加します。

npx @biomejs/biome migrate eslint --write --include-inspired

ターミナルの最後の 1 文を読むとマイグレーションは成功しているようですが、一部失敗しているようです。

./.eslintrc.cjs:1:173 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Negated patterns are not supported.

  > 1 │ {"root":true,"parserOptions":{"ecmaVersion":"latest","sourceType":"module","ecmaFeatures":{"jsx":true}},"env":{"browser":true,"commonjs":true,"es6":true},"ignorePatterns":["!**/.server","!**/.client"],"extends":["eslint:recommended","prettier"],"overrides":[{"files":["**/*.{js,jsx,ts,tsx}"],"plugins":["react","jsx-a11y"],"extends":["plugin:react/recommended","plugin:react/jsx-runtime","plugin:react-hooks/recommended","plugin:jsx-a11y/recommended"],"settings":{"react":{"version":"detect"},"formComponents":["Form"],"linkComponents":[{"name":"Link","linkAttribute":"to"},{"name":"NavLink","linkAttribute":"to"}],"import/resolver":{"typescript":{}}}},{"files":["**/*.{ts,tsx}"],"plugins":["@typescript-eslint","import"],"parser":"@typescript-eslint/parser","settings":{"import/internal-regex":"^~/","import/resolver":{"node":{"extensions":[".ts",".tsx"]},"typescript":{"alwaysTryTypes":true}}},"extends":["plugin:@typescript-eslint/recommended","plugin:import/recommended","plugin:import/typescript"]},{"files":[".eslintrc.cjs"],"env":{"node":true}}]}
      │                                                                                                                                                                             ^^^^^^^^^^^^^
    2 │


./.eslintrc.cjs:1:187 deserialize ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Negated patterns are not supported.

  > 1 │ {"root":true,"parserOptions":{"ecmaVersion":"latest","sourceType":"module","ecmaFeatures":{"jsx":true}},"env":{"browser":true,"commonjs":true,"es6":true},"ignorePatterns":["!**/.server","!**/.client"],"extends":["eslint:recommended","prettier"],"overrides":[{"files":["**/*.{js,jsx,ts,tsx}"],"plugins":["react","jsx-a11y"],"extends":["plugin:react/recommended","plugin:react/jsx-runtime","plugin:react-hooks/recommended","plugin:jsx-a11y/recommended"],"settings":{"react":{"version":"detect"},"formComponents":["Form"],"linkComponents":[{"name":"Link","linkAttribute":"to"},{"name":"NavLink","linkAttribute":"to"}],"import/resolver":{"typescript":{}}}},{"files":["**/*.{ts,tsx}"],"plugins":["@typescript-eslint","import"],"parser":"@typescript-eslint/parser","settings":{"import/internal-regex":"^~/","import/resolver":{"node":{"extensions":[".ts",".tsx"]},"typescript":{"alwaysTryTypes":true}}},"extends":["plugin:@typescript-eslint/recommended","plugin:import/recommended","plugin:import/typescript"]},{"files":[".eslintrc.cjs"],"env":{"node":true}}]}
      │                                                                                                                                                                                           ^^^^^^^^^^^^^
    2 │


./.eslintrc.cjs has been successfully migrated.

どうやら、./.eslintrc.cjsignorePatternsで否定!を使っているのが原因のようです。まるまるコメントアウトすると成功します。

.eslintrc.cjs
  // Base config
-  ignorePatterns: ["!**/.server", "!**/.client"],
+  // ignorePatterns: ["!**/.server", "!**/.client"],

コメントアウトした部分はマイグレーションされません。
なので、lint のマイグレーションを行った後、biome.jsonに以下の設定を追加します。

biome.json
{
  "linter": {
    "enabled": true,
+    "ignore": ["!./.server", "!./.client"]
  },
}

Prettier からの移行

続いて、Prettier を移行します。

npx @biomejs/biome migrate prettier --write

ターミナル.prettierignore.prettierrcからの移行が成功しているようです。

.prettierignore has been successfully migrated.
.prettierrc has been successfully migrated.

ただ、実際にフォーマットしてみると、ダブルクォーテーションがシングルクォーテーションになっていたり、最後にセミコロンがつかなかったりとルールの適用がうまくいっていない箇所が見受けられます。なので以下のように変更しました。

biome.json
{
  "javascript": {
-    "semicolons": "asNeeded",
+    "semicolons": "always",
-    "quoteStyle": "single",
+    "quoteStyle": "double",
  },
}

また、Prettier を設定する際に.prettierrcに書いたルールもbiome.jsonに反映されています。

biome.json
{
  "formatter": {
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 100
  },
  "javascript": {
    "formatter": {
      "trailingCommas": "es5"
    }
  },
}

ルールについては、適宜変更して下さい。

不要なファイルを削除する

.eslintrc.cjs.prettierignore.prettierrcを削除します。
また、biome.jsonの overrides に.eslintrc.cjsの記述があるので削除します。

.eslintrc.cjs
{
  "overrides": [
-    { "include": [".eslintrc.cjs"] }
  ]
}

package.jsonに lint のスクリプトがあるので修正します。
ここでは format と lint を同時に行うスクリプトを追加しています。
お好みで format だけ、lint だけをそれぞれ行うスクリプトを書いて下さい。

package.json
{
  "scripts": [
-    "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
-    "lint-fix": "eslint --fix",
+    "biome-check":"npx @biomejs/biome check",
+    "biome-check-write":"npx @biomejs/biome format --write",
  ]
}

パッケージも整理しましょう。以下で不要なパッケージを確認します。

npx depcheck

depcheckをインストールするか聞かれるのでyと答えます。すると使われていないパッケージが表示されます。

Need to install the following packages:
depcheck@1.4.7
Ok to proceed? (y) y

Unused devDependencies
* @typescript-eslint/eslint-plugin
* @typescript-eslint/parser
* eslint
* eslint-config-prettier
* eslint-import-resolver-typescript
* eslint-plugin-import
* eslint-plugin-jsx-a11y
* eslint-plugin-react
* eslint-plugin-react-hooks
* prettier

eslint や prettier のパッケージが不要なので削除します。

npm uninstall @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier eslint-import-resolver-typescript eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks prettier

おまけ

エディタの拡張機能で ESLint と Prettier を入れていたら削除します。
Biome の拡張機能もあるのでインストールしましょう。

https://marketplace.visualstudio.com/items?itemName=biomejs.biome

また、個人的な設定として、以下を設定しています。デフォルトのフォーマッタを Biome にするのと、ファイル保存時にフォーマッタが動くようにしています。

  • Editor: Default Formatter: Biome
  • Format On Save: true

こちらの記事で remix の簡単なアップグレード方法も書いているので、よかったら読んでいって下さい!

https://zenn.dev/kyrice2525/articles/article_tech_018

最後に

Remix のプロジェクトで ESLint&Prettier から Biome に乗り換える方法を書いてきました。
Biome は日本語ドキュメントがあるので移行手順の理解が簡単でした!

こちらに Biome に移行した Remix のリポジトリがありますので、よかったらご参照ください!
devcontainer で立ち上げるとnpm installや拡張機能のインストール、各種設定もしてある状態で立ち上げられます!

https://github.com/yuta-kume/remix-biome

この記事が参考になれば幸いです!🙇

Discussion