📝

Next.js+TS+ESLint+Prettier+husky環境構築【2024 - 全量版】

2024/01/24に公開

前述

以下、Nextプロジェクトの環境構築手順書であり、私の備忘録である。
なるべく順番通りに書いてる。
詳細に書いてるので、コピペ用には向いてない。

[OS]
mac M2

[使用技術]
・Next.js - 14.1.0
・TypeScript - 5
・ESLint - 8.56.0
・Prettier - 3.2.4
・Tailwind - 3.4.1
・antd - 5.13.2
・husky - 8.0.3

[前提]
・node ,yarn インストール済み
└ brewで入れた方が便利

create-next-app

zsh
npx create-next-app@latest [PJ名] --ts

--実行後--

✔ Would you like to use ESLint? … No / Yes // Yes
✔ Would you like to use Tailwind CSS? … No / Yes // Yes(後述で設定するのでどちらでもいい)
✔ Would you like to use `src/` directory? … No / Yes //Yes
✔ Would you like to use App Router? (recommended) … No / Yes // No
✔ Would you like to customize the default import alias (@/*)? … No / Yes // No
✔ What import alias would you like configured? … @/* //上記Yesなら表示。基本的に「@/」がエイリアスにして良いでEnter押すだけ

参考)create-next-appで訊かれていること

Path alias の設定

絶対パスを綺麗にする設定

tsconfig.json
...
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"]
}

■使用例
// before
import { Button } from '../../../components/button';
 
// after
import { Button } from '@/components/button';

Prettier設定

prettierをインストール。
eslint-config-prettierを入れることで、Eslintと被るルールに対して、Prettierを優先することができる。

zsh
yarn add -D prettier eslint-config-prettie

.prettierrcの設定

.prettierrcを新規作成。ここで成形ルールを設定する。
設定は以下の通り。

prettierrc
 {
  "singleQuote": true,  
  "semi": true,       
  "trailingComma": "all", 
  "useTabs": false,  
  "tabWidth": 2,        
  "bracketSpacing": true 
}
  • 上記のそれぞれの意味
    • singleQuote - ダブルクォートの代わりにシングルクォートを使用する
    • semi - ステートメントの最後にセミコロンを追加する。
    • trailingComma - 末尾のカンマの設定。allは可能な限り末尾にカンマを付ける
    • useTabs - スペースではなくタブで行をインデント。falseならスペース
    • tabWidth - インデントのスペースの数を指定
    • bracketSpacing - オブジェクトリテラルの角かっこの内側にスペースを入れる

💡 .prettierignoreで無視するファイルを作成することもできる

その他設定値について

Next.jsにPrettierを導入して設定する

Options · Prettier

.eslintrc.jsonの設定

Eslintを使用しているとPrettierで被るルールがある場合、
Prettier側のルールが優先される設定を施します。

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

package.json設定

package.json
"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "lint:fix": "eslint src/**/*.{ts,tsx} --fix",
	
    // 追加↓//
    "format": "prettier --write .",
    // 追加↑//

    "prepare": "husky install"
  },

保存時に実行されるようにする

.vscode/settings.jsonに以下を追記する(存在しなければ作成)

setting.json
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true. //セーブ時に実行するおっていう設定
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "typescript.preferences.importModuleSpecifier": "non-relative"

}
  • editor.defaultFormatter - フォーマッターの指定
  • editor.formatOnSave - セーブ時にフォーマットを実行するっていう設定
  • editor.codeActionsOnSave - セーブ時のアクション
    └ source.fixAll.eslint - ESLintによるフォーマットを行う(eslint付与でESLint機能拡張の対象ファイルのみ有効)
  • typescript.preferences.importModuleSpecifier - 自動インポートする際に絶対パスで指定する

Eslint 設定

tsconfig.eslint.json設定

tsconfig.eslint.json
{
  "extends": "./tsconfig.json",
  "includes": ["src/**/*.ts", "src/**/*.tsx", ".eslintrc.json"],
  "exclude": ["node_modules", "dist"]
}

package.json設定

package.json
"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
		
    // 追加↓//
    "lint": "next lint",
    "lint:fix": "eslint src/**/*.{ts,tsx} --fix",
    // 追加↑//
  
    "format": "prettier --write .",
    "prepare": "husky install"
  },

[.eslintrc.json設定]env

グローバル変数を使っていいよという設定。
ESlintはブラウザ上で動くものがないためwindowalertをいきなり使われても
定義されてないよ!と怒られるため。

.eslintrc.json
{
  "env": {
    "browser": true,
    "node": true,
    "es6": true
  },
  ...
}
- browser - ブラウザのグローバル変数を理解
- node -node.jsのグローバル変数を理解
- es6 - ECMAScript 6(ES6)の機能をサポート

[.eslintrc.json設定]settings

ESLintが適用するReact関連のルールが、使用しているReactのバージョンに対して適切であるかの確認。detectを設定することで、インストールされている React のバージョンを自動的に検出。

.eslintrc.json
"settings": {
    "react": {
      "version": "detect"
    }
  },

[.eslintrc.json設定]parser

ESLintでTypeScriptのチェックをできるようにするために、@typescript-eslint/parserをインストール

zsh
yarn add -D @typescript-eslint/parser
.eslintrc.json
"parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2020,
    "project": "./tsconfig.json",
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  },
  • ecmaVersion - 使用するECMAScriptのバージョンを指定
  • project - parserが読み込むtsconfig.jsonファイルを指定
  • ecmaFeatures - jsxをtrueにすると、.js、.jsx、.tsxのファイルが解析される
  • .eslintrc ファイルの parserOptions セクション内の sourceType は、ESLintが解析するコードのモジュールの種類を指定

[.eslintrc.json設定]rules

コアルール(eslint標準)

.eslintrc.json
"no-empty-function": "off",
"no-restricted-imports": [
      "error",
      {
        "patterns": [".*"]
      }
    ]

  • no-empty-function - 中身が空などで意味のないコンストラクタを許可
  • no-restricted-imports - 指定したインポート方法をエラーとする。

[.eslintrc.json設定]rules - プラグイン

@typescript-eslint

npm i @typescript-eslint/eslint-plugin
TypeScript 専用のセット

.eslintrc.json
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/naming-convention": [
      "error",
      {
        "selector": "typeAlias",
        "format": ["PascalCase"],
        "custom": {
          "regex": "^.*Props$",
          "match": true
        }
      }
    ],
  • @typescript-eslint/explicit-function-return-type - 関数が戻り値の型を明示的に宣言することを要求(ex.function example(): number {...})
  • @typescript-eslint/no-explicit-any” - any 型の使用を禁止し、より具体的な型の使用を強制
  • @typescript-eslint/no-empty-function - 空の関数(中身のない関数)を作成することを禁止
  • @typescript-eslint/ban-ts-comment - TypeScriptの特殊コメントの使用を制限または禁止
  • @typescript-eslint/no-unused-vars - 宣言されたが使用されていない変数を検出し、警告またはエラーとして報告

eslint-plugin-simple-import-sort

npm install --save-dev eslint-plugin-simple-import-sort

.eslintrc.json
"parserOptions": {
    "sourceType": "module"
  },
・・・
"plugins": ["simple-import-sort", "import"],
  "rules": {
    "simple-import-sort/imports": "error",
    "simple-import-sort/exports": "error",
    "import/first": "error",
    "import/newline-after-import": "error",
    "import/no-duplicates": "error"
  }
・simple-import-sort/imports - 順番通りか確認。
・import/first - すべてのインポートがファイルの先頭にあることを確認
・import/newline-after-import - インポートの後に改行があることを確認
・import/no-duplicates - 同じファイルの import ステートメントをマージ

eslint-plugin-react,eslint-plugin-react-hooks

npm install eslint eslint-plugin-react --save-dev
yarn add eslint-plugin-react-hooks --dev

.eslintrc.json
"react/prop-types": "off",
"react/react-in-jsx-scope": "off",
"react/jsx-sort-props": [
      "error",
      {
        "callbacksLast": true,
        "shorthandFirst": true
      }
    ],
"react-hooks/exhaustive-deps": "off"

react/react-in-jsx-scope - 「React' must be in scope when using JSX」に対応するため
react/jsx-sort-props - ReactのJSX要素のプロパティ(props)の順序を制御
└callbacksLast - コールバック関数(例:イベントハンドラ)を他のプロパティより後に配置
└shorthandFirst - 短縮形(shorthand)のプロパティを通常のプロパティより先に配置

useEffect has amissing dependencyを解決
useEffect内で依存関係を引き起こす変数(概ねuseStateの変数)を使用しており、かつuseEffectの第二引数に空配列を指定した場合、このwarningが発生します。

[ソート系]sort-keys-fix

npm install eslint-plugin-sort-keys-fix --save-dev

npm install eslint-plugin-sort-destructure-keys --save-dev

yarn add -D eslint-plugin-typescript-sort-keys

ソートが正しくない場合エラー

.eslintrc.json
"sort-keys-fix/sort-keys-fix": "error",
"sort-destructure-keys/sort-destructure-keys": "error",
"typescript-sort-keys/interface": "error",
"typescript-sort-keys/string-enum": "error",

sort-keys-fix/sort-keys-fix - オブジェクトリテラル内のキーをアルファベット順にソート
sort-destructure-keys/sort-destructure-keys - 分割代入(destructuring assignment)の際にオブジェクトのキーをアルファベット順にソートするよう要求
typescript-sort-keys/interface - TypeScriptのインターフェース内のキーをアルファベット順にソートすることを要求
typescript-sort-keys/string-enum - TypeScriptの文字列列挙型(string enum)の値をアルファベット順にソートすることを要求

unused-imports

npm install eslint-plugin-unused-imports --save-dev

.eslintrc.json
"unused-imports/no-unused-imports": "error",

• 未使用のimportを検出し、削除してくれる


antd

yarn add antd

yarn add @ant-design/icons


Tailwind CSS

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p (ファイルが存在しない場合)

tailwind.config.js

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
 
    // Or if using `src` directory:
    "./src/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

autoprefixer - CSSにベンダープレフィックスを自動で追加するためのツール.
ベンダープレフィックスとは、特定のブラウザが新しいCSS機能をサポートするために必要な、特別な接頭辞のこと.
postcss - CSSを変換するためのツールキット

postcss.config.js

postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

Installation - Tailwind CSS


Husky, lint-staged

コマンド

yarn add --dev husky lint-staged
yarn husky install

npx husky add .husky/pre-commit "yarn lint-staged --config .lintstagedrc.js”

.husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn lint-staged --config .lintstagedrc.js

package.json設定

package.json
"scripts": {
・・・
    "lint": "next lint",
    "lint:fix": "eslint src/**/*.{ts,tsx} --fix",
    "format": "prettier --write .",
   // 追加↓//
    "prepare": "husky install"
    // 追加↑//
・・・
  },
// 追加↓//
"lint-staged": {
    "*.{ts,tsx}": [
      "yarn lint",
      "yarn format",
      "yarn lint:fix"
    ]
  },
// 追加↑//

ステージングファイルのみにeslintを適用する方法

.lintstagedrc.js
const path = require('path')
 
const buildEslintCommand = (filenames) =>
  `next lint --fix --file ${filenames
    .map((f) => path.relative(process.cwd(), f))
    .join(' --file ')}`
 
module.exports = {
  '*.{js,jsx,ts,tsx}': [buildEslintCommand],
}

.eslintignore ファイルを無視する方法

.lintstagedrc.js
const { ESLint } = require('eslint')

const removeIgnoredFiles = async files => {
  const eslint = new ESLint()
  const isIgnored = await Promise.all(
    files.map(file => {
      return eslint.isPathIgnored(file)
    })
  )
  const filteredFiles = files.filter((_, i) => !isIgnored[i])
  return filteredFiles.join(' ')
}

module.exports = {
  '**/*.{ts,tsx,js,jsx}': async files => {
    const filesToLint = await removeIgnoredFiles(files)
    return [`eslint --max-warnings=0 ${filesToLint}`]
  },
}

特定のファイルをlint-stagedの対象から除外する - Qiita

⚠️依存関係がおかしい?

package.json
"resolutions": {
    "strip-ansi": "6.0.1",
    "string-width": "4.2.3"
  }

実行権限の付与

zsh
$ chmod a+x .husky/pre-push
$ chmod a+x .husky/pre-commit

[補足]punycodeのwrningが出た場合

(node:85791) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)

yarn info yarn description

Discussion