👴

Next.js +TypeScriptで環境構築!ESLint + Prettierの導入から解析自動化までやってみた

2021/03/28に公開

はじめに

こんばんは、バックエンドエンジニア見習い1年目のkuraoです!
「Next.js+TypeScript」でアプリケーション環境を構築する機会があり、その際リンターツール導入の手順などと併せてドキュメントとしてまとめていたので、アウトプットを兼ねてこの場に書き連ねて行こうと思います!

実現したい事

今回のアプリケーション環境構築で目指す所としては以下です。

  • Next.js環境のアプリを構築
  • Next.js環境のアプリにTypeScriptを導入
  • ESLint + Prettier を導入して、コードの一貫性を高め、かつバグ回避を行う
  • husky + lint-staged を導入して、ESlintとPrettierによるコード解析を自動化する

前提

  • nodeがインストールしてある事(ver. 14.15.4)
  • yarnパッケージがインストールしてある事(ver. 1.22.4)
  • GitHubにこのアプリケーションを管理するための新規リポジトリを作成しておいてください
  • アプリケーション環境は以下のようになります
  "dependencies": {
    "next": "10.0.9",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
    "@types/node": "14.14.36",
    "@types/react": "17.0.3",
    "@typescript-eslint/eslint-plugin": "^4.19.0",
    "@typescript-eslint/parser": "^4.19.0",
    "eslint": "^7.22.0",
    "eslint-config-prettier": "8.1.0",
    "eslint-plugin-react": "^7.23.1",
    "husky": "^5.2.0",
    "lint-staged": "^10.5.4",
    "prettier": "2.2.1",
    "typescript": "^4.2.3"
  },

今回導入するツールについて超ざっくりと説明

ESLint

静的解析ツール。設定したルールに従ってバグを発見してくれます。

Prettier

コード整形ツール。設定したオプションに従ってコードを良い感じに整形してくれます。

husky

git commitgit pushをフックして、scriptsを実行してくれるツールです。

lint-staged

stagingされているコードに対して、lintを実行してくれるツールです。

本記事の大まかな流れ

  1. Next.jsアプリの雛形を構築
  2. GitHubの新規リポジトリのmainへ上記アプリを登録
  3. Next.js環境にTypeScriptを導入
  4. ESLint + Prettierを導入
  5. husky + lint-staged導入
  6. 動作確認

1. Next.jsアプリケーションの雛形を作成

まずはじめに、Next.jsアプリケーションの雛形を作成したいのでcreate-next-appを実行します。

ターミナル
npx create-next-app new-app

cd new-app

package.jsonの中身がnpx create-next-appを実行した時点でどうなっているか、試しに見てみます。
nameプロパティやscriptsプロパティは良いとして、despendenciesを見てみると、next, react, react-domというパッケージがインストールされていることが確認できます。
これがNext.jsアプリケーションを動かす上で必要なパッケージという事になります。

package.json
{
  "name": "new-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
+   "next": "10.0.9",
+   "react": "17.0.2",
+   "react-dom": "17.0.2"
  }
}

2. リモートブランチにnew-appを登録

次に、上記で作成したnew-appを、作成済みのリモートのブランチに登録します。

ターミナル
# new-appでgitを初期化
git init

git add .

git commit -m "first commit"

# リモートのベースブランチがmasterになっている場合
git branch -M main 

# このnew-appをリモートリポジトリのnew-appへ登録
git remote add origin git@github.com:username/new-app.git

# new-appをmainブランチへpush
git push -u origin main

3. Next.js環境にTypeScriptを導入

導入の方法はVervelに示されている導入方法に概ね従っている感じです。

tsconfig.jsonを作成

new-appのトップディレクトリ配下にtsconfig.jsonを作成します。
このtsconfig.jsonをトップディレクトリ配下に作成する事で、new-appがTypeScriptプロジェクトであることを宣言した事になります。

The presence of a tsconfig.json file in a directory indicates that the directory is the root of a TypeScript project. The tsconfig.json file specifies the root files and the compiler options required to compile the project. (from TypeScript公式

ターミナル
touch tsconfig.json

# tsconfig.jsonを開き、設定を記述
code ./tsconfig.json

tsconfig.jsonには以下のような設定を記述しました。

各プロパティについて(ざっくり)

各プロパティに関する説明についてはtsconfig.jsonの全オプションを理解する(随時追加中)という記事がわかりやすいので、TypeScript公式と合わせて確認すれば良い感じなのではと思います。

compilerOptions

compilerOptions内に各プロパティと、それに対する値を指定していきます。

target

プロジェクトで使用しているJSバージョンに古いブラウザに対応していない場合があります。その際にはJSのコードを古いブラウザために変換してあげなければなりません。そこでtargetES5と記述する事でES5のコードへ変換してくれるようになります。

lib

targetに指定しているJSバージョンではサポートされていないライブラリの中で、使用したいライブラリをここに設定します。

baseUrl

ファイルを探しに行く際の開始地点を指定します。./とした場合、トップレベルディレクトリを指定した事になり、以下のような階層構造になります。

baseUrl
┣━━ sample.ts
┣━━ src
┃   ┣━━ index.ts
┃   ┗━━ test.ts
┗━━━ tsconfig.json

こうした場合、例えばtest.tsにてindex.tsのコンポーネントをimportしたいとなった際には 以下のようにして絶対パスで参照ができるようになります。

test.ts
import { Home } from "src/index"

console.log(Home)


typeRoots

コンパイルしたいパッケージを指定します。逆に、ここに指定したパッケージ以外のものはコンパイルされません。
以下ではnode_modules/@typesというのを指定しています。これはこの後インストールするTypeScriptパッケージである@types系のパッケージを指します。

allowJS

TypeScriptファイルへの、JavaScriptファイルのimportを許可します。例えば以下のように、.js.tsファイルへimportが可能になります。

src/sample.js
export const Sample = () => {
  console.log("hello")
}
example.ts
import { Sample } from "src/sample.js"

Sample()
// -> hello


skipLibCheck

宣言ファイルの型チェックをスキップします。コンパイル時間の節約と引き換えに、型システムの正確性を捨てるという設定になります。

strict

プログラムの正しさをより強く保証するために、幅広い型チェックの動作を可能にする設定です。
これをtrueに設定すると、上記リンクに飛んだ先のStrict以下にリストアップされているオプション全てを有効にします。

forceConsistentCasingInFileNames

TypeScriptは実行しているファイルシステムの大文字・小文字の区別ルールに従います。このオプションをtrueに設定すると、例えばSample.tsというファイルがあったとしてこれをimportしたいとなった際、sample.tsとして大文字部分を小文字にしてimportするとエラーを出力するようになります。

noEmet

JavaScriptのソースコード、ソースマップ、宣言などのコンパイラ出力ファイルを実行しないようにすることができます。
これにより、Babelなどの別ツールがTypeScriptファイルをJavaScript環境で実行可能なファイルに変換する処理を行う余地ができます。

esModuleInterop

ES6モジュールの仕様では、名前空間のインポート(import * as x)はオブジェクトでなければならないとされています。
このesModuleInteropをtrueに設定することで、TypeScriptがこれをrequire("x")と同じように扱うことによりTypeScriptはimportを関数として扱い、呼び出しを可能にします。

module

プログラムのモジュールシステムを設定します。今回esnextの構文を使用します。

moduleResolution

モジュール解決方法を設定します。nodeもしくはclassicのどちらかを指定します。classicを使うことは最近ではないとのことなのでnodeを指定で問題ないかと思います。

resolveJsonModule

拡張子が.jsonのモジュールのimportを可能にします。これには、import用の静的なJSON形式の型を生成も含まれます。
TypeScriptではデフォルトでJSONファイルの解決をサポートしていないのでtrueに設定します。

isolatedModules

isolatedModulesを設定すると、シングルファイルのトランスパイル処理で正しく解釈できない特定のコードを書いた場合に、TypeScriptがwarningを出力してくれるようになります。
以下で、isolatedModulesを設定した場合にエラーを出力するようになるコードの例を示します。

  • TypeScriptでは型のimportが可能です。(ここではsomeTypeを指します)
  • しかし、isolatedModulesを設定している場合、exportしようとするとエラーを出力します。someTypeに値が設定されていないためです。
import { someType, someFunction } from "someModule";

someFunction();

// someTypeには値が指定されていないのでエラーになる
export { someType, someFunction };


jsx

JavaScriptファイルでJSXコンストラクトがどのように出力されるかを制御します。
これは、.tsxファイルで始まるJSファイルの出力にだけ影響します。
preserveを設定すると、JSXを変更せずに.jsxファイルを出力します。

include

プログラムに含めるファイル名またはパターンの配列を指定します。
これらのファイル名は、tsconfig.jsonファイルを含むディレクトリからの相対パスで解決を行います。
例えば、以下のァイルをincludeに設定したとすると

{
  "include": ["src/**/*", "test/**/*"]
}

以下のチェックの付いたファイルだけを含めることになります。

.
├── scripts                ⨯
│   ├── lint.ts            ⨯
│   ├── update_deps.ts     ⨯
│   └── utils.ts           ⨯
├── src                    ✓
│   ├── client             ✓
│   │    ├── index.ts      ✓
│   │    └── utils.ts      ✓
│   ├── server             ✓
│   │    └── index.ts      ✓
├── tests                  ✓
│   ├── app.test.ts        ✓
│   ├── utils.ts           ✓
│   └── tests.d.ts         ✓
├── package.json
├── tsconfig.json
└── yarn.lock

(from https://www.typescriptlang.org/tsconfig#include)

exclude

excludeでは、上記で挙げたincludeとは逆に、含めないファイルを指定します。

tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "baseUrl": "./",
    "typeRoots":
      "node_modules/@types"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}



このタイミングで一旦、yarn devを実行してローカルサーバーを立ち上げてみましょう。

ターミナル
yarn dev



しかし、以下のようなエラーが表示されて立ち上がらないと思います。
TypeScriptの実行に必要なパッケージ(typescript, @type/react, @type/node )がインストールされていないためにTypeScriptでサーバーを起動できないよ的なことが書いてあります。

ターミナル
It looks like you're trying to use TypeScript but do not have the required package(s) installed.

Please install typescript, @types/react, and @types/node by running:

yarn add --dev typescript @types/react @types/node

If you are not trying to use TypeScript, please remove the tsconfig.json file from your package root (and any TypeScript files in your pages directory).



ので、エラーメッセージに書いてある通りにパッケージをインストールしてあげます。
パッケージはyarn add--devオプションを付与してインストールします。

ターミナル
yarn add --dev typescript @types/react @types/node



インストールしたパッケージがpackage.jsonのdevDependenciesに追加されていることを確認してみます。
tsconfig.jsontypeRootsで指定したnode_modules/@typesはこれらパッケージを参照することになります。

package.json
{
  "name": "new_talktape_client_web",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev -p 4000",
    "build": "next build",
    "start": "next start",
  },
  "dependencies": {
    "next": "10.0.9",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  },
  "devDependencies": {
+   "@types/node": "14.14.36",
+   "@types/react": "17.0.3",
+   "typescript": "^4.2.3"
  }
}

  • 少々逸れますが、dependenciesとdevDependenciesの違いについてざっくり説明しておきます。
    • dependencies
      • 本番環境でも利用するパッケージやその依存関係をここに追加します。依存関係とは、パッケージを動作させるために必要なパッケージです。手動でパッケージ類をインストールする場合は依存関係のあるパッケージも追加しないと動きませんが、npmyarnを利用してインストールする事により、依存関係パッケージもインストールしてくれるため、私たちは依存関係については気にする必要がありません。
    • devDependencies
      • 開発環境テスト環境で利用するパッケージやその依存関係をここに追加します。タイミング的には本番用にビルドする時で、ビルドしたタイミングでここへ追加したパッケージ類は排除されます。今回導入するESLinthuskylint-stagedはこちらに追加する事になります。



インストールが完了しましたら、今度は上手くいくはずですのでもう一度ローカルサーバーを立ててみましょう。

ターミナル
yarn dev



以上でNext.js環境へのTypeScriptの導入は完了ですので、リンターツール導入へ参ります。

4. ESLint + Prettier を導入

ESLint + Prettier 導入で参考にさせて頂いた記事

インストールするパッケージ

4-1. ESLint導入

ESLintに必要なパッケージのインストール

まずはESLintから導入していきます。yarn add--devオプションを付与して必要となるパッケージをインストールしていきましょう。

ターミナル
yarn add --dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react

# ESLintのバージョンを確認
./node_modules/.bin/eslint --version
v7.22.0

TypeScriptにESLintを設定するためのファイルを作成

次に、TypeScriptにESLintを設定するための設定ファイルであるtsconfig.eslint.jsonを作成します。
これは先ほど作成したtsconfig.jsonファイルとは異なり、ESLintを設定するために作成します。

ターミナル
touch tsconfig.eslint.json

# 設定を記述するのでエディタを開きます
code ./tsconfig.eslint.json



ファイルを開いたら、設定事項を記述していきます。

  • extends:./tsconfig.jsonの設定事項を継承
  • includes:指定したファイルにESLintを含める
  • excludes:指定したファイルをESLintに含めない
tsconfig.eslint.json
{
  "extends": "./tsconfig.json",  
  "includes": [
    "src/**/*.ts",
    "src/**/*.tsx",
    ".eslintrc.json",
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

ESLint用の設定ファイルを作成

次にESLint設定そのものを記述していくファイル.eslintrc.jsonをトップレベルディレクトリ配下に作成し、必要な設定事項を記述していきます。

ターミナル
touch .eslintrc.json

# .eslintrc.jsonファイルを開く
code ./.eslintrc.json


各プロパティについて(ざっくり)

各プロパティについての詳細はコチラ:ESLint - Configure ESLint

root

.eslintrc.jsonがプロジェクトのルートに配置されているかをチェックします。指定がない場合は上位階層へ設定ファイルを探しにいきます。

env

環境を指定できます。

  • browser:trueを指定すると、ブラウザのグローバル変数を有効にします。
  • node:trueを指定すると、Node.jsのグローバル変数やスコープを有効にします
  • es6:trueを指定すると、ECMAScript6のモジュールを除いた全ての機能が使用可能になります。

settings

今回ここでreactのバージョンを指定しています。detectとする事で、インストールしている既存のReactのバージョンを参照してくれます
参考:https://github.com/yannickcr/eslint-plugin-react

parserOptions

ESLintでは、サポートするJavaScriptの言語オプションを指定できます。
デフォルトではESLintはECMAScript5の構文のみをサポートします。
parserOptionsでは、この設定を上書きして、ECMAScript6とJSXのサポートを有効にすることができます。
指定できるオプションは以下です。

  • ecmaVersion:デフォルトではECMAScript5、使用したいECMAScriptを指定してください。
  • sourceType:"script"もしくは"module"を指定できます。
  • ecmaFeatures:使用したい構文を追加で指定できます。
    • jsx:trueを指定することでJSXの使用を有効化します

plugins

ESLintはサードパーティ製のプラグインの使用をサポートしています。
インストールしたサードパーティ製のプラグインをESLintで有効にするには、pluginsプロパティに配列で指定します。

extends

ESLintによるオススメのルールを適用することができます。
rulesで1個ずつ指定するのが大変な部分はここに推奨設定を指定しちゃうのが良いかと思います。

rules

rulesでは指定したプロパティに対してo ~ 2の数値を指定して設定を行います。

  • 0:指定したruleをOFFに設定します
  • 1:指定したruleをONに設定します。rule違反を犯した場合はwarningを返します
  • 2:指定したruleをONに設定します。rule違反を犯した場合はerrorを返します
.eslintrc.json
{
  "root": true,
  "env": {
    "browser": true,
    "es6": true,
    "node": true
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "sourceType": "module",
    "ecmaVersion": 2020,
    "ecmaFeatures": {
      "jsx": true
    },
    "project": "./tsconfig.eslint.json"
  },
  "plugins": ["react", "@typescript-eslint"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
    "prettier"
  ],
  "rules": {
    "@typescript-eslint/explicit-function-return-type": 0,
    "@typescript-eslint/no-explicit-any": 0,
    "@typescript-eslint/no-empty-function": 0,
    "react/prop-types": 0,
    "react/react-in-jsx-scope": 0,
    "no-empty-function": 0,
    "@typescript-eslint/ban-ts-comment": 0
  }
}

ESLintの設定ファイルの記述は以上です、次はESLintを実行するためのスクリプトを書いていきます。

ESLint実行用のscriptsを記述

次にpackage.jsonファイルに、ESLint実行用のscriptsを記述していきます。

package.json
{
  ...
  "scripts": {
    ...
    // 型チェック用
    "check-types": "tsc --noEmit",
    // .ts, .tsx ファイルのリンターを実行
    "lint": "eslint \"src/**/*.{ts,tsx}\"",
    // .ts, .tsx のリンターで引っかかったものの中で修正出来るならば修正を行う
    "lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
    // 上記3つのscriptsを一斉実行
    "test-all": "npx yarn-run-all lint check-types lint:fix"
  },
  ...
}



以上でscriptsの記述が済みましたので、早速scriptsを実行してみましょう。
試しにcheck-typesを実行してみます。するとScirptsが走って、正常に通ったかと思います(型エラーになるような記述が今はないはずなので)

ターミナル
yarn check-types

# 自動で実行される部分
yarn run v1.22.4
$ tsc --noEmit
✨  Done in 1.86s.



実行できず、もし以下のようなエラーが出てしまった場合は、エラーメッセージにて示している通りのコマンドを実行してあげることで解決できます。

ターミナル
npm WARN lifecycle The node binary used for scripts is
/var/folders/wz/y5x9_lts6hn_wcsj3_902q8w0000gn/T/yarn--1616765067702-
0.6028041030632163/node but npm is using /usr/local/bin/node itself.
Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed with.	


ターミナル
# 以下のコマンドを実行したら、再度 $ yarn check-types を実行してみてください
yarn config set scripts-prepend-node-path true



上記ではcheck-typesを実行してみましたが、他のscriptsも実行してみて正常に動作するかどうか確認してみてください。
以上でESLintの設定は一通り完了しました。次はコードを整形するPrettierの設定を行っていきます。

4-2. Prettier導入

必要なパッケージのインストール

yarn add--dev--exactを付与して実行します。--exactオプションを付与するとバージョンを固定してインストールします。

ターミナル
yarn add --dev --exact prettier eslint-config-prettier

.eslintrc.jsonファイルにPrettierの設定を加える

Prettierの設定で何故ESLintの設定ファイルを開く必要があるのかと思うかもしれませんが、ESLintによるコード整形とPrettierによるコード整形が競合して上手くいかないことがあったり、チーム開発している場合だと自分のブランチとチームメンバーのブランチとでコード整形に差異が生まれる可能性があるので、ここでPrettierとESLintの競合を無くす設定を行っていいきます。

ターミナル
code ./eslintrc.json



prettierextendsプロパティの最後の行に記述してください。最後の行に記述することで、ESLintによるコード整形を上書きして、Prettierだけでコード整形してくれるようになります。
.eslintrc.jsonファイルが、下に記述したオプションで上にある記述を上書きすることがこれを可能にしてくれています。

.eslintrc.json
{
  "root": true,
  
  // ...
  
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:react/recommended",
+    "prettier"
  ],
  
  // ...
  
}

Prettierの設定ファイルを作成

次にPrettierの設定ファイルである.prettierrcファイルを作成して、Prettierそのものの設定を記述します。

.prettierrc
{
  "singleQuote": true,
  "semi": false,
  "trailingComma": "all"
}

VSCode側でのPrettierの設定

VSCodeを使用している場合にはこの設定を行うと、良い感じにコード整形を行ってくれるようになります。
まずはトップレベルディレクトリ配下に.vscodeディレクトリが存在しない場合は作成し、.vscode配下にsettings.jsonファイルを生成します。

ターミナル
mkdir .vscode
touch .vscode/settings.json



ファイルを生成したら、設定を記述をします。
各プロパティの詳細については公式を参照すると良いかなと思います:https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode

.vscode/settings.json
{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "eslint.format.enable": false,
  "editor.formatOnSave": true,
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "editor.lineNumbers": "on",
  "editor.rulers": [80],
  "editor.wordWrap": "on",
  "eslint.packageManager": "yarn",
  "files.insertFinalNewline": true,
  "files.trimTrailingWhitespace": true,
  "npm.packageManager": "yarn",
  "typescript.enablePromptUseWorkspaceTsdk": true
}

Prettier実行用のscriptsを記述

次に、package.jsonにPrettierでコードを整形するためのscriptsを記述していきます。

package.json
{

  // ...

  "scripts": {
    // ...
    "check-types": "tsc --noEmit",
    "lint": "eslint \"src/**/*.{ts,tsx}\"",
    "lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
    // Prettierによるコード整形
+    "format": "prettier --write .",
    // "format"を追加
+    "test-all": "npx yarn-run-all lint check-types format lint:fix"
  },
 
 // ...
 
}



Prettierの設定は以上です。次は、husktlint-stagedを導入して、commit, pushする直前にリンター、コード整形、型チェックなどを行うよう設定を行っていきます。

5. husky + lint-staged導入

次はhuskylint-stagedなる物を導入していきます。

参考


そもそもhuskyとlint-stagedって何?

冒頭でも軽く説明しましたが、この2つがどんな役割を担ってくれるのかという所を改めて説明しますと

  • huskypre-commitpre-pushなどのフックにpackage.jsonに既に記述してあるscriptsを設定することで、gitのコミットプッシュする直前に登録したscriptsを実行してくれるツールです。
  • lint-staged:stageに上がっているファイルに対してlintをかけてくれるツールです。

といった具合に、この2つを併せて使うことにより、stageに上がっているアプリケーションのコードに対してコミットやプッシュをする前にリンターの実行を差し込むことが出来るようになります。

ワークフローのざっくりとしたイメージとしては、例えば「git commitする前にlintでチェックを入れたい」とするならば、

  1. git addしてgit commitする
  2. git commitした瞬間huskyが走る
  3. lint-stagedが走る
  4. stageに上がっているファイルに対してlint実行用のscriptsが走る

みたいな感じになります。

前置きが長くなりまして申し訳ありません!ここからは導入へと参ります。


husky, lint-stagedパッケージのインストール

huskylint-stagedもまたdevDependenciesに追加したいので、yarn add--devオプションを付与してパッケージをインストールします

ターミナル
yarn add --dev husky lint-staged



一応package.jsonに上記パッケージがインストールされていることを確認します。

pakcage.json
{

  // ...

  "devDependencies": {
    "@types/node": "14.14.36",
    "@types/react": "17.0.3",
    "@typescript-eslint/eslint-plugin": "^4.19.0",
    "@typescript-eslint/parser": "^4.19.0",
    "eslint": "^7.22.0",
    "eslint-config-prettier": "8.1.0",
    "eslint-plugin-react": "^7.23.1",
+   "husky": "^5.2.0",
+   "lint-staged": "^10.5.4",
    "prettier": "2.2.1",
    "typescript": "^4.2.3"
  },
  
  // ...
  
 }

lint-stagedの設定

先にlint-stagedの設定から行っていきますので、package.jsonファイルを開いて、lint-stagedというプロパティを新たに追加します。
ここでは「lint-stagedが実行されたら.ts, .tsxファイルに対して、lint, format, lint:fixのscriptsを実行してくれい」というようなことを指定しています。

package.json
{
  // ...
  "dependencies": {
    // ... 
  }
  "devDependencies": {
    //...
  },
+ "lint-staged": {
+   "*.{ts,tsx}": [
+     "yarn lint",
+     "yarn format",
+     "yarn lint:fix"
+   ]
+ }
 }

huskyの設定

次にhuskyの設定も行なっていきましょう。

huskyの設定ファイル.huskyを生成

先ほどhuskyというパッケージをインストールしましたが、それとは別に以下のようにyarn husky installというコマンドを実行します。
このコマンドを実行することで、husky.shファイルを含む.huskyフォルダをトップレベルディレクトリ配下に作成します。

ターミナル
yarn husky install



次にpackage.jsonのscriptsのpresparehusky installを設定します。
prepareは、yarn installで全てのインストールが終了した後に実行されるscriptsです。

package.json
{

  // ...

  "scripts": {
    "dev": "next dev -p 4000",
    "build": "next build",
    "start": "next start",
+   "prepare": "husky install",

     // ...

  },
  
  // ...
  
}



上記コマンドを実行することで、トップレベルディレクトリ配下に.huskyが作成されていることを確認します。

new-app
┣━━ .husky
┃     ┣━━ _
┃     ┗━━ .gitignore
┃


pre-commitで実行するscriptsを設定

次にgit commit直前に実行させるためのscriptsを設定していきます。

上記で作成した.huskyディレクトリ配下にpre-commitというファイルを作成して、その中に設定を記述します。

ターミナル
touch .husky/pre-commit

code .husky/pre-commit



commit前にはlint-stagedを実行するよう設定します、つまりlint, format, lint:fixを実行するようにします。

.husky/pre-commit
#!/bin/sh
# $0はこのファイルを指します
."$(dirmname "$0")/_/husky.sh"

# yarn lint-stagedを実行
yarn lint-staged



pre-commitの設定は以上です、次にpre-pushの設定です。

pre-pushで実行するscriptsを設定

git push直前の実行させるためのscriptsを設定していきます。

pre-commit同様、.huskyディレクトリ配下にpre-pushファイルを作成し、その中に設定を記述します。

ターミナル
touch .husky/pre-push

code .husky/pre-push



pushの直前にcheck-types、つまり型チェックを実行するよう設定します。

.husky/pre-push
# $0はこのファイルを指します
."$(dirname "$0")/_/husky.sh"

# yarn check-typesを実行
yarn check-types

pre-pushの設定は以上になります。


pre-commit, pre-pushファイルの実行権限を変更

次に、上記で設定したpre-commit, pre-pushファイルに対して実行権限を付与します。
これをしない場合、pre-commitとpre-pushファイルが実行できない可能性があるので注意です。

ターミナル
chmod a+x .husky/pre-commit
chmod a+x .husky/pre-push

以上でhuskylint-stagedの設定は完了です!
では、試しにcommitとpushをしてみて、正常に動作するかを確認してみましょう。


husky + lint-stagedの動作確認

上記で設定したhuskylint-stagedが正常に動作するかをチェックしていきます。

動作確認用のファイルを作成

まずは、srcディレクトリ配下に動作確認用のファイルとして、test.tstest.tsxファイルを用意して、ファイル内に整形されていない汚いコードを記述してください。
加えて、src/index.tsxファイル内に、誤った形で関数の呼び出しを行なってください。これを行う意図としては型チェックを行うためです。

src/test.ts
export const hello = (name: string) => {


console.log(name)
}


export const hello2 = (name: string) => {


  console.log(name)
  }


  export const hello3 = (name: string) => {


    console.log(name)
    }
    export const hello4 = (name: string) => {


      console.log(name)
      }
src/test.tsx
export const hello = (name: string) => {


console.log(name)
}


export const hello2 = (name: string) => {


  console.log(name)
  }


  export const hello3 = (name: string) => {


    console.log(name)
    }
    export const hello4 = (name: string) => {


      console.log(name)
      }
src/index.tsx
import { NextPage } from 'next'
import Head from 'next/head'
import styles from '../styles/Home.module.css'

// 追加
import { hello } from './test-for-tsx'

const Home: NextPage = () => {

// 追加
// hello()の引数にはstring型を指定していますが
// あえてnumber型を入れて型エラーになることをチェックします
+ hello(12)

  return (
    <div className={styles.container}>
      // ...
    </div>
  )
}

export default Home

commitとpushを実行

次に、実際にgit commitgit pushを行なってみましょう。

まずはcommit

pre-commitにはyarn lint-stagedが走るように設定したのでした、つまりlint, format, lint:fixが走るように設定しました。
正常にこれらsctiptsを実行してくれるか確認してみましょう。

ターミナル
# stagingに上げる
git add .

git commit -m "check pre-commit"

# .husky/pre-commitに設定したscriptsが走る/
yarn run v1.22.4
$ /Users/username/new-app/node_modules/.bin/lint-staged
✔ Preparing...
✔ Running tasks...
✔ Applying modifications...
✔ Cleaning up...
✨  Done in 4.18s.
[main .....] check pre-commit

scirptsが実行された結果、Doneと表示されていれば正常に動作したということになりますので、上記で作成したtest.ts, test.tsxファイル(インデントとかバラバラの汚いコード書いたやつ)は、以下のようにコード整形などがされているはずですので、確認してみてください。

test.tsx
export const hello = (name: string) => {
  console.log(name)
}

export const hello2 = (name: string) => {
  console.log(name)
}

export const hello3 = (name: string) => {
  console.log(name)
}
export const hello4 = (name: string) => {
  console.log(name)
}

次にpush

pre-pushでは、yarn check-typesが走るよう設定したのでした、つまり型チェックを行うよう設定しました。
正常に型チェックを行なってくれるか確認してみましょう。

ターミナル
git push

# .husky/pre-pushに設定したscriptsが走る
yarn run v1.22.4
$ tsc --noEmit
src/pages/index.tsx:7:9 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

7   hello(12)
          ~~

Found 1 error.

正常に型チェックを行なってくれました!

ちなみに型チェックに成功する場合は、以下のようにDoneと表示され、GitHubのリモートブランチへpushされることになります。

ターミナル
git push

# .husky/pre-pushに設定したscriptsが走る
yarn run v1.22.4
$ tsc --noEmit
✨  Done in 1.45s.	


おわりに

以上で Next.js + TypeScript + ESLint + Prettier + husky + lint-staged での環境構築は完了です。
フロントエンドの環境構築全般に言えることなんですが、各プロパティが何をやってくれているのかみたいな理解については、曖昧な部分がほとんどなのでもうちょっと頑張ります。。。😇

何かご指摘やご意見などありましたら、ご気軽に頂けると大変助かります!
最後まで読んで頂きありがとうございました🙇‍♂️

Discussion