【Next.js】環境構築を解説してみた【TS+ESlint+ Prettier+Husky】
こんにちは、あきのと申します。
普段は、業務でもプライベートでもフロント・バックエンド・インフラ等を幅広く触っています。最近の悩みはデザインセンスがないことです。(デザイナーさん仲良くしてください!)
はじめに
最近、個人開発やコミュニティのWebフロントでNext.jsプロジェクトを何度も立ち上げることがあったので、解説も兼ねて記事として残しておこうと思います。
今回のコードはGithubで公開しています。
解説すること
- 各パッケージの設定方法
-
Next.js
の環境構築手順と解説 -
ESlint
やPrettier
について -
Husky
やlint-staged
を使ったcommit、push時にCI実行
解説しないこと
- Next.jsの機能
バージョン等
- yarn:1.22.11
- Next.js:12.1.6
- React:18.1.0
- TypeScript:^4.6.4
- ESLint:^8.14.0
- Prettier:^2.6.2
- Husky:^7.0.4
- lint-staged:^12.4.1
では、次章から解説していきます!
Next.jsプロジェクトを作成
下記のコマンドでNext.jsプロジェクトを作成することができます。プロジェクト名は任意ですので、ご自身で設定してください。
$ npx create-next-app {プロジェクト名}
僕はプロジェクト名はnext-sample
にしたいと思います。
下記のように出力されたら成功です!
$ npx create-next-app next-sample
# 略
Success! Created next-sample at /Users/user-name/hobby/next-sample
Inside that directory, you can run several commands:
yarn dev
Starts the development server.
yarn build
Builds the app for production.
yarn start
Runs the built app in production mode.
We suggest that you begin by typing:
cd next-sample
yarn dev
A new version of `create-next-app` is available!
You can update by running: yarn global add create-next-app
次に作成したプロジェクトに移動し、ローカルで立ち上げてみます。
$ cd next-sample
$ yarn dev
yarn run v1.22.11
$ next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
wait - compiling...
event - compiled client and server successfully in 379 ms (125 modules)
正常に立ち上がったら、http://localhost:3000 にアクセスしてみてください。下記のような画面が表示されていれば成功です。
次にsrc
ディレクトリを作成して、pages
ディレクトリとstyles
ディレクトリをsrc
ディレクトリの配下に移動させます。
$ mkdir src && mv pages src && mv styles src
TypeScriptの導入
次にTypeScriptの導入をしていきます。
TypeScript関連パッケージのインストール
下記のコマンドを実行することでTypeScript関連パッケージのインストールをすることができます。
$ yarn add -D typescript @types/react @types/react-dom @types/node
tsconfigの追加と記述
次に、設定ファイルであるtsconfigを追加していきます。
$ touch tsconfig.json
次に、設定ファイルの中身を書いていきます。tsconfig.json
のオプションは色々あるので、公式ドキュメントを参照しながら書いていくことをお勧めします。(ちなみに、公式のドキュメントがとてもみやすいです。)
{
"compilerOptions": {
"baseUrl": "./",
"target": "es5",
"module": "esnext",
"jsx": "preserve",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"noEmit": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true
},
"exclude": ["node_modules", "deployments", ".next", "out"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}
次章でオプションごとに解説していきます。必要のない方は読み飛ばしてください。
tsconfigのオプション解説
compilerOptions
"compilerOptions": {}
この中に設定のオプションを書いていきます。
baseUrl
"baseUrl": "./",
baseディレクトリを指定することで、ここに指定したディレクトリを起点として、モジュールをインポートすることができます。
例えば、下記のようなディレクトリ構造だったとします。
baseUrl
├── ex.ts
├── hello
│ └── world.ts
└── tsconfig.json
こうすることで、ex.ts
ファイルからhello
ディレクトリ内のモジュールをインポートするには下記のように書くことができます。
import { helloWorld } from "hello/world";
これはbaseUrl
が起点となって、絶対パス(のように)書くことができるからです。
target
"target": "es5",
どのバージョンのJSで出力するか指定しています。
今回はes5で出力しています。
module
出力するJSのモジュールを指定します。(CommonJS
とか)
"module": "esnext",
jsx
"jsx": "preserve",
tsx
ファイルをコンパイルする形式を指定しています。preserve
は下記のようにコンパイルされます。
コンパイル前
export const helloWorld = () => <h1>Hello world</h1>;
コンパイル後
export const helloWorld = () => <h1>Hello world</h1>;
つまりは変わらないってことです。デフォルトだと下記のようにコンパイルされます。
export const helloWorld = () => React.createElement("h1", null, "Hello world");
strict
"strict": true,
下記のオプションが全てtrue
になります。
// "use strict";を全てのファイルの先頭行につける
--alwaysStrict
// 暗黙的なanyにエラーを出します。
--noImplicitAny
// 暗黙的なthisにエラーを出します。
--noImplicitThis
// nullの可能性のある(Nullableな)値の呼び出しにエラーを出す
--strictNullChecks
// bind, call, applyに厳密な型チェックが行われる
--strictBindCallApply
// 関数代入時の引数の型チェックで、Contravariantly(共変性と反変性)にチェックが入る
--strictFunctionTypes
// クラス定義するとき、インスタンス変数の初期化が宣言、コンストラクタの両方で行われていない場合にエラーを出す
--strictPropertyInitialization
esModuleInterop
"esModuleInterop": true,
例えば、CommonJS形式でexportされたものをrequire
ではなく、import~from
で読み込むことができる。(詳しい解説は割愛させていただきます。)
参考
skipLibCheck
"skipLibCheck": true,
*.d.ts
ファイルの型チェックをスキップします。node_modules
配下(つまり、パッケージとかライブラリとか)の型チェックをスキップするのによく使われます。主な理由は下記。
- コンパイルの時間を削減。
- 複数のパッケージがバージョンの違う同一ライブラリの型定義ファイルを読み込んでしまってエラーが出る。
forceConsistentCasingInFileNames
"forceConsistentCasingInFileNames": true,
importした時にファイルの大文字、小文字を区別するかどうか。
lib
"lib": ["dom","dom.iterable","esnext"],
コンパイル時に使用する組み込みライブラリを指定。target
で指定しているバージョンの組み込みライブラリは暗黙的に指定されます。
allowJs
"allowJs": true,
js
やjsx
ファイルをコンパイル対象に入れるかどうかを指定する。この場合、型チェックは行われないが、記法を指定のバージョンに変換するという作業が行われる。
noEmit
"noEmit": true
コンパイル結果を出力しない。型チェック機能をだけを使いたい時にtrue
にする。実際のコンパイルをBabel
などに任せているときに使う。(Next.js
は内部的にBabel
が使われています。)
Next.js includes the next/babel preset to your app,
moduleResolution
"moduleResolution": "node",
モジュール解決の方法を指定。基本的にはnode
でOK。
resolveJsonModule
"resolveJsonModule": true,
jsonファイルを使用するとき、interfaceを用意しなくて良くなる。
isolatedModules
"isolatedModules": true
全てのファイルを単一のモジュールとしてコンパイルする。
include
コンパイルする対象のファイルを指定する。
exclude
コンパイルしない対象のファイルを指定する。
簡単にでしたが、tsconfig
に関しては以上です。繰り返しになりますが、詳しい解説は公式ドキュメントをご参照ください。
ESlintの導入
次に静的解析ツールであるESlintを導入していきます。
ESlint関連パッケージのインストール
下記のコマンドでESlint関連のパッケージをインストールします。
$ yarn add --dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-plugin-react-hooks
TypeScript用の設定ファイルの追加
今回はTypeScriptとESlintを併用するため、tsconfig.eslint.json
を作成して、設定を記述します。ここで lintを適用する範囲を指定します。
下記のコマンドでtsconfig.eslint.json
ファイルを作成します。
$ touch tsconfig.eslint.json
下記のように指定しました。
{
"extends": "./tsconfig.json",
"includes": ["src/**/*.ts", "src/**/*.tsx", ".eslintrc.json"],
"exclude": ["node_modules", "dist"]
}
ESlint用の設定ファイルの追加
次にESlintの設定ファイルを作成し、設定を記述していきます。
$ touch .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のプロパティについて簡単に解説していきます。(必要のない方は飛ばしてください。)
root
"root": true,
ルートに設定ファイルがある場合は、true
にしておきます。
env
"env": {
// ブラウザのグローバル変数を有効にする。
"browser": true,
// Node.jsのグローバル変数やスコープを有効にします
"node": true
// ECMAScript6のモジュールを除いた全ての機能が使用可能になります。
"es6": true,
},
環境変数を設定します。(それぞれの解説は上記にコメントとして書いておきました。)
settings
"settings": {
"react": {
"version": "detect"
}
},
ここではReactのバージョンを指定しています。detect
にすることで、インストールしているバージョンを参照してくれます。
plugins
"plugins": [
"react",
"@typescript-eslint"
],
サードパーティ用のプラグインを追加しています。
extends
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"prettier"
]
追加でまとめられたルールを設定できます。prettier
については後で解説します。
rules
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"react/prop-types": "off",
"react/react-in-jsx-scope": "off",
"@typescript-eslint/ban-ts-comment": "off"
}
各プロパティの設定を変更することができます。例外的に適応したくないルールがある場合は、ここの設定を変えるとプロジェクトごとにカスタマイズすることができます。
実行スクリプトを記述
次にESlintのコマンドを実行するスクリプトをpackage.jsonに定義していきます。後々、これらのコマンドがcommit時やpush時に実行されるように設定していきます。
{
"scripts": {
// 略
+ "check-types": "tsc --noEmit",
+ "lint": "eslint src/**/*.{ts,tsx}",
+ "lint:fix": "eslint src/**/*.{ts,tsx} --fix",
},
}
-
check-types
:型チェックを行います。(tsc
) -
lint
:リンターを実行します。(ESlint
) -
lint:fix
:修正可能であれば修正します。
以上で、ESlintの設定は完了です。次にコードフォーマッターであるPrettier
を導入していきます。
Prettierの導入
コードフォーマッターであるPrettier
を導入していきます。
Prettier関連パッケージのインストール
下記のコマンドでPrettier関連のパッケージをインストールします。
$ yarn add --dev prettier eslint-config-prettier
Prettier用の設定ファイルの追加
次に設定ファイルを追加します。
$ touch .prettierrc
追加した設定ファイルに設定内容を書き込んでいきます。
{
"singleQuote": true,
"semi": false,
"trailingComma": "all"
}
実行スクリプトを記述
ESlint
と同様にpackage.json
にスクリプトを記述していきます。こちらも後々、commit前に実行するように設定していきます。
{
"scripts": {
// 略
+ "format": "prettier --write .",
},
}
Huskyとlint-stagedの導入
huskyはpre-commit
(コミットの前)やpre-push
(プッシュの前)にpackage.json
に記述したスクリプトを実行してくれます。このタイミングでlint
などを実行することによって、コードの保守性が保たれるというわけです。
huskyでcommit時とpush時に処理を引っ掛けて、lint-stagedでGitのステージ上にあるファイルに対してESlintやPrettierのコマンドを実行していきます。
Huskyとlint-stagedをインストール
最初にhuskyとlint-stagedのパッケージをインストールしていきます。
$ yarn add --dev husky lint-staged
さらに下記のコマンドを実行することによって、.husky
ディレクトリが作成され、設定の雛形が自動作成されます。
$ yarn husky install
次章から、具体的にhuskyとlint-stagedを使ってcommit時にlintが実行されるように設定していきます。
commit前にlintが実行されるように設定する
最初にpackage.json
にlint-staged
で実行されるコマンドを設定していきます。
"devDependencies": {
・・・
// 略
},
"lint-staged": {
"*.{ts,tsx}": [
"yarn lint",
"yarn format",
"yarn lint:fix"
]
}
上記のように指定して、yarn lint-staged
と実行することでGitのステージ上にあるファイルに対してlintやformatなどのコマンドを実行することができます。
次に、huskyでpre-commit(commitの前)をトリガーにyarn lint-staged
を実行させたいと思います。
まずはpre-commit
用のファイルを作成していきます。
$ touch .husky/pre-commit
作成したファイルにcommit時に実行したい処理を記述します。今回はyarn lint-staged
を実行するように設定します。
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn lint-staged
上記のように記述することで、commit前にGitのステージ上に存在するファイルに対してlint関連のコマンドが実行されます。
次にpush時にyarn check-types
(型チェック)が実行されるように設定していきます。
push前にcheck-types(型チェック)が実行されるように設定する
最初にpre-push
(push前)用のファイルを作成していきます。
$ touch .husky/pre-push
下記のように設定することで、push前に型チェックを走らせることができます。
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn check-types
実行権限の付与
スクリプトを実行できるように実行権限を与えておきます。
$ chmod a+x .husky/pre-push
$ chmod a+x .husky/pre-commit
既存ファイルの修正
Next.js
はデフォルトではjs
ファイルが生成されるので、修正していきます。このままcommitしてもlintで怒られるので修正していきます。
変更を加えるのは下記のファイルです。
-
_app.tsx
(_app.js
から) -
index.tsx
(index.js
から)
それぞれ下記のように変更しました。
import { NextPage } from "next";
import { AppProps } from "next/app";
const MyApp: NextPage<AppProps> = ({ Component, pageProps }: AppProps) => {
return (
<>
<Component {...pageProps} />
</>
);
};
export default MyApp;
import { NextPage } from "next";
const Home: NextPage = () => {
return (
<>
<div />
</>
);
};
export default Home;
さらに、現状src/**/*.ts
パターンのファイルが存在しないので、こちらも修正していきます。
ちなみに、下記のようにESlintで怒られます。
No files matching the pattern "src/**/*.ts" were found.
Please check for typing mistakes in the pattern.
下記のように定数を管理する(予定の)ディレクトリとファイルを作成していきます。
$ mkdir src/constants
$ touch src/constants/index.ts
任意の値を定義しておきます。
export const hoge = "hoge";
以上で、commitとpushが正常に通るようになると思います!
最後に
解説は以上です。お疲れ様でした!
Discussion