文系プログラマによるTIPSブログ

文系プログラマ脳の私が開発現場で学んだ事やプログラミングのTIPSをまとめています。

create-react-appのESLint or TSLint + Prettier設定とファイル保存時のPrettier+Lint自動実行設定

もう使わない設定がネット上に散見されていたり、両者で微妙な違いがあったりする罠があるので、少しだけまとめてみます。


f:id:treeapps:20170310234605p:plain

最近のjsではprettierの設定が一般的になってきているので、create-react-appと組み合わせる方法について、少しだけまとめてみます。

※ 本記事はmacを前提としているので、windowsの方は適宜読み替えて下さい。

BabelかTypescriptか

create-react-appには、使用されるトランスパイラは標準でBabelとなります。

しかし、オプションでtypescriptにする事も可能です。(Reactオフィシャルではないようです)

github.com

Babelの場合のlintは eslint になります。

Typescriptの場合のlintは tslint になります。

eslintとtslintは目的は同じものですが、設定ファイルの構造や中身が異なります。

という事で、今回はeslintとtslintの両方のパターンでprettierを適用する方法を書いてみます。

Babel版:eslint + prettierの設定

create-react-appをグローバルにインストール

npm install -g create-react-app

babelでプロジェクトを生成

create-react-app eslint-prettier-example

エディタ設定

cd eslint-prettier-example

cat<<EOF > .editorconfig
# Editor configuration, see http://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
max_line_length = off
trim_trailing_whitespace = false
EOF

eslintとprettierのモジュールをインストール

yarn add -D eslint prettier eslint-plugin-prettier eslint-config-prettier

eslint-plugin-prettierは、eslintを実行すると、prettier → eslintという順番で実行してくれるモジュールです。(prettierの整形結果をeslintが怒る事があるので)
eslint-config-prettierは、prettierが整形した部分をeslintに無視させるモジュールです。

create-react-appでeslintするとモジュールが足りないと警告されるので、以下をインストールします。

yarn add -D eslint-plugin-react eslint-config-react-app eslint-plugin-import eslint-plugin-flowtype eslint-plugin-jsx-a11y

js標準コーディング規約(JavaScript Standard Style)をインストールします。

yarn add -D eslint-plugin-standard eslint-config-standard eslint-plugin-node eslint-plugin-promise

eslint-config-standardはJavascript標準スタイル(皆で決めた標準lintルール)のeslint版です。例えば「function hoge(){}」で「hoge」と「()」の間にスペースが無い場合はNGとする、といったルール集です。
JavaScript Standard Style
標準スタイルにはgoogle版やairbnb版等があります。任意に変更して下さい。

eslint設定ファイル追加

cat<<EOF > .eslintrc.json
{
  "extends": [
    "standard",
    "plugin:prettier/recommended"
  ],
  "plugins": [
    "react",
    "prettier"
  ],
  "parser": "babel-eslint",
  "parserOptions": {},
  "env": {
    "browser": true,
    "es6": true
  },
  "globals": {
    "it": false
  },
  "rules": {
    "prettier/prettier": "error",
    "react/jsx-uses-react": 1,
    "react/jsx-uses-vars": 1,
    "no-console": "warn",
    // warning  Definition for rule 'jsx-a11y/href-no-hash' was not found に対応
    "jsx-a11y/href-no-hash": "off",
    "jsx-a11y/anchor-is-valid": "off"
  }
}
EOF

eslintの除外設定ファイル追加

cat<<EOF > .eslintignore
node_modules
**/*.min.js
src/registerServiceWorker.js
EOF

registerServiceWorker.jsを追加しているのは、自動生成されるregisterServiceWorker.jsがJavaScript Standard Styleに準拠しておらず、エラーが出まくるためです。。。

prettier設定ファイル追加

cat<<EOF > .prettierrc
{
  "singleQuote": false,
  "trailingComma": "es5",
  "semi": false
}
EOF

prettierに何をさせるかのオプションです。オプション一覧は以下に掲載されています。

prettier.io

package.jsonにscript追加

lint実行コマンドを追加
"lint": "eslint --fix src/**/*.js"

--fixオプションを付けると、prettierの自動整形+eslintの軽い整形を自動保存してくれるようになります。fixを付けないと、ただlintがエラー・警告を発し、ソースコードに編集は入らない挙動になります。

ESLintとPrettierの競合チェックコマンドを追加

eslintの設定とprettierの設定が競合していないかをチェックするコマンドも用意されています。
https://github.com/alexjoverm/tslint-config-prettier#cli-helper-toolhttps://github.com/prettier/eslint-config-prettier#cli-helper-tool

"eslint-check": "eslint --print-config .eslintrc.js | eslint-config-prettier-check"

lintを実行する

lintを実行すると、prettier -> eslintの順に実行されます。つまり、ソースコードの整形+保存 → lintで書式チェック、という順に処理が行われます。

yarn lint

Typescript版:tslint + prettierの設定

create-react-appをグローバルにインストール

npm install -g create-react-app

Typescriptでプロジェクトを生成

create-react-app --scripts-version=react-scripts-ts tslint-prettier-example

エディタ設定

cd tslint-prettier-example

cat<<EOF > .editorconfig
# Editor configuration, see http://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
max_line_length = off
trim_trailing_whitespace = false
EOF

tslintとprettierのモジュールをインストール

https://github.com/ikatyang/tslint-plugin-prettier
https://github.com/alexjoverm/tslint-config-prettier

yarn add -D prettier tslint-plugin-prettier tslint-config-prettier tslint-config-standard

tslint-plugin-prettierは、tslintを実行すると、prettier → tslintという順番で実行してくれるモジュールです。
tslint-config-prettierは、prettierが整形した部分をtslintに無視させるモジュールです。(prettierの整形結果をeslintが怒る事があるので)

tslintの設定ファイル追加

cat<<EOF > tslint.json
{
  "rulesDirectory": [
    "tslint-plugin-prettier"
  ],
  "extends": [
    "tslint-config-standard",
    "tslint-config-prettier"
  ],
  "rules": {
    "prettier": true
  }
}
EOF

tslint-config-standardはJavascript標準スタイル(皆で決めた標準lintルール)のtslint版です。例えば「function hoge(){}」で「hoge」と「()」の間にスペースが無い場合はNGとする、といったルール集です。
JavaScript Standard Style
標準スタイルにはgoogle版やairbnb版等があります。任意に変更して下さい。

prettier設定ファイル追加

cat<<EOF > .prettierrc
{
  "singleQuote": false,
  "trailingComma": "es5",
  "semi": false
}
EOF

prettierに何をさせるかのオプションです。オプション一覧は以下に掲載されています。

prettier.io

package.jsonにscript追加

lint実行コマンドを追加
"lint": "tslint -c ./tslint.json --exclude **/*.d.ts --exclude ./node_modules --project . --fix **/*.tsx",
    • fixオプションを付けると、prettierの自動整形+tslintの軽い整形を自動保存してくれるようになります。fixを付けないと、ただlintがエラー・警告を発し、ソースコードに編集は入らない挙動になります。
TSLintとPrettierの競合チェックコマンドを追加

tslintの設定とprettierの設定が競合していないかをチェックするコマンドも用意されています。
https://github.com/alexjoverm/tslint-config-prettier#cli-helper-tool

"tslint-check": "tslint-config-prettier-check ./tslint.json"

lintを実行する

lintを実行すると、prettier -> tslintの順に実行されます。つまり、ソースコードの整形+保存 → lintで書式チェック、という順に処理が行われます。

yarn lint

ファイル保存時にPrettierとLintを自動実行する

JetBrans WebStorm(Intellij IDEAやGoLandでも同じ)の設定

WebStormの設定はPrettier公式が設定例を提示してくれています。

prettier.io

中でも「Auto-save edited files to trigger the watcher」のチェックを外す事を忘れないようにしましょう。これのチェックがついていると、新規関数を追加しようと改行した瞬間Watcherに介入され、絶改行できなくなります。(自分の改行が即座にPrettierに消される)

File typeを指定できるので、jsxやtsx等の特定の拡張子に限定してWather + Prettierを実行させる事ができます。複数の拡張子を指定した場合は、File Watchersの設定を拡張子毎に複数作成しましょう。

Prettierのオプション設定ですが、Argumentsの実行時引数でオプションをツラツラ書いてしまうとプロジェクト内の.prettierrcと内容が食い違う可能性があるので、Arguments(Prettierには以下のようにプロジェクト内に配置したPrettier設定ファイルを指定した方が安心できそうです。

--config $ProjectFileDir$/.prettierrc --write $FilePathRelativeToProjectRoot$

「$ProjectFileDir$」にはプロジェクトのルートディレクトリが設定されるようなので、プロジェクトに配置した「.prettierrc」を指定すれば、プロジェクト固有のPrettier整形をWebStormに行わせる事が可能になります。

f:id:treeapps:20180420212313p:plain

File Watchersについては公式の日本語解説に詳細が書かれているので、是非ウォッチしておくとよいと思います。

ファイルウォッチャーの使用 - ヘルプ | IntelliJ IDEA


一見これで設定が完了したように見えますが、実はこれでは「未使用importの整理」が実現できていません。

これについてはマクロ機能を利用すると簡単に実現できます。既存のcommand + sは「Save all」ですが、「Optimize imports + Save all」にショートカットを書き換えてしまえばよいのです。

  1. File -> Edit -> Macros -> Start Macro Recordingをクリックして録画開始。
  2. Code -> Optimize importsをクリック
  3. File -> Save Allをクリック
  4. File -> Edit -> Macros -> Stop Macro Recordingをクリックして録画終了。マクロ名入力を求められるので「Save All with Optimize Imports」等と命名して保存します。
  5. File -> Preferences -> Keymap -> Macros -> Save All with Optimize Imports(自分で付けたマクロ名)をダブルクリック -> Add Keyboard Shortcut -> cmmand + sを指定。既にcommand + sには「Save All」が割り当てられていると警告されますが、removeして上書き保存します。

これでファイル保存のcommand + sで、Optimize imports+Save Allが実行され、ついでにFile WatchersによってPrettierも実行されるようになります。

もしArgumentsの部分に指定ミスがあった場合、File WatchersのPrettier設定時に「Show console」で「On error」を設定している場合(初期はerror)、コンソールに例えば以下のように表示されるので、どう間違えたかを確認し、修正します。

/Users/tree/go/src/tree-maps-go/node_modules/prettier/bin-prettier.js --find-config-path /Users/tree/go/src/tree-maps-go/.prettierrc --write client/containers/Test.js
[error] Cannot use --find-config-path with multiple files

Process finished with exit code 1

これで完成です!この設定をすると、以下のような事が自動で行われるようになります。

f:id:treeapps:20180420215512g:plain

Visual Studio Codeの設定

Prettierの拡張機能をインストールします。

f:id:treeapps:20180420223159p:plain

再起動後にprettierの自動実行設定をしますが、今回はワークスペースのみに設定していきます。(共通の設定にすると全jsが自動整形されてしまうので)

まず、プロジェクト直下に .vscode ディレクトリを作成します。続いて直下にsettings.jsonを作成します。

root
 └ .vscode
   └ settings.json

settings.jsonに以下を記述します。

Typescriptの場合のsettings.json
{
  "files.insertFinalNewline": true,
  "javascript.format.enable": false,
  "prettier.tslintIntegration": true,
  // for .ts
  "[typescript]": {
    "editor.formatOnSave": true,
    "editor.formatOnPaste": true,
    "editor.codeActionsOnSave": {
      "source.organizeImports": true
    }
  },
  // for .tsx
  "[typescriptreact]": {
    "editor.formatOnSave": true,
    "editor.formatOnPaste": true,
    "editor.codeActionsOnSave": {
      "source.organizeImports": true
    }
  },
  // for .json with comment
  "[jsonc]": {
    "editor.formatOnSave": true,
    "editor.formatOnPaste": true
  }
}
Babelの場合のsettings.json
{
  "files.insertFinalNewline": true,
  "javascript.format.enable": false,
  "prettier.eslintIntegration": true,
  // for .js
  "[javascript]": {
    "editor.formatOnSave": true,
    "editor.formatOnPaste": true,
    "editor.codeActionsOnSave": {
      "source.organizeImports": true
    }
  },
  // for .jsx
  "[javascriptreact]": {
    "editor.formatOnSave": true,
    "editor.formatOnPaste": true,
    "editor.codeActionsOnSave": {
      "source.organizeImports": true
    }
  },
  // for .json with comment
  "[jsonc]": {
    "editor.formatOnSave": true,
    "editor.formatOnPaste": true
  }
}

f:id:treeapps:20180420224549p:plain

この設定でTypescriptまたはBabelで、ファイル保存時・ペースト時に自動整形され、且つimportの最適化(未使用importの削除+並び替え)が行われるようになります。ワークスペース設定なので、他のプロジェクトの整形に影響を与える事なく、自動整形が設定されます。.vscode/settings.jsonはgitで管理するといいと思います。

後はcommand + sをすると、Prettier+ESLint(TSLint)の整形処理が実行されます。

f:id:treeapps:20180420224249g:plain

Prettierの設定は「.prettierrc」を参照してくれているようです。

未使用importの整理についてはできないっぽい?ようです。(ご存知の方いたら教えてください!)

Babel:eslintのエラー・警告集

Cannot find module 'eslint-config-standard'

Cannot find module 'eslint-config-standard'
Referenced from: /private/tmp/test/eslint-prettier-example/.eslintrc.json
Error: Cannot find module 'eslint-config-standard'
Referenced from: /private/tmp/test/eslint-prettier-example/.eslintrc.json
    at ModuleResolver.resolve (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/util/module-resolver.js:74:19)
    at resolve (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-file.js:515:25)
    at load (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-file.js:584:26)
    at configExtends.reduceRight (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-file.js:421:36)
    at Array.reduceRight (<anonymous>)
    at applyExtends (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-file.js:403:28)
    at loadFromDisk (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-file.js:556:22)
    at Object.load (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-file.js:592:20)
    at Config.getLocalConfigHierarchy (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config.js:226:44)
    at Config.getConfigHierarchy (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config.js:180:43)

eslint-plugin-standardはeslint-config-standardに依存しているので、eslint-config-standardも一緒にインストールしましょう。

yarn add -D eslint-plugin-standard eslint-config-standard

ESLint couldn't find the plugin "eslint-plugin-node"

Oops! Something went wrong! :(

ESLint: 4.10.0.
ESLint couldn't find the plugin "eslint-plugin-node". This can happen for a couple different reasons:

1. If ESLint is installed globally, then make sure eslint-plugin-node is also installed globally. A globally-installed ESLint cannot find a locally-installed plugin.

2. If ESLint is installed locally, then it's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

    npm i eslint-plugin-node@latest --save-dev

If you still can't figure out the problem, please stop by https://gitter.im/eslint/eslint to chat with the team.

メッセージ通り、eslint-plugin-nodeをインストールします。

yarn add -D eslint-plugin-node

ESLint couldn't find the plugin "eslint-plugin-promise".

Oops! Something went wrong! :(

ESLint: 4.10.0.
ESLint couldn't find the plugin "eslint-plugin-promise". This can happen for a couple different reasons:

1. If ESLint is installed globally, then make sure eslint-plugin-promise is also installed globally. A globally-installed ESLint cannot find a locally-installed plugin.

2. If ESLint is installed locally, then it's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

    npm i eslint-plugin-promise@latest --save-dev

If you still can't figure out the problem, please stop by https://gitter.im/eslint/eslint to chat with the team.

メッセージ通り、eslint-plugin-nodeをインストールします。

yarn add -D eslint-plugin-promise

ESLint couldn't find the plugin "eslint-plugin-import".

Oops! Something went wrong! :(

ESLint: 4.19.1.
ESLint couldn't find the plugin "eslint-plugin-import". This can happen for a couple different reasons:

1. If ESLint is installed globally, then make sure eslint-plugin-import is also installed globally. A globally-installed ESLint cannot find a locally-installed plugin.

2. If ESLint is installed locally, then it's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

    npm i eslint-plugin-import@latest --save-dev

If you still can't figure out the problem, please stop by https://gitter.im/eslint/eslint to chat with the team.

メッセージ通り、eslint-plugin-importをインストールします。

yarn add -D eslint-plugin-import

Configuration for rule "indent" is invalid:

/private/tmp/test/eslint-prettier-example/node_modules/eslint-config-standard/index.js:
	Configuration for rule "indent" is invalid:
	Value "[object Object]" should NOT have additional properties.

Referenced from: /private/tmp/test/eslint-prettier-example/.eslintrc.json
Error: /private/tmp/test/eslint-prettier-example/node_modules/eslint-config-standard/index.js:
	Configuration for rule "indent" is invalid:
	Value "[object Object]" should NOT have additional properties.

Referenced from: /private/tmp/test/eslint-prettier-example/.eslintrc.json
    at validateRuleOptions (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-validator.js:113:15)
    at Object.keys.forEach.id (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-validator.js:153:9)
    at Array.forEach (<anonymous>)
    at validateRules (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-validator.js:152:30)
    at Object.validate (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-validator.js:230:5)
    at loadFromDisk (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-file.js:549:19)
    at load (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-file.js:592:20)
    at configExtends.reduceRight (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-file.js:421:36)
    at Array.reduceRight (<anonymous>)
    at applyExtends (/private/tmp/test/eslint-prettier-example/node_modules/eslint/lib/config/config-file.js:403:28)

これは、create-react-appに内蔵されたeslintのバージョンでは不足していると起きるようです。

create-react-app自体のバージョンと、内蔵eslintのバージョンはそれぞれ以下の通りです。この組み合わせではエラーが起きました。

$ create-react-app --version
1.5.2
$ eslint --version
v4.10.0

なので、ちょっと嫌ですが別途eslintの最新バージョンをインストールしてしまいます。

yarn add -D eslint

現状では「v4.19.1」インストールされました。このバージョンであればエラーは出ませんでした。

prettier-eslintは使わないの?

ネット上でprettier-eslintを使った例が見られますが、prettier-eslintの作者が↑のようにもう使ってないよ、と言っているので、使わなくて大丈夫です。

今後はeslint-plugin-prettier と eslint-config-prettierの組み合わせを使えば特に問題ないかと思われます。

Typescript:tslintのエラー・警告集

yarn lintで大量のrule requires type informationが発生する

Warning: The 'await-promise' rule requires type information.
Warning: The 'no-unused-variable' rule requires type information.
Warning: The 'no-use-before-declare' rule requires type information.
Warning: The 'return-undefined' rule requires type information.
Warning: The 'no-floating-promises' rule requires type information.
Warning: The 'no-unnecessary-qualifier' rule requires type information.
Warning: The 'no-unnecessary-type-assertion' rule requires type information.
Warning: The 'strict-type-predicates' rule requires type information.

projectを指定すると直ります。

"lint": "tslint -c tslint.json --exclude **/*.d.ts --exclude node_modules --fix **/*.tsx"

"lint": "tslint -c tslint.json --exclude **/*.d.ts --exclude node_modules --project . --fix **/*.tsx"

雑感

これをまとめるの、実はかなり苦戦しました。(単に私が最近のフロントエンド事情・歴史を知らないからですが)もう使われていないモジュールの例に惑わされたり、バージョン違いによる謎エラーなどなど。

こうして記事にまとめておかないと、1週間後に全部忘れて「eslint + prettier動かねー!「tslint+prettier動かねー!」と発狂して全てを諦めてしまいそうです。

lintは最初は調査や設定に手間がかかりますが、lint + prettierの効果は絶大で、チームの内で横行するオレオレコードフォーマットルールを撲滅したり、しょうもない凡ミスをlintで気づく事を自動化する事ができます。

また、本来コードレビューでは設計やアーキテクチャが問題ないかを見て欲しいのに、
ここにスペースを追加して下さい
ファイル末尾に空行を入れて下さい
1行が長すぎるので改行を挟んで下さい
行末にセミコロンが抜けています
等という指摘に終始されてしまい、本来のレビュー目的が全スルーされる事がよくありますが、これを防ぐ事に繋がります。

最近私は運悪く実際にそれを経験してしまいましたが、このコードレビューのしょうもない指摘はプロジェクトの品質を下げる事に繋がるだけでなく、退屈な修正作業によってモチベーションが低下する事にも繋がってしまうので、是非設定しておきたいですね!