モノレポ(NestJS)でBiomeをセットアップする
ESLint, Prettierをいちいち設定するのが嫌(というか面倒)なので、Biomeを導入してみました。
まだNestJSベースのAPIサーバーしか配置していないのですが、モノレポ前提で設定してみたので、モジュールが増えても対応できると思います。
モノレポでのBiomeのセットアップ
Biomeの公式ドキュメントにモノレポについての記載があり、ちょっと制限はあるようですが、問題があったら変えればいいやの精神でやっていきます。
In order to have the best developer experience despite the current limitation, it’s advised to have a biome.json at the root of the monorepo, and use the overrides configuration to change the behaviour of Biome in certain packages.
まずモノレポにおいてBiomeをどこにインストールするか?という疑問が湧きますが、公式の推奨通り、ルートにインストールします。
npm install --save-dev --save-exact @biomejs/biome
npx @biomejs/biome init
作成されたbiome.jsonに基本的な設定を書きます。一見長く見えますが、ほとんど基本的な設定しかしてません。overrides
の部分については後述します。
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"ignore": [],
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"json": {
"parser": {
"allowComments": true
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
}
},
"javascript": {
"formatter": {
"enabled": true,
"quoteStyle": "single",
"jsxQuoteStyle": "double",
"trailingCommas": "all",
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
}
},
"overrides": [
{
"include": ["api/**"],
"linter": {
"rules": {
"style": {
"useImportType": "off",
"useNodejsImportProtocol": "off"
}
}
}
}
]
}
VSCodeの拡張機能をインストール。extensions.jsonとsettings.jsonも設定しておきます。
一応デフォルトフォーマッタはPrettierを残してますが、Biomeに寄せても良さそうならBiomeにしてしまいたいところ。ここは様子見。
{
"recommendations": ["biomejs.biome", "esbenp.prettier-vscode"]
}
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
}
}
次にモノレポの各モジュールです。私はapiというフォルダを切って、そこにNestJSでAPIサーバーを構築しています。まずはnpm run xxx
スクリプトでBiomeを使うように修正します。
"scripts": {
- "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
- "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
+ "format": "biome format --write",
+ "lint": "biome lint --write",
},
はじめは"format": "biome format --write \"src/**/*.ts\" \"test/**/*.ts\""
のように単純にBiomeを使うように変更したんですが、以下のエラーを解消できませんでした。
> biome format --write "src/**/*.ts" "test/**/*.ts"
src/**/*.ts internalError/io INTERNAL ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ No such file or directory (os error 2)
⚠ This diagnostic was derived from an internal Biome error. Potential bug, please report it if necessary.
test/**/*.ts internalError/io INTERNAL ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ No such file or directory (os error 2)
⚠ This diagnostic was derived from an internal Biome error. Potential bug, please report it if necessary.
Formatted 0 files in 297µs. No fixes applied.
internalError/io ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ No files were processed in the specified paths.
そこで、api/package.jsonは先述の通りただ"format": "biome format --write"
とし、api/biome.jsonを作成して対象のファイルを指定しました。
{
"extends": ["../biome.json"],
"linter": {
"include": ["src/**/*.ts", "apps/**/*.ts", "libs/**/*.ts", "test/**/*.ts"]
},
"formatter": {
"include": ["src/**/*.ts", "test/**/*.ts"]
}
}
extends
はこちらに記載があるとおり、ルートのbiome.jsonを拡張するものです。
ルートのbiome.jsonでinclude
を使っても同様のことができそうです。このあたりは、今後モジュールが増えていったときにどちらがよいか判断したいと思います。
また、これでESLintとPrettierは不要なので外します(もともと入っていたので)。
- ESLint, Prettier関連ライブラリをアンインストール
- eslintrc, prettierrcなど設定ファイルを削除
NestJSでのBiomeのセットアップ
モノレポでのセットアップはここまでなんですが、NestJSでBiomeを使ったときに問題が出たので記載します。
NestJSが依存関係の解決に失敗する
npm run lint
したあとにnpm run start:dev
で起動しようとすると、以下のエラーが発生しました。
[Nest] 83612 - 07/01/2024, 8:01:59 PM LOG [NestFactory] Starting Nest application...
[Nest] 83612 - 07/01/2024, 8:01:59 PM LOG [InstanceLoader] ConfigHostModule dependencies initialized +15ms
[Nest] 83612 - 07/01/2024, 8:01:59 PM ERROR [ExceptionHandler] Nest can't resolve dependencies of the AppController (?). Please make sure that the argument Function at index [0] is available in the AppModule context.
Potential solutions:
- Is AppModule a valid NestJS module?
- If Function is a provider, is it part of the current AppModule?
- If Function is exported from a separate @Module, is that module imported within AppModule?
@Module({
imports: [ /* the Module containing Function */ ]
})
Error: Nest can't resolve dependencies of the AppController (?). Please make sure that the argument Function at index [0] is available in the AppModule context.
Potential solutions:
- Is AppModule a valid NestJS module?
- If Function is a provider, is it part of the current AppModule?
- If Function is exported from a separate @Module, is that module imported within AppModule?
@Module({
imports: [ /* the Module containing Function */ ]
})
怒られているAppControllerは以下。コンストラクタの0番目、つまりAppServiceの依存が解決できないようです。
import { Controller, Get } from '@nestjs/common';
import type { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
もともとここは以下のように、typeではなく値と型の両方をインポートするのが正しいです。
- import type { AppService } from './app.service';
+ import { AppService } from './app.service';
しかし、typeを外すと"All these imports are only used as types."と言われてしまいます。確かに型しか使ってないように見えるんですが、NestJSはコンパイル時に値としても使うため、import type
だと動かないんですよね...(Issue見つけた)
ということで、これを見逃してくれるようにuseImportType
をオフにしました。ルートのbiome.jsonでapiフォルダをincludeして配下のルールを設定します。これも公式で案内されているスタイルです。
...
"overrides": [
{
"include": ["api/**"],
"linter": {
"rules": {
"style": {
"useImportType": "off",
"useNodejsImportProtocol": "off"
}
}
}
}
]
...
Node.jsの標準モジュールをRequireさせてくる
今回はpath
だったんですが、Node.jsの標準モジュールをimportしようとすると
"A Node.js builtin module should be imported with the node: protocol."と怒られました。
基本的にimport/exportで対応したいので、このルールも同様にオフにしました。上記のuseNodejsImportProtocol
です。
また実装を進めていくうちに色々出てきそうな気もしますが、ひとまずかなりサッパリした設定でフォーマッタ・リンタが設定できて嬉しいです。また、依存パッケージが減ったのもとても嬉しいです。
わーい🙌
Discussion