🦊

【Next.js】環境構築を解説してみた【TS+ESlint+ Prettier+Husky】

2022/05/07に公開

こんにちは、あきのと申します。
普段は、業務でもプライベートでもフロント・バックエンド・インフラ等を幅広く触っています。最近の悩みはデザインセンスがないことです。(デザイナーさん仲良くしてください!)

はじめに

最近、個人開発やコミュニティのWebフロントでNext.jsプロジェクトを何度も立ち上げることがあったので、解説も兼ねて記事として残しておこうと思います。

今回のコードはGithubで公開しています。

https://github.com/onikan27/next-sample

解説すること

  • 各パッケージの設定方法
  • Next.jsの環境構築手順と解説
  • ESlintPrettierについて
  • Huskylint-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のオプションは色々あるので、公式ドキュメントを参照しながら書いていくことをお勧めします。(ちなみに、公式のドキュメントがとてもみやすいです。)

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ディレクトリ内のモジュールをインポートするには下記のように書くことができます。

ex.ts
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,

jsjsxファイルをコンパイル対象に入れるかどうかを指定する。この場合、型チェックは行われないが、記法を指定のバージョンに変換するという作業が行われる。

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

下記のように指定しました。

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

ESlint用の設定ファイルの追加

次にESlintの設定ファイルを作成し、設定を記述していきます。

ターミナル
$ touch .eslintrc.json

下記のように指定しました。各項目に関しては次章で解説していきます。

.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時に実行されるように設定していきます。

package.json
{
  "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前に実行するように設定していきます。

package.json
{
  "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.jsonlint-stagedで実行されるコマンドを設定していきます。

package.json
"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を実行するように設定します。

pre-commit
#!/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前に型チェックを走らせることができます。

pre-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.tsxindex.jsから)

それぞれ下記のように変更しました。

_app.tsx
import { NextPage } from "next";
import { AppProps } from "next/app";

const MyApp: NextPage<AppProps> = ({ Component, pageProps }: AppProps) => {
  return (
    <>
      <Component {...pageProps} />
    </>
  );
};

export default MyApp;
index.tsx
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

任意の値を定義しておきます。

src/constants/index.ts
export const hoge = "hoge";

以上で、commitとpushが正常に通るようになると思います!

最後に

解説は以上です。お疲れ様でした!

参考記事

Discussion