リリース済みのCRAプロダクトにESLintを導入した際の備忘録です
どのように既存プロダクトに導入させたかという点に主眼を置いた記事となっています
はじめに
2024年1月にリリース済みのTypeScriptプロダクトにESLintを段階的に導入しました。
2023年11月に入社→12月にESLintの導入を提案、メンバーの皆様に快く受け入れていただいたこともあり、それなりにスムーズに進めることができました。
導入時、多くの方が記事を残しており助かったこと、どうするのかは残っているものの、どのようなスケジュールや現場の動きで導入したかはなかなか残っていないため、導入時の動きを備忘録として残しておきます。
11月-静的解析が・・・ない
元々フロントエンドへの自動テスト導入が必要と聞いていたものの、静的解析は入っているかなと思っていたのですが、いざソースコードをみてみるとESLint自体のインストールはされているものの型チェックが行われない状況でした。
ESLint自体のインストールされているけれど型チェックが行われない状態とは
ESLintのver6以降であれば初期設定でもある程度型情報の必要なルールはありますが、採用していたのはver5であり、参考記事のとおり型の関係のないルールもあります。
そして、@typescript-eslint/eslint-pluginのバージョンはver.5、かつ、型の関係ないrecommendedのみとなっていました。anyもエラーなしで利用できるため、型エラーの検知がなく、型があたるかどうかは人によるという属人化が進んでいました。
しかもなぜか型誤りでもコンパイルエラーがおきない現象が発生しており、子コンポーネントで受け取るはずのないpropsを親コンポーネントで指定されている箇所も見られました。
11月は頭出しのみ
新規開発をすすめていても型があわず開発しづらいため、型チェックができるようESLintを本格導入したいと相談したところ、ありがたいことにリーダーとメンバーも入れたいと言ってくれたため、開発の隙間で準備を進めました。
<参考記事>
- 公式ポスト「Announcing typescript-eslint v6」
- ESLintのver5とver6の違いをわかりやすく解説してくれている記事「typescript-eslint v6 アップデートガイド」
12月-静的解析の導入方針策定とサンプルPR作成
ESLint構成
結論からいうと、以下理由よりairbnbのeslint configをシンプルに導入しました。
理由
- 世界的導入数1位でありメンテナンスが継続的にされそうである:下図は当時のダウンロード数比較
- 導入時の参考例が豊富:たとえばメルカリさんの導入例
- メンテナンスがされていれば最新のESLintルールへの追従が簡単である
なぜ個別設定でなくconfigで一括でextendするのか(気になった人向け)
- 実際のプロダクトで利用されている標準的な設定が一括設定できること、他のESLint設定との比較がしやすい(ベースがairbnbなのかxoなのかフルカスタマイズなのかといった比較ができる)、最新のESLintルールへの追従が簡単なこと(そのeslintルールもうないよエラーで時間を消費したくない)
- まずは標準のESLint開発に慣れる
- 慣れてきたら完全カスタマイズするのもありだが、更新するのがしんどすぎるので急がなくてもよい
- 詳しくは:ESLintのルールを全部手動で設定するのは大変だからやめておけ
- フルカスタマイズ例 - むしろカスタマイズしたくないならXOにするのも手だがまずはある程度エラー解消しないと採用できない
参考記事のものをみつつ、airbnbのESLintを導入したPRを作成、どのように導入するのかを話し合う場を設け、方針の共有や疑問点や懸念点をすりあわせました。
その結果、以下のような構成となりました。
module.exports = {
...
extends: [
'plugin:react/recommended',
'airbnb',
'airbnb-typescript',
'airbnb/hooks',
'prettier',
],
rules: {
'react/function-component-definition': [
2,
{ namedComponents: 'arrow-function' },// 基本アロー関数でかかれていればOKとする設定
],
'react/react-in-jsx-scope': 'off', // react v18にも関わらず import reactしないといけない設定をoff
'@typescript-eslint/no-shadow': ['error', { ignoreTypeValueShadow: true }], // 型の名前とその他の名前は被っても支障がないため同名被りエラーを表示させない
},
...
}
<参考記事>
- airbnbのgitHub
-
airbnb(typescript)のgitHub
-ESLint設定内容は 「Vite + React + SWC + ESLintの環境を整える」が非常に参考になりました
ESLint導入とともに行う必要があったこと
ESLint対応のため他ライブラリへの影響も少しありました
-
@typescript-eslint/eslint-pluginと@typescript-eslint/parserのバージョンをアップ
- パッケージの依存関係解消のため@^5.5.0 から@^6.0.0 にしました
- 考え方が違うアップグレードなので、通常は慎重に行う必要があるのですが、もともと型チェックを行う[ “plugin:@typescript-eslint/recommended-requiring-type-checking”]が入っていなかったので影響はほぼほぼなかったです(いままでは「 TypeScript の型情報がいらないルール」だけだったのが逆に動きやすくなりました)
-
prettier-plugin-organize-importsをインストール
ESLintのうちimport/orderルールがあり、これをフォーマッターであるprettierに自動解決させるためにインストール -
styled-componentsなどライブラリに型を追加
@types/styled-componentsをインストール -
env設定
コンパイルエラーやlinterエラーが多いため、以下の記述を追加しました(3000を超えるproblems数でしたので後述する段階的解決が必要)-
.env
# 型定義が落ち着くまで、コンパイルエラーはブラウザに表示しない TSC_COMPILE_ON_ERROR=true # ESLint のエラーが発生してもブラウザ上に表示をしないように設定 ESLINT_NO_DEV_ERRORS=true DISABLE_ESLINT_PLUGIN=true
-
<参考記事>
1月-ESLint導入
対応ルールを決めて段階的に導入
上記の通りの構成でPRを作成したところ、3000超えのproblemsであることがわかりました。
.eslintignoreにて多くのディレクトリのエラーを沈黙させつつ導入しました。
エラーは以下のルールにて段階的に解消していく予定です
- 開発で触るディレクトリのignoreを削除し、エラーを復活させてから作業する
- 数ヶ月ごとにproblems数の定点観測をし、記録用のスプレッドシートに記入していく
- 触らなさそうなディレクトリのものについては観測結果により対応
- warnを許容するかどうかは適宜チームに相談する
しんどかったらチームに相談しつつ進められるようmtgで話し合いもいたしました。
以下のようなエラー対応ナレッジの土台も完備しているので大丈夫・・・なはずです。
ふりかえり
デグレへの警戒とPRの切り分けをしすぎるくらいする位でいい
なんとか良い感じに導入できましたが、以下のような反省点がありました。
- はじめのエラー修正ではデグレが発生しやすいので、修正後のPRはペア作業にてデグレチェックを行った方がよかった(1つ修正するごとに動作確認はしていたものの、気づかないところでデグレが発生しました)
- ESLintのエラー修正のPRは新規開発の前に実施し、新規開発のPRと分けて出すように開発フローを変更する必要がありました。分けられたら分けたらよいかなという曖昧な考えですと、混ざりがちになります。新規開発とESLintエラー修正が混ざった場合に問題を切り分ける時間がもったいないです。私自身もいきなりできませんでしたし、注意喚起することも想定以上に大事でした
今後は
新規開発がどうしても入ってきますが、無理のない範囲で目指せproblems0です!!
あとは、チームのルールをESLintに反映させたり、コンパイル時に暗黙のany型のチェックもつけていったりと夢が広がりますね!
※暗黙のany型のチェックについて:型をつけ忘れており推論でanyとされているものがないかの確認です。ESLintでいい感じに暗黙のanyで怒ってくれるオプションがみつからなかったため、ある程度エラーがなくなったころに以下を追加しようとしています。コンパイル時のエラーとなってしまうので、まとまってエラーを解消するタイミングがないとなかなか着手できない第二の山場です。
"noImplicitAny": true /* 暗黙の 'any'型で式と宣言のエラーを発生させます。 */,
"strictNullChecks": true /* 厳密なnullチェックを有効にします。 */,
"noImplicitThis": true /* 暗黙の 'any'型で 'this'式のエラーを発生させます。 */