Prettierに関する小ネタ


はじめに

以下ではPrettierに関する小ネタをまとめる。

設定ファイルを楽に書く

Prettierは以下のいずれかの形式で設定する (Configuration File · Prettierより):

  • package.jsonファイル内の"prettier"キー。
  • JSONかYAMLで書かれた.prettierrcファイル。ファイル名に次の拡張子を付けることも可能: .json/.yaml/.yml
  • オブジェクトをエクスポートする.prettierrc.jsprettier.config.js
  • TOMLで書かれた.prettierrc.tomlファイル (.toml拡張子は必須) 。

Prettierのオプションに関しては以下にドキュメントが存在する:

Options · Prettier

それほど数は多くないためすべて目を通すことも大変ではないが、Prettierのウェブサイト内にはPlaygroundという、ビジュアルに設定ファイルを生成することができる機能があるので、手っ取り早く設定ファイルを作成したい場合は利用するといい。

エディタが左右に分割されており、左側のエディタがフォーマット前のコードを、右側のエディタがフォーマット後のコードをそれぞれ表わしている。タブのサイズやセミコロンの有無など、フォーマット時の設定を決めているのが一番左側にあるペインで、ここで各オプションを選択すると、すぐに右側のエディタにそのオプションを選択した場合のフォーマット結果が反映される。

フォーマット結果に満足できたら、最下部にある"Copy config JSON"をクリックすると、クリップボードに設定用のJSONがコピーされるので、あとは設定ファイルにペーストすればいい。

Playgroundを共有する機能などもあるため、チームで設定を議論するための場としても使うことができる。

eslint-config-prettierでESLintの不要なルールを取り除く

LinterとしてESLintを併用している人は多いと思うが、フォーマットに関するルールがPrettierの設定と衝突する。こうしたPrettierと競合するルールを無効化するためのライブラリに、eslint-config-prettierがある。

インストール方法:

$ npm install --save-dev eslint-config-prettier

ESLintの設定ファイルに以下を記述する:

{
  "extends": [
    "他の設定",
    "prettier"
  ]
}

これでESLintの不要なルールが無効化される。なお、他の設定をオーバーライドできるように、必ず配列の末尾に"prettier"を記入する必要がある。

Husky、lint-stagedとの連携

VS Codeなどで"Format On Save"を利用して保存時にフォーマットしている場合は別だが、そうした機能がないエディタを使用している場合に、フォーマットのコマンドを毎度入力することは面倒だ。他のメンバがフォーマットし忘れたコードをPRしてくる可能性もある。ところで、gitにはフック機能があり、これを利用して特定のgit操作時に任意のアクションを実行させることができるので、たとえば「リントがパスしない/フォーマットされていない」コードのコミットを弾くことなどができる。

フックを自前で書くことももちろん可能だが (なお、git init時に、フックのサンプルが自動的に.git/hooksに生成されるので、確認しておくといい)、Huskyを使えばフックがより簡単に設定できる。

Huskyをインストールすると、.git/hooksディレクトリにファイルが自動的に追加される:

$ ls .git/hooks      
applypatch-msg.sample  fsmonitor-watchman.sample  pre-applypatch.sample  prepare-commit-msg.sample  pre-rebase.sample   update.sample
commit-msg.sample      post-update.sample     pre-commit.sample  pre-push.sample        pre-receive.sample
$ npm install --save-dev husky
$ ls .git/hooks
applypatch-msg         fsmonitor-watchman.sample  post-merge    post-update.sample     pre-commit      prepare-commit-msg.sample  pre-rebase.sample   sendemail-validate
applypatch-msg.sample  post-applypatch        post-receive  pre-applypatch         pre-commit.sample   pre-push           pre-receive     update
commit-msg         post-checkout          post-rewrite  pre-applypatch.sample  pre-merge-commit    pre-push.sample        pre-receive.sample  update.sample
commit-msg.sample      post-commit        post-update   pre-auto-gc        prepare-commit-msg  pre-rebase             push-to-checkout

Huskyの設定は、package.jsonまたは.hukyrcにておこなう。たとえばコミット時にテストを実行したい場合は次のようにする:

// package.json
{
  "husky": {
    "hooks": {
      "pre-commit": "npm test",
    }
  }
}

あるいは

// .huskyrc
{
  "hooks": {
    "pre-commit": "npm test"
  }
}

これにより、コミット時にテストが実行され、テストが失敗した場合はコミットされないようになる。

Prettierの設定

ESLintやPrettierでチェックをおこなう場合は、package.jsonにあらかじめscriptsとしてコマンドを登録しておき、それをさらにhooksとして登録するのがいいだろう。たとえば:

// package.json
{
  "scripts": {
    "lint": "eslint .",
    "prettier": "prettier \"**/*.+(js|json)\"",
    "format": "npm run prettier -- --write",
    "check-format": "npm run prettier -- --list-different",
    "validate": "npm run lint && npm run check-format"
  }
  "husky": {
    "hooks": {
      "pre-commit": "npm run validate"
    }
  }
}

ここで、--list-differentはフォーマットされていないファイルがある場合、そのファイル名を出力してエラーコードを返す。これによりフックが止まってくれるため、フォーマットされていないファイルがある場合はコミットできないという仕組みになる。

なお、npm runの際に--を用いることで引数を指定することができるので、実際にフォーマットをおこなうformatと、チェックのみおこなうcheck-formatの重複部分をprettierとしてまとめている。

コミット時に自動フォーマットする

上の設定でフォーマットされていないコードを弾くことができるようになったが、上述したように、自動フォーマットされないエディタを使用している場合は毎度フォーマット用のコマンドを打ち込むことになり面倒だ。そこで、コミット時に自動的にコードをフォーマットすることを次に考える (これをしたいかどうかは人によって好みがあると思うが) 。

上のpre-commitの設定を素朴にnpm run formatに変更しても、フォーマット後のファイルがgit addされていないため期待通りの結果とならない。&&でコマンドをつなぐこともできるが、lint-stagedを使うとより簡単・効率的におこなうことができる。

lint-stagedは、変更された (git addされた) ファイルに対してのみリントをおこなうためのライブラリだ。これにより、リントの実行時間を短くし効率化できる。名前にはlintと書いてあるが、フォーマッタなど他のコマンドをパイプのようにつなぐこともできる。

インストール方法:

$ npm install --save-dev lint-staged

設定はpackage.jsonまたは.lintstagedrcにておこなう:

// package.json
{
  "lint-staged": {
    "*": "your-cmd"
  }
}

あるいは

// .lintstagedrc
{
  "*": "your-cmd"
}

のようにする。ここで、キーとなっている"*"は、コマンドの実行対象をglobで指定したものとなる。よって、たとえばJavaScriptとTypeScriptを対象とする場合は

"*.+(js|ts)": "your-cmd"

のようにする。stageされたファイルが"*.+(js|ts)"によってフィルタされ、その結果に対して"your-cmd"コマンドが実行されるという意味だ。"your-cmd"の部分は配列にして複数のコマンドを記述することもできる。

ESLintとPrettierを組み合わせる場合は次のように書く:

{
  "*.+(js)": [
    "eslint",
    "prettier --write",
    "git add"
  ]
}

また、設定内容をフックとして登録するためには、Huskyの設定ファイルを次のように変更する:

"hooks": {
  "pre-commit": "lint-staged"
}

これにより、まずフィルターされたstagedファイルに対してリントが実行され、次にフォーマットが実行され、最後にgit addされることとなる。ここまででエラーが発生しなければ、実際にコミットが実行される。

実行の順番をまとめると

commitを試みる -> pre-commit hookが呼ばれる -> lint-stagedが実行される -> 対象ファイルに対してリント、フォーマット、git addをおこなう -> 本commit

のようになる。