Next.jsにおける静的解析ツールとフォーマッター検討 〜Eslint, PrettierとRomeの比較〜

Next.jsにおける静的解析ツールとフォーマッター検討 〜Eslint, PrettierとRomeの比較〜

概要

JavaScriptやTypeScriptを使った開発では各種補助ツールが群雄割拠しています。また複数のツールを導入するとその設定ファイルが多々あり正直ややこしいですよね。

ESLintやPrettierの設定について、どこかで見たBlog記事などそのまま真似て適当に設定してしまってはいないでしょうか?これらのツールがどういう役割でなぜその設定をしているのか改めて記事に起こします。

またRomeというRust製のツールがESLintとPrettierを包含しているらしく、それが現状の開発に耐えられそうか検討しました。

今回はNext.jsのTypescriptで開発するプロジェクトをベースに設定しています。

目次

目的

今回はメジャーな静的解析ツールのESlint とフォーマッターのPrettierの設定を題材として、Blogの記事などを元になんとなく設定してしまいがちなものを、その設定値が何なのか改めて確認します。また新進気鋭のRomeという静的解析とフォーマッターを兼ね備えたツールの検証も行います。

対象読者

フロントエンジニア、何でもやらされるフルスタックエンジニア

PR

UZUMAKIではアジャイル開発で新規事業の開発から、大規模Webアプリケーションのアーキテクチャ更新などの開発をしています。

お問い合わせはUZUMAKIのHPのお問合せフォームから

本文

Next.jsのベースのプロジェクトの作成

今回使用したサンプルプロジェクト

ESLint+Prettierのバージョン

Romeのバージョン

Next.jsの最新版13をTypescriptで作成するものをベースします。

今回使うNodeのバージョン確認

> node -v                                                                                                                                                                                        ~/d/uzumaki
v19.5.0

npx create-next-app コマンドで新規で base_next というプロジェクトを作成します。 —eslint をプションをつけてeslintのデフォルトの設定を追加します。

❯❯❯❯ npx create-next-app@latest base_next --ts --eslint                                                                                                                                             ~/d/uzumaki
✔ Would you like to use `src/` directory with this project? … No / [Yes]
✔ Would you like to use experimental `app/` directory with this project? … [No] / Yes
✔ What import alias would you like configured? … @/*
Creating a new Next.js app in /hogehoge/base_next.

Using npm.

Installing dependencies:
- react
- react-dom
- next
- @next/font
- typescript
- @types/react
- @types/node
- @types/react-dom
- eslint
- eslint-config-next


added 270 packages, and audited 271 packages in 13s

102 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Initializing project with template: default

Initialized a git repository.

Success! Created base_next at /hogehoge/base_next

適当なAboutページを作成

src/pages/about.tsx に下記の内容を追加します。

const About = () => {
  const greeting = "hello";

  return (
    <>
      <h1>About me</h1>
      <h2>{greeting}</h2>
    </>
  )
};
  
export default About;

ESLintの設定をする

next.jsプロジェクト作成時に --eslint をつけたことでeslintの基本の設定は完了しています。

package.jsonを確認すると以下のようになります。

{
  "name": "base_next",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint" <= npm run lint コマンドでeslintが起動します
  },
  "dependencies": {
    "@next/font": "13.1.6",
    "@types/node": "18.11.18",
    "@types/react": "18.0.27",
    "@types/react-dom": "18.0.10",
    "eslint": "8.33.0", <= eslint本体
    "eslint-config-next": "13.1.6", <= next.js向けのeslintの設定
    "next": "13.1.6",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "typescript": "4.9.5"
  }
}

ESLintの設定ファイル.eslintrcの設定は以下のようになっています。

{
  "extends": "next/core-web-vitals"
} 

next/core-web-vitalsで設定されているルールについては公式ドキュメントを確認してください。パッと見た感じ十分な設定になっています。

Next.jsではNext.js向けのルールをESLint用意してくれています。 さり気なくnext/core-web-vitals 1行で書かれていますがESLintの設定はかなりややこしいので、なんとなくいい感じにルールを設定したいと思っている多くの人には福音でしょう。

next/core-web-vitalsが厳しすぎるという場合は基本的なルールだけ適応するには .eslintrcを下記のように書き換えるといいようです。

{
  "extends": "next"
} 

ESLintを動かして、ワーニングを発生させる

試しにESlintを動かしてワーニングが発生するか動作確認をします src/pages/about.tsx に下記のimgタグを追加します

const About = () => {
  const greeting = "hello";

  return (
    <>
      <h1>About me</h1>
      <h2>{greeting}</h2>
      <img src='sample.jpg' /> <= 追加
    </>
  )
};
  
export default About;

ESlintの動作確認をします。下記のようにワーニングが発生するのが確認できます

> npm run lint

./src/pages/about.tsx
8:7  Warning: Using `<img>` could result in slower LCP and higher bandwidth. Use `<Image />` from `next/image` instead to utilize Image Optimization. See: https://nextjs.org/docs/messages/no-img-element  @next/next/no-img-element
8:7  Warning: img elements must have an alt prop, either with meaningful text, or an empty string for decorative images.  jsx-a11y/alt-text

info  - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/basic-features/eslint#disabling-rules

一般的にPrettierはESLintを両方設定する意味は何か?

ESlintとPrettierの両方設定するパターンがあります。どう違うのでしょうか?両方ともコードを整形する機能があります。

Prettierは書式整形(フォーマット)には Prettier を使用し、バグを生み出しやすいコードを見つける静的解析の検出には 静的解析ツールを使用することを奨励しています。

ESLintにも書式を整形に対するルールもあり eslint --fix コマンドで整形することもできます。 ESLintにはPrettierと重複しているルールを抑制するプラグイン eslint-config-prettier も用意されています。このプラグインのReadmeを見るとESLintのどのルールを抑制しているかを確かめることができます。またESLintで自動で修正できるルール Rules Reference は、左記のリンク内のルール一覧にある🔧がアクティブのものがそれに当たります。

ウェブで検索するとPrettierのフォーマットのほうがESLintの修正より高速で、修正できるルール多いというのをみましたが根拠となる情報にはたどりつけませんでした。

Prettierには各種エディタのプラグインが用意されており、ファイルを保存したタイミングでファイルを整形することができます。ファイル保存時にフォーマットさせるということをさせるのであれば、先程のESLintとの速度差は気になるほどではないです。

個人的には設定ファイルの簡潔さや、エディタの設定が楽であることがPrettierの最大の魅力ではないかと思います。

Prettierの設定をする

Prettierのインストール

npm install prettier --save-dev

まず、ESLintでバッティングする機能を回避するライブラリをインストールします。

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

.eslintrc.json ファイルを修正します。

{
  "extends": ["next/core-web-vitals", "prettier"]
}

Prettierを起動するコマンドに package.json のscriptsのブロックにformatを登録する

"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
		"format": "prettier --write --ignore-path .gitignore --ignore-unknown './**/*'"
  },

上記オプションの簡易な説明 --write ファイル変更を許可 --ignore-path .gitignore で記載したファイルはチェック外 --ignore-unknown 対応外のファイルを無視する 引数 './**/*' ですべてのファイルを指定しているのでPrettierに対応するファイルをすべてフォーマット対象にする

Prettierの設定ファイルについて

Prettierをデフォルト設定から変更したい場合はプロジェクトのルート.prettierrcファイルを作成してあげると良さそうです。この設定ファイル名や中の設定の形式はyamlやjson,jsファイルなどいくつかの形式が使えます。

ESLintの設定ファイル.eslintrcなどと合わせて.prettierrcとするのが統一されていて良いかと思います。またファイル形式もyaml形式にするとコメントを追加しやすいのでおすすめです。

細かい設定方法については公式ドキュメントを参照してください

新進気鋭のRomeの設定を試してみる

新進気鋭のフロントエンドのオールインワンツールRomeについてNext.jsに適用できるか検討します。Romeはフォーマッター, 静的解析, コンパイラ、バンドラ、テストをまでカバーする野心的なプロジェクトです。開発言語はRustで作られており高速です。

2023年2月時点では。コンパイラ以降の機能が3月にリリースされる予定です。

image

Romeのインストール

npm install --save-dev rome

package.json ファイルのdevDependenciesの項目に "rome": "^11.0.0" が追加されていることが確認できます。

初期化コマンドを実行して rome.json を作成します。すると下記のようにLinterの設定が追加されます。

npx rome init
{
  "$schema": "./node_modules/rome/configuration_schema.json",
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  }
}

Romeの静的解析ツールとしての機能を検討する

ESlintの代わりにRomeを実行してみましょう。

src/badCode.ts にRomeに指摘されるコードを作成します。

function test(callback) {
  try {
    return callback();
  } catch (e) {
    console.log(e);
    throw e;
  }
  return 20;
}

romeの静的解析機能をcheckのサブコマンドで実行してみます

> npx rome check src 
                                                                                                                                                              ~/d/u/base_next feat/rome
src/badCode.ts:8:5 lint/correctness/noUnreachable ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ This code will never be reached ...

    6 │       throw e;
    7 │     }
  > 8 │     return 20;
      │     ^^^^^^^^^^
    9 │   }

  ℹ ... because either this statement will return from the function ...

    1 │ function test(callback) {
    2 │     try {
  > 3 │       return callback();
      │       ^^^^^^^^^^^^^^^^^^
    4 │     } catch (e) {
    5 │       console.log(e);

  ℹ ... or this statement will throw an exception beforehand

    4 │     } catch (e) {
    5 │       console.log(e);
  > 6 │       throw e;
      │       ^^^^^^^^
    7 │     }
    8 │     return 20;


Checked 6 file(s) in 1037µs
Found 1 error(s)

RomeのFomatterとしての機能を検討する

Prettierの機能をRomeで置き換えは簡単にできそうです。

rome.jsonprettierのデフォルト設定に寄せたフォーマッターの設定をします。

formatter以下を追記してください

{
  "$schema": "./node_modules/rome/configuration_schema.json",
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  },
  "formatter": {
    "enabled": true,
    "formatWithErrors": false,
    "indentStyle": "space",
    "indentSize": 2,
    "lineWidth": 80,
    "ignore": ["node_modules"]
  }
}

ただしこの記事を書いている2022年2月上旬のRomeのバージョン11.0.0では trailingComma の設定をrome.jsonに書くとエラーになるバグがあるため、romeでフォーマットを実行時のオプションに --trailing-comma es5 を付加して代用しています

npx rome format --trailing-comma none src

PrettierとRomeのフォーマッターの簡易な実行速度チェック

Prettierの場合

time npx prettier --write --ignore-unknown src                                                                                                                                  ~/d/u/base_next feat/rome
src/pages/_app.tsx 77ms
src/pages/_document.tsx 3ms
src/pages/about.tsx 2ms
src/pages/api/hello.ts 6ms
src/pages/index.tsx 17ms
src/styles/globals.css 22ms
src/styles/Home.module.css 12ms
npx prettier --write --ignore-unknown src  0.48s user 0.07s system 110% cpu 0.493 total

Romeのformatterの場合

time npx rome format --write --trailing-comma es5 src                                                                                                                            ~/d/u/base_next feat/rome
Formatted 5 file(s) in 937µs
npx rome format --write --trailing-comma es5 src  0.20s user 0.06s system 80% cpu 0.313 total

Romeのほうが起動終了のオーバーヘッドを考慮しても倍以上早いことがわかります。

考察・提案

Romeの静的解析ツールとしての機能を検討

ESLintの項で触れたように、Next.jsではNext.js向けのルールをESLint用意してくれています。このルールをエコシステムを考慮するとRomeを静的解析で使うのは時期尚早です。Romeが流行りデファクトになった暁にはRome向けのルールもNext.jsで作られるのではないかと思われます。

RomeのFomatterとしての機能を検討

Prettierの機能をRomeで置き換えは簡単にできそうです。

ただし速度が早いことを加味してみても、2023年2月現在では取り入れるのは時期尚早だと思われます。ESLintを使用し続けるとしたらESLintのフォーマッターとしての機能をPrettierに任せるようなしくみが必要です。ただしこれはフロントエンドのツールを統合したいRomeの思想と離れているので良い判断だとは思えません。

Romeには下記のようにサブコマンドciをつけると、静的解析とFomatterの機能を同時に実行してくれる便利な機能があり、Github ActionsやCircleCIなどCIツールの設定において便利です。

npx rome ci src

Romeについて今後も開発が続いてデファクトスタンダードになるとフロントエンドのツールが乱立している問題がスッキリしてほしいというのが本音です。

まとめ

ESLintやPrettierの設定について、なぜ機能がかぶっているESLintとPrettierを組み合わせているのか調査し、ゼロベースからNext.jsのプロジェクトを使って構築しました。またRomeという新進気鋭の静的解析兼フォーマッターが現状の開発に耐えられそうか検討しました。

今の所Romeを使うにはNext.jsのESLintのルールが充実しており、これを置き換えるには設定が追いついていません。Romeがこのまま順調にシェアを伸ばすとNext.jsのルールが使えるようになるのではと思います。

PR

XではUZUMAKIの新しい働き方や日常の様子を紹介!ぜひフォローをお願いします!

noteではUZUMAKIのメンバー・クライアントインタビュー、福利厚生を紹介!

UZUMAKIではRailsエンジニアを絶賛募集中です。

↓の記事を読んでご興味を持っていただいた方は、ぜひ応募よろしくお願いします!

是非応募宜しくおねがいします!