既存のJSに安全にPrettierを一括適用した話

この記事は [ビビッドガーデン Advent Calendar 2021](https://qiita.com/advent-calendar/2021/vivid-garden) の2日目です。
こんにちは、食べチョクの開発をお手伝いさせていただいている@ogawa65aです。

大きくなり始めたコードベースのリーダビリティを改善するために、コードフォーマッターを導入したいと思ったことはありませんか？
しかし、既存のコードベースに一括で自動フォーマット修正を適用するのは少し不安が残りますよね？
かといって変更内容をすべて目で確認するのも至難の業です。
そこで、食べチョクでは、どのように安全かつ効率的にPrettierを既存のJSに適用したかをご紹介します。

## Prettierとは
設定できるオプションが非常に少ないのが特徴のコードフォーマッターです。
Prettierの設計思想は、「**コーディングスタイルに関するすべての議論を止める**」ことです。
もしもっとたくさんのオプションがあると、どのオプションを有効にするかの議論が始まってしまい本末転倒であるという考えが根底にあります。
たくさんのプログラミング言語がサポートされていますが、今回はJSに対象を絞ってお話ししたいと思います。

https://prettier.io/docs/en/option-philosophy.html

## Prettier導入後の課題
食べチョクでは今年、チームのエンジニアの人数が急激に増加したことに伴って、コードのフォーマットがバラバラになる問題に直面し、Prettierを導入しました。
Prettierを導入した当初は既存のコードにはPrettierを適用せず、プルリクで新規追加または変更されたファイルに対してのみGitHub ActionsでPrettierのチェックを走らせるという運用をしていました。
しかし、既存のファイルのほんの一部を修正しただけで、他の多くの箇所でPrettierによる自動修正が適用され、**プルリクの差分がPrettierの修正だらけになってしまって、コードレビューがしにくい状態が続いていました**。

Prettierによる修正のコミットを分けてレビューすることで問題は多少軽減できましたが、レビューでの指摘事項の修正コミットがさらに上に積まれたりすると、Prettierのコミットが他のコミットに挟まれて、やはりレビューしづらいという問題がありました。
どうしてもPrettierの修正が邪魔になってしまう場合は、.prettierignoreに追加して一時的にチェック対象外にしてしまうということもしていましたが、これでは一向に前に進まないという状況でした。
ですから、既存のコードへのPrettier適用もコツコツ進めていかなければ…、と思いつつ、なんとかうまく効率良く解決できないかと模索していました。

## まずはコードベース全体に適用してみる
とりあえず何が起こるか試しにコードベース全体にPrettierを適用してみました。
![スクリーンショット 2021-11-28 19.10.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2332516/dcbfe4d8-e44b-1cde-69ea-964c8ab6818b.png)
これではとてもじゃないですが怖すぎてマージできませんし、レビューもできません。
というか、そもそも重すぎてFile changedのタブを開くことすらままなりません。

よくよく見てみると実際の修正内容は、

- 改行位置の修正
- 末尾カンマの追加
- `{}`の内側のスペースの追加

がほとんどで、**フォーマッターなので当然といえば当然ですが、ほぼすべて構文上等価な修正である**という点に気が付きました。
であれば、**webpackでJSをビルドしてしまえば最終的には同じバンドルができあがるのでは？**という見通しが立ちました。

## Prettier適用前後でバンドルが一致するか確認してみる
Prettierを適用する前後でそれぞれproductionビルドしてできたバンドルがもしも同じであれば、最終的に配布するのはバンドルですから、安心してPrettierを適用できるわけです。

以下のように、バンドルは複数できて、ファイル名は`[name]-[contenthash].js`となっています。

```zsh
$ webpack build --node-env=production
$ ls public/entries
app1-578d7f8fbcd7d5a41f90.js
app2-b9a19f69d7d0c0e12917.js
...
```

Prettier適用前の`public/`を`public_before/`に退避し、Prettier適用後の`public/`を`public_after/`とリネームします。
次に、バンドルが一致するかどうかを確認するスクリプトを用意します。
今回は、Rubyでスクリプトを書きました。
ここでは、ファイル名の`[contenthash]`が一致すれば（つまり、ファイル名が一致すれば）、バンドルの内容も同じであることを利用しています。

```ruby
before_bundles = Dir.glob("**/*.js", base: "public_before/entries").sort
after_bundles = Dir.glob("**/*.js", base: "public_after/entries").sort

same_bundles = before_bundles & after_bundles
puts same_bundles
puts same_bundles.length

different_bundles = before_bundles - same_bundles
puts different_bundles
puts different_bundles.length
```

このスクリプトを実行してみると、**約100個あるバンドルの内、なんと95個のバンドルが一致することがわかりました**。
これで、一致したバンドルのソースコードについては安心してPrettierを適用できることになります。

## バンドルが変わってしまったソースコードを確認してみる
ここまでくれば、一致しなかったバンドルのソースコードについてはそのまま残しておいて、後で目視でレビューしても問題ないと思いますが、せっかくなのでもう少し踏み込んでみます。
一般に複数のソースコードが1つのファイルにまとめてバンドルされます。
ですから、変わってしまったバンドルは5つですが、それに対応するソースコードは5個以上あります。
この中でも**すべてのソースコードのPrettier修正がバンドルを変えてしまう原因になっているわけではない**はずです。
ですから、バンドルを変えてしまっているソースコードを特定したいと思います。

実際にバンドルが具体的にどのように変わってしまったかを確認してみます。
ここで、Prettier適用前後のバンドルに対して直接diffを取ってしまうと、バンドルは最小化されて1行になってしまっているので、どこが変更されたかわかりません。
ですから、バンドルに対して一度Prettierを適用してからdiffを取ります。

```zsh
$ prettier --write public_before/entries/app1-578d7f8fbcd7d5a41f90.js
$ prettier --write public_after/entries/app1-28b709482ec2bd6e901a.js
$ diff public_before/entries/app1-578d7f8fbcd7d5a41f90.js public_after/entries/app1-28b709482ec2bd6e901a.js
```

そうすると、差分の要因の箇所が表示されます。
その差分がどのソースコードから来ているかは、grepして確認すれば多くの場合すぐにわかります。
ちなみに、今回は、以下のようなReactのソースコードのPrettierによる修正がバンドルの差分を産んでいるということがわかりました。

```diff
  <span>
-   あいうえお{" "}
-   かきくけこ
---
+   あいうえお かきくけこ
  </span>
```

上記のようにテキスト中の改行で空白を明示的にJSXで挿入していた箇所が、1行にまとめたことで空白が生のテキストとして挿入される形に修正されています。

このように、具体的なソースコードの修正箇所を特定することで、そのソースコード以外はPrettierを適用しても問題ないことが確認できます。
今回は、変わってしまった5つのバンドルで、結果的に計5つのソースコードが原因で変わってしまっていることがわかりました。

**最初、724のファイルに変更があった内、この5つを除く719のソースコードに一括でPrettierを適用してもバンドルが変わらないという結果になりました。**
残り5つのファイルは、別のプルリクでPrettierを適用して目でレビューして、これで無事すべての既存のJSにPrettierが適用できました！:tada:

## 最後に
手探りで無策の状態からのスタートでしたが、非常に満足な結果になりました。
ここで説明した方法は、JS以外のコードやPrettier以外のツール（例えば、sedで一括置換するような場合等）にも適用できるかもしれません。

食べチョクのコードベースにはフロント・サーバーサイドともにまだまだ課題がたくさんありますが、試行錯誤や地道な改善を続けていければと思っています。

もし興味を持っていただけましたら、ぜひ食べチョクを覗いて行ってみてください！
ついでに、プロダクトチームも覗いて見てはいかがでしょうか？

https://www.tabechoku.com/

https://herp.careers/v1/vividgarden/KpLywERTT_Wh