Next.js における ESLint, Prettier のメモ
// 追記
最後に追記しているが、Prettier は利用しなくなった😣
Biome を検討したけど、npx create-next-app@latest
の選択肢に ESLint しかないから、選択肢に出てくるまでは ESLint にしようと思った。詳しくは調べていない。公式尊重マン!
Next.js, React, Google のガイドラインをできる限り尊重したつもり。
package.json
デフォルト
eslint
eslint-config-next
@eslint/eslintrc
追加
eslint-config-prettier
prettier
npm install eslint-config-prettier --save-dev
npm install prettier --save-dev
"scripts": {
"lint": "next lint",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
eslint.config.mjs
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import { FlatCompat } from '@eslint/eslintrc';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
// Next.jsの推奨設定を継承
...compat.extends('next/core-web-vitals', 'next/typescript'),
{
rules: {
'no-var': 'error', // var の使用を禁止
'prefer-const': 'error', // 再代入がない変数は const を使用
'eqeqeq': ['error', 'always'], // 厳密等価演算子(===)を強制
// インポート順序の制御
'import/order': [
'error',
{
// インポートのグループ化と順序
groups: [
'builtin', // Node.js の組み込みモジュール
'external', // npm パッケージ
['parent', 'sibling'], // 親/兄弟ディレクトリからのインポート
'internal', // 内部モジュール
'index', // インデックスファイル
],
// グループ間に空行を挿入
'newlines-between': 'always',
// アルファベット順でソート
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
// @/ で始まるパスの特別扱い
pathGroups: [
{
pattern: '@/**',
group: 'external',
position: 'after',
},
],
// builtin グループから除外するタイプ
pathGroupsExcludedImportTypes: ['builtin'],
},
],
},
},
{
// shadcn のUIコンポーネントは除外
ignores: ['src/components/ui/**'],
},
];
export default eslintConfig;
.prettierrc.json
{
"semi": true, // 文末にセミコロンを付与
"singleQuote": true, // シングルクォートを使用
"trailingComma": "es5", // 複数行の場合、ES5互換の末尾カンマを使用
"printWidth": 80, // 1行の最大文字数
"bracketSpacing": true, // オブジェクトリテラルの中括弧の周りにスペースを入れる
"arrowParens": "always", // アロー関数の引数は常に括弧で囲む
"jsxSingleQuote": false, // JSXではダブルクォートを使用
"singleAttributePerLine": true, // JSX/HTMLの属性は1行に1つずつ記述
"bracketSameLine": false // JSX/HTMLの閉じ括弧は新しい行に配置
}
.prettierignore
src/components/ui/
Zed
"code_actions_on_format": {
"source.fixAll.eslint": true
},
実行手順
- フォーマットチェック
npm run format:check
- 修正
npm run format
- リントチェック
npm run lint
学んだことメモ
- Prettier の公式によれば、ファジーな設定であることがわかった。
For readability we recommend against using more than 80 characters:
In code styleguides, maximum line length rules are often set to 100 or 120. However, when humans write code, they don’t strive to reach the maximum number of columns on every line. Developers often use whitespace to break up long lines for readability. In practice, the average line length often ends up well below the maximum.
Prettier’s printWidth option does not work the same way. It is not the hard upper allowed line length limit. It is a way to say to Prettier roughly how long you’d like lines to be. Prettier will make both shorter and longer lines, but generally strive to meet the specified printWidth.
Remember, computers are dumb. You need to explicitly tell them what to do, while humans can make their own (implicit) judgements, for example on when to break a line.
In other words, don’t try to use printWidth as if it was ESLint’s max-len – they’re not the same. max-len just says what the maximum allowed line length is, but not what the generally preferred length is – which is what printWidth specifies.
Prettier の設計思想:
厳密なルールではなくガイドライン
人間の判断を尊重
シンプルな設定で一貫性を保つ
import する関数の長さによって、改行される/されない ということがある。うーん、ファジー🤔
と思ったけど、
printWidth
に依存しているらしい。printWidth
の影響が強すぎて、各所で議論になっている理由がなんかわかった…
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import {
handleConvertToDisplayUser,
handleConvertToDisplayUserTweets,
} from '@/actions/users/displayAction'; // 改行される
import {
handleFetchUserTweets,
handleGetUser,
} from '@/actions/users/firestoreAction'; // 改行される
import { DisplayCards } from '@/components/features/display';
import { UserProfile } from '@/components/pages/users/Profile';
import { ContentContainer } from '@/components/ui/container';
import { BASE_DESCRIPTION, SITE_NAME } from '@/constants/metadata'; // 改行されない
import type { FirestoreTweet, FirestoreUser } from '@/types/firestore'; // 改行されない
import { createActionSuccess } from '@/utils/responseUtil';
- 信仰している宗教に合わせて、以下のようなスタイルガイドを import することもできる。vercel 気になるな…
npm install --save-dev @vercel/style-guide
npm install --save-dev eslint-config-google
以下蛇足…🐍
printWidth: 80 の歴史的な背景
以下、ChatGPTの回答だけど面白かったのでメモです🤓
IBM パンチカード (1928年-)
IBM 80欄パンチカード(IBM 80-column punched card)
用途
1-72列: プログラムコード用
73-80列: カード番号(シーケンス番号)用
1970年代まで広く使用
ターミナル表示 (1978年-)
VT100端末により、80×24文字表示が標準化
影響
現代のターミナルエミュレータのデフォルト設定の基準に
macOS Terminal
Windows Terminal
xterm など
主要なコーディング規約
Linux カーネル
/*
* 強く推奨される制限として80列を規定
* https://www.kernel.org/doc/html/latest/process/coding-style.html
*/
Python (PEP 8)
"""
基本方針:
- コード: 79文字
- docstrings/コメント: 72文字
- チーム合意があれば: 最大99文字まで許容
https://peps.python.org/pep-0008/#maximum-line-length
"""
歴史は面白いっすね!
しかし現世では…JSX, TSX におけるネストが深くなったときに悲惨になる。公式に推奨はされていないけど、実際に運用するなら 80
よりも 120
のほうが適切なのではないかなーと思った。
追記
Prettier の挙動がどうしても納得できなかった
そもそも個人開発だし、エディタ側の自動フォーマットを利用したらいいのでは
Rule も自分で設定するのではなくて、ライブラリを尊重すればいいのでは
というわけで以下のようになった…
Prettier は削除した。
package.json からも以下の項目を削除。
"scripts": {
"lint": "next lint",
// "format": "prettier --write .",
// "format:check": "prettier --check ."
},
- 新しく以下のライブラリをインストールした
npm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
- 結果として以下のようになった。
eslint.config.mjs
import { FlatCompat } from "@eslint/eslintrc";
import { dirname } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
{
files: ["**/*.ts", "**/*.tsx"],
...compat.extends(
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-type-checked",
),
},
{
ignores: ["src/components/ui/**"],
},
];
export default eslintConfig;
Zed
{
"code_actions_on_format": {
"source.fixAll.eslint": true,
"source.organizeImports": true
},
"format_on_save": "on"
}
The Vercel Style Guide
を利用しようと思ったけど…
node 20.x までだった
Next.js v15 に対応していなかった