React + TypeScript + ESLint + UIテスト環境の構築
初めに
この記事はAizu Advent Calendar 2021 10日目の記事です。
概要
React/TypeScriptにESLintを導入するする際、色々とこけることが多かったので書きました。また、今回UIテストにはStorybookを使用しますが、使い方までは説明しないので公式などを見てください。
前提
- npmが使える
- ターミナルで
npm -v
を実行して8.1.4
などの出力がされればOK - されなかった場合はnpmの環境構築をしておいてください
- ターミナルで
動作環境
- 機種:MacBook Air (M1 2020)
- OS:MacOS ver 11.2.3
- npm:ver 8.1.4
- node:ver 17.2.0
React/TypeScript
まずReactとTypeScriptのプロジェクトを作ります。今回使うcreate-react-app
はプロジェクトの名前のディレクトリを生成するため、先にプロジェクト用のディレクトリを作っておく必要はありません。またこのことから、GitHub上でリポジトリを作る場合PrivateとPublicの選択だけして、READMEの生成などは行わない方が良いと思います。
create-react-app
まずプロジェクトのディレクトリを配置したい場所へ移動してください。移動し終えたら、下記のコマンドをプロジェクト名
の部分だけ変更して実行しましょう。
$ npx create-react-app プロジェクト名 --template typescript --use-npm
create-react-app
はReactの環境を自動でビルドしてくれます。
プロジェクト名
はその名の通りあなたの作りたいプロジェクトの名称にしましょう。ただし、英大文字を使えないそうなので、日本語などは無理だと思います。記号だと、-
は使えましたが他は試していないので調べてみてください。
--template typescript
はその環境をTypeScriptのものにしてくれます。
最後に--use-npm
ですが、これは後ほど説明します。
追記
create-react-app A Cannot create a project named "A" because of npm naming restrictions: * name can no longer contain capital letters Please choose a different project name.
create-react-app あ Cannot create a project named "あ" because of npm naming restrictions: * name can only contain URL-friendly characters Please choose a different project name.
上のコマンドを実行したら、cd プロジェクト名
で作られたディレクトリに移動してください。
ESLint
初期設定
まず、お使いのエディターでpackage.json
を開き、
- "eslintConfig": {
- "extends":[
- "react-app",
- "react-app/jest"
- ]
- },
この部分を削除してください。次にターミナル上で、以下を実行してください。
$ npm install eslint --save-dav
これで、このプロジェクトにeslint
のパッケージがインストールされます。とはいえインストールされてもまだ使えないので、初期設定のために以下を実行してください。
$ npx eslint --init
実行するといくつかの質問をされるので以下のように答えてください。
質問内容 | 選択 | |
---|---|---|
1 | How would you like to use ESLint? | To check syntax, find problems, and enforce code style |
2 | What type of modules does your project use? | JavaScript modules (import/export) |
3 | Which framework does your project use? | React |
4 | Does your project use TypeScript? | Yes |
5 | Where does your code run? | 複数選択が可能です、Browserは選択しておいてください |
6 | How would you like to define a style for your project? | Use a popular style guide |
7 | Which style guide do you want to follow? | Airbnb: https://github.com/airbnb/javascript |
8 | What format do you want your config file to be in? | JSON |
9 | Would you like to install them now with npm? | Yes |
長くなりそうなので、ここでは解説しません。
これでプロジェクト内にeslintに必要なパッケージ群がインストールされ、.eslintrc.json
という、以下のようなファイルが生成されたと思います。
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"plugin:react/recommended",
"airbnb"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 13,
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
}
}
これでnpx exlint src/* --fix
などと実行すればESlintがsrc内のコードのうち修正可能な部分を自動整形してくれます。ただ、毎回これを打つのは手間なので、package.json
内のscripts
にスクリプトを追加します。
{
...,
"scripts": {
"lint": "eslint \"src/**/*.{ts,tsx}\" \"src/*.{ts,tsx}\" --fix",
...
},
...
}
こうすることで先程のコマンドをnpm run lint
で実行できます。
実行するとおそらく大量のエラーが出たことでしょう。これを今から修正していきます。
ESLintの設定
では、私が手元でnpm run lint
を実行して出たエラーを上から順に潰していきたいと思います。
1. error Unable to resolve path to module './App'
以下のコマンドを実行してください
$ npm install eslint-import-resolver-typescript --save-dev
したら、.eslintrc.json
に以下を追記してください
{
"settings": {
"import/resolver": {
"typescript": {}
}
},
...
}
2. error Missing file extension "tsx" for "./App"
.eslintrc.json
のrules
内に以下を追加
{
...
"rules": {
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"tsx": "never"
}
]
}
}
3. error 'test' is not defined
.eslintrc.json
に以下のように追加。
{
env: {
"jest/globals": true,
...
},
...
plugins: [
"jest",
...
],
...
}
4. error JSX not allowed in files with extension '.tsx'
.eslintrc.json
に以下のように追加。
{
...
"rules": {
"react/jsx-filename-extension": [
"warn",
{
"extensions": [
".tsx"
]
}
],
...
}
}
追加の設定
これで全てのエラーが消えたかと思います。ここからは私の好みが含まれますがより良い開発のためにいくつか設定していこうと思います。また、設定していることを前提としてやっていくので今回は設定しておいてください。
1. ファンクションコンポーネントをアロー関数で表記するようにする
.eslintrc.json
に以下のように追加。
{
...
"rules": {
"react/function-component-definition": [
2,
{
"namedComponents": "arrow-function",
"unnamedComponents": "arrow-function"
}
],
...
}
}
App.tsx
などでエラーが出ますがnpm run lint
を実行すれば修正されます
2. 関数の返り値の型を明示的に記入させる
.eslintrc.json
に以下のように追加。
{
...
"rules": {
"@typescript-eslint/explicit-function-return-type": [
"error",
{
"allowExpressions": true
}
],
...
}
}
App.tsx
とreportWebVitals.ts
でエラーが起きていると思うので修正します。
import React from 'react';
import logo from './logo.svg';
import './App.css';
const App: React.VFC = () => (
...
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler): void => {
...
Prop Typesに関するルールをオフにする
.eslintrc.json
に以下のように追加。
{
...
"rules": {
"react/prop-types": "off",
"react/require-default-props": "off",
...
}
}
これでESLintの設定は終わりです。一応ここまでで出来上がった.eslintrc.json
を載せておきます。設定の順番が異なっていたりしてもあまり気にしなくて大丈夫です。
{
"settings": {
"import/resolver": {
"typescript": {}
}
},
"env": {
"jest/globals": true,
"browser": true,
"es2021": true
},
"extends": [
"plugin:react/recommended",
"airbnb"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 13,
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint",
"jest"
],
"rules": {
"react/prop-types": "off",
"@typescript-eslint/explicit-function-return-type": [
"error",
{
"allowExpressions": true
}
],
"react/function-component-definition": [
2,
{
"namedComponents": "arrow-function",
"unnamedComponents": "arrow-function"
}
],
"react/jsx-filename-extension": [
"warn",
{
"extensions": [
".tsx"
]
}
],
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"tsx": "never"
}
]
}
}
Storybook
以下のコマンドを実行してください
$ npx sb init
途中で
? Do you want to run the 'eslintPlugin' fix on your project?
と聞かれるのでy
と入力。
すると、src
ディレクトリ内にstories
ディレクトリが生成されます。これで、npm run storybook
と実行すればStorybookが起動するのですが、このディレクトリ内に生成されたファイルはESLintによってエラーを吐いてしまうのでStorybookが途中で落ちます。今回は参考用にbutton.css
、Button.tsx
、Button.stories.tsx
、Introduction.stories.mdx
を残してそれ以外は消してください。
ではこれらのファイルのエラーを無くします。
1. error '@storybook/react' should be listed in the project's dependencies, not devDependencies
package.json
のdevDependencies
内から"@storybook/react": "^6.4.9",
を消し、同様の内容をdependencies
内に追記してください。"^6.4.9"
の部分は変わっているかもしれないので生成されていた方を使うようにしてください。
2. error Prop spreading is forbidden
該当する行の上に
// eslint-disable-next-line react/jsx-props-no-spreading
と書き込んじゃってください。行儀が悪いですが、Storybook用の型がよくわからないのでこれでどうにかします。
3. error propType "primary" is not required, but has no corresponding defaultProps declaration
interfaceごと消してしまってください。代わりに、以下のように型を宣言してください。
type ButtonProps = {
label: string,
primary?: boolean,
backgroundColor?: string,
size?: 'small' | 'medium' | 'large',
onClick?: () => void
}
そして、Buttonの型宣言を修正します。
...
const Button: React.VFC<ButtonProps> = ({
primary = false,
size = 'medium',
backgroundColor,
label,
}) => {
...
ついでに引数からpropsを消しちゃったので、以下のようにpropsを消します。
<button
type="button"
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
style={{ backgroundColor }}
>
4. error Prefer default export
Button.tsx
のButtonの宣言についているexport
を外して、ファイルの一番下にexport default Button
を追記。
import React from 'react';
import './button.css';
...
const Button: React.VFC<ButtonProps> = ({
...
};
export default Button;
これによってButton.stories.tsxでButtonをインポートしている部分を修正しないとなのでします。
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import Button from './Button';
...
これにてStorybookの設定完了です。npm run storybook
と実行してみてください。Storybookが立ち上がるはずです。
これにてReact + TypeScript + ESLint + Storybookの環境構築完了です。お疲れ様でした、そして頑張ってください。