Next.js 10にTypeScriptとESLint, PrettierとJestを導入する

# はじめに
この記事にはNext.js 10にTypeScript, ESLint, Prettier, Jestを導入するまでの手順を書いていきます。
個人的な好みの設定も記述していますので、適宜設定を追加したり、抜いたりしながら進めてください！

最終的に出来上がるソースコードはこちらにあります。
https://github.com/282Haniwa/next-example

# プロジェクトの作成

まず、`next-create-app`を使ってプロジェクトを作成します。

```shell
$ npx create-next-app your-project
# or
$ yarn create next-app your-project
```

この時点で、私はソースコードを`src`ディレクトリで管理したいので修正します。
`src`ディレクトリでソースコードを管理したい場合、コンフィグファイルや`public`ディレクトリは`src`配下に移動しないように注意しましよう。

```
.
├── .next
├── node_modules
├── README.md
├── package.json
├── public
├── src
│   ├── pages
│   └── styles
└── yarn.lock
```

最後に`yarn dev`をすると、[localhost:3000](http://localhost:3000)でアクセスできることが確認できると思います。

参考 : https://nextjs.org/docs/advanced-features/src-directory

# TypeScriptの導入

プロジェクトが作成できたら、`src/pages/index.js`を`src/pages/index.tsx`に変更するか、空の`tsconfig.json`を作成して`yarn dev`を実行してください。

`yarn dev`を実行するとメッセージとともにTypeScriptを導入するためのコマンドが表示されるので、その通りに実行しましょう。

```shell
$ yarn add --dev typescript @types/react @types/node
```

パッケージを追加できれば、`yarn dev`と実行することでNext.jsでTypeScriptを利用するのに必要なファイルが自動で生成されます。
必要に応じて`tsconfig.json`などを編集してください。

とりあえず、`tsconfig.json`のstrictはtrueにしておきましょう。

```
{
    ...
    "strict": true,
    ...
}
```

これでTypeScriptの導入は完了です。

# ESLint, Prettierの導入

ESLintとPrettierを導入していきます。
ESLint関係のパッケージとPrettier関係のパッケージをそれぞれ追加します。

```shell
$ yarn add --dev \
    eslint \
    @typescript-eslint/parser \
    @typescript-eslint/eslint-plugin \
    eslint-plugin-react \
    eslint-plugin-react-hooks \
    eslint-plugin-jsx-a11y \
    eslint-plugin-import\
    eslint-import-resolver-typescript
```

```shell
yarn add --dev prettier eslint-plugin-prettier eslint-config-prettier
```

最低限`eslint`, `@typescript-eslint/parser`, `@typescript-eslint/eslint-plugin`, 
 `eslint-plugin-react`があればNext.jsでESLintを利用することはできますが、個人的にこのようなプラグインの構成にしています。
適宜好みに合わせてカスタマイズしてください。

このプラグインの構成での`.eslintrc.js`の構成は以下のファイルのようになります。

また、ESLint, Prettierの導入と同時に、`src`から始まる絶対パスでimportできるようにaliasを設定しています。

```js:.eslintrc.js
/*eslint-env node*/
module.exports = {
  root: true,
  env: {
    browser: true,
    es2020: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/eslint-recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'plugin:jsx-a11y/recommended',
    'plugin:import/errors',
    'plugin:import/warnings',
    'plugin:prettier/recommended',
    'prettier/@typescript-eslint',
    'prettier/react',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 2020,
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint', 'react', 'import'],
  settings: {
    'import/resolver': {
      node: {
        extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
      },
      typescript: {
        config: 'tsconfig.json',
        alwaysTryTypes: true,
      },
    },
  },
  rules: {
    '@typescript-eslint/ban-types': [
      'error',
      {
        types: {
          '{}': false,
        },
      },
    ],
    'react/prop-types': ['off'],
    'react/react-in-jsx-scope': 'off',
    'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }],
    'import/order': ['error'],
    'prettier/prettier': [
      'error',
      {
        trailingComma: 'all',
        endOfLine: 'lf',
        semi: false,
        singleQuote: true,
        printWidth: 80,
        tabWidth: 2,
      },
    ],
  },
}

```

```js:next.config.js
/* eslint-env node */
/* eslint-disable @typescript-eslint/no-var-requires */

const path = require('path')

module.exports = {
  webpack(config) {
    config.resolve.alias = {
      ...config.resolve.alias,
      src: path.join(__dirname, 'src/'),
    }
    return config
  },
}

```

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

必要に応じて編集しながら設定してください。

`package.json`の`scripts`に`yarn lint`で実行できるように追記しておきましょう。

```json:package.json
{
  ...
  "scripts": {
    ...
    "lint": "eslint --ext .js,.jsx,.ts,.tsx .",
    "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx --fix ."
  },
  ...
}
```

### huskyの導入

ついでにcommit時にlintが走るようにhuskyも入れておきましょう。

```shell
$ yarn add --dev husky lint-staged
```

```json:package.json
{
  ...
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix"
    ]
  }
  ...
}
```

これで、ESLint, Prettierの導入は完了です。

# Jestの導入

Next.jsのプロジェクトにJestを導入していきます。
まず、必要なパッケージを追加します。

```shell
$ yarn add --dev  \
    jest  \
    ts-jest \
    react-test-renderer \
    enzyme \
    enzyme-to-json \
    @wojtekmaj/enzyme-adapter-react-17 \
    @types/jest \
    @types/react-test-renderer \
    @types/enzyme
```

EnzymeはまだReact 17に対応していないので、代わりに`@wojtekmaj/enzyme-adapter-react-17`を使います。
EnzymeはまだReact 17に対応したときに置き換えれば良いはずです。

参考: https://github.com/enzymejs/enzyme/issues/2429

必要な設定を記述します。

```js:jest.config.js
/* eslint-env node */

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: [
    '**/__tests__/**/*.+(ts|tsx|js)',
    '**/?(*.)+(spec|test).+(ts|tsx|js)',
  ],
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
  },
  moduleNameMapper: {
    '^src/(.*)$': '<rootDir>/src/$1',
  },
  testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/'],
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
  snapshotSerializers: ['enzyme-to-json/serializer'],
  setupFilesAfterEnv: ['<rootDir>/test/setupTests.ts'],
  // https://github.com/zeit/next.js/issues/8663#issue-490553899
  globals: {
    // we must specify a custom tsconfig for tests because we need the typescript transform
    // to transform jsx into js rather than leaving it jsx such as the next build requires. you
    // can see this setting in tsconfig.jest.json -> "jsx": "react"
    'ts-jest': {
      tsconfig: '<rootDir>/test/tsconfig.jest.json',
    },
  },
}

```

```json:test/tsconfig.jest.json
{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "jsx": "react"
  }
}

```

```ts:test/setupTests.ts
import Enzyme from 'enzyme'
import Adapter from '@wojtekmaj/enzyme-adapter-react-17'

Enzyme.configure({ adapter: new Adapter() })

```

最後に`package.json`の`scripts`に`yarn jest`で実行できるように追記しておきましょう。

```json:package.json
{
  ...
  "scripts": {
    ...
    "jest": "jest"
  },
  ...
}
```

これで、Jestの導入は完了です。
試しにコンポーネントを作って試してみます。

```tsx:src/compontns/Hello/Hello.tsx
import React from 'react'

type Props = {
  name: string
}

export const Hello: React.FC<Props> = (props) => {
  const { name } = props
  return <div>Hello {name}!</div>
}

```

```tsx:src/compontns/Hello/Hello.test.tsx
import React from 'react'
import { shallow } from 'enzyme'
import { Hello } from 'src/components/Hello'

test('Hello test', () => {
  const hello = shallow(<Hello name="World" />)

  // Interaction demo
  expect(hello.text()).toEqual('Hello World!')
  hello.setProps({ name: 'Jest' })
  expect(hello.text()).toEqual('Hello Jest!')

  // Snapshot demo
  expect(hello).toMatchSnapshot()
})

```

`yarn jest`を実行すれば、テストが走っていることが確認できます。

# まとめ

以上でNext.js 10にTypeScriptとESLint, PrettierとJestを入れた環境の構築が完了になります！

最新のバージョン(2020/12/21時点)で環境構築する上でEnzymeがまだReact 17に対応していなかったりなどと、まだ業務上で使うには少し不安がありそうですが、以上の手順で環境構築まではできました！
新しい機能などを使いながら開発していければ良いなと思います！

試行錯誤しながら進めていましたので、こっちの方がいいよみたいなアドバイスがあれば是非コメントで教えてください！

# 参考
- https://nextjs.org/docs/api-reference/create-next-app
- https://nextjs.org/learn/excel/typescript
- https://typescript-jp.gitbook.io/deep-dive/intro-1/jest