転職会議事業部の srkw です。 今期事業部内で利用する eslint および prettier の共通ルールを管理するパッケージを作成したので、その工程と成果物をご紹介したいと思います。 なお、今回紹介するパッケージの内容には多分に要修正箇所があり、今後他のプロジェクトとの優先順位を鑑みて、都度改善される可能性があります。その際はこちらの記事も併せて更新できればと考えています。
TL;DR
最終成果物は以下のリポジトリで公開しています。利用リポジトリ側での設定等は README に記載しております。
https://github.com/livesense-inc/eslint-config-template
モチベーション
転職会議は現在ページごと・機能ごとにサーバーを別で管理するマイクロサービス構成で開発を行っています。その中で利用する静的コード分析やコードフォーマッタのルールは codingstyle.jobtalk というリポジトリで管理していました。codingstyle.jobtalk リポジトリの概要は以下の通りです。
- ./target/配下のコードに対して eslint(+ eslint-plugin-prettier を経由した prettier 実行)と rubocop をそれぞれ実行する
- codingstyle.jobtalk 側で eslint や rubocop およびそれらのプラグインパッケージのバージョン管理・ルールの管理を行う
- codignstyle.jobtalk の circleci でイメージをビルドして ECR にプッシュする
- 利用リポジトリ側では ECR から codingstyle.jobtalk のイメージを取得し、静的検査を行いたいコードを codingstyle.jobtalk/target/配下にコピーして静的検査を行う
この方法でも静的検査は可能なのですが、特に JavaScript の静的検査については以下の問題がありました
- eslint-plugin-prettier が非推奨になっている
- docker コンテナを立ち上げて静的検査を行う都合上、利用リポジトリ側に eslint(および prettier)の設定ファイルが存在せず、エディタの自動整形が利用できない
この問題を踏まえて、eslint および prettier の共通ルールを管理するパッケージを github package registry に立てることにしました。また、github package registry との相性を鑑みて、CI は github actions 上で行うことにしました。
期待する挙動
前項の裏返しになりますが、期待する挙動は以下の通りです。
- 複数のリポジトリの eslint と prettier のルールを共通管理できること
- npm パッケージとしてバージョン管理されていること
- 利用者のローカル環境で eslint と prettier の設定ファイルを保持し、リポジトリごとにルールの個別指定ができること
以降、「パッケージ内部の実装」「パッケージ用の設定」「ワークフローの実装」「利用者側の実装」に分けて実装を紹介していこうと思います。
方針
今回のケースでは社外(organization外)にパッケージを公開することを想定していないため、scoped modulesとしてnpm packageを定義/publishします。
また、eslint および prettier の設定については、eslint と prettier の責務が類似していること、管理対象のリポジトリをむやみに増やしたくないことから、単一のパッケージとしてまとめて公開したいと考えました。
eslint の共用設定パッケージ(sharable config)については公式ドキュメントにもある通りeslint-config-xxx
という命名規則に則ってパッケージ名を設定することで、継承する設定パッケージの指定がシンプルになります。
パッケージ名をhogeとした場合の利用側.eslintrc.js
module.exports = { extends: ['./node_modules/@orgname/hoge/eslint-config'], // 相対パスで継承したいパッケージのeslint設定ファイルを指定 ... }
パッケージ名をeslint-config-hogeとした場合の利用側.eslintrc.js
module.exports = { extends: ['@orgname/eslint-config-hoge'], // パッケージ名のみの指定でOK ... }
prettierについては推奨されるパッケージ名(prettier-config-xxxx
)が一応あるものの、今回のケースでは推奨されるパッケージ名の規則を無視してもデメリットが大きくなかったので、パッケージ名は @orgname/eslint-config
とし、パッケージ内トップレベルに eslint と prettier の共通設定をそれぞれ配置する構成にします。
パッケージ内部の実装
パッケージで公開する成果物は eslint と prettier それぞれの設定が記載されたjsファイルになるので、パッケージのトップ階層に2つのファイルを配置します。
また、eslintのルールについてはテスト対象ファイルの拡張子ごとにルールを使い分けたい(後述)ため、lib/
配下に各拡張子ごとの設定ファイルを配置します。
$ tree js-rules js-rules ├── README.md ├── lib │ └── eslint-config │ ├── common.js │ ├── react.js │ └── typescript.js ├── package.json ├── eslint-config.js ├── prettier-config.js └── yarn.lock
eslint 用の設定
eslint 用の設定は拡張子ごとに期待するルールが異なるケースが多いと思います。ネット上にある他の共通ルールパッケージなどでは「ルールを複数公開して利用者側で適宜必要なルールを選択して import +継承する」というような実装もありましたが、今回は
- 事業部内でのみ利用する共通ルールであること、
- 事業部内で拡張子ごとに適用するルールを分けたいニーズがなかったこと
- 利用者側での共通ルールの利用のための記述を最小限にしたかったこと
などを踏まえて、./eslint-config.js
で拡張子ごとに適用する共通ルールを適宜呼び出す実装にしました。
./eslint-config.js
module.exports = { root: true, overrides: [ { files: ['*.js'], extends: ['./common.js'], }, { files: ['*.jsx'], extends: ['./common.js', './react.js'], }, { files: ['*.ts'], extends: ['./common.js', './typescript.js'], }, { files: ['*.tsx'], extends: ['./common.js', './react.js', './typescript.js'], }, ], };
上記で呼び出すルールは以下のように配置していて、各所で必要なルールを定義しています。
lib └── eslint-config ├── common.js ├── react.js └── typescript.js
prettier 用の設定
prettier についてはファイルの種類ごとに分けたいルールなどが特になかったため、prettier-config.js
にルールを記載して export しています。
./prettier-config.js
module.exports = { singleQuote: true, printWidth: 120, tabWidth: 2, };
ここまででパッケージ内部の実装は完了したので、次は publish するためのパッケージの設定を追加してゆきます。
パッケージ用の設定
パッケージの公開用の設定は package.json
に記載してゆきます。
package.json
{ "name": "@orgname/eslint-config", // @{organisation-name}/{package-name} "version": "0.0.1", "description": "eslintおよびprettierのルールをまとめるnpmパッケージ", "main": "eslint-config.js", "files": [ // 公開するファイルおよびディレクトリを全て指定する "lib", "prettier-config.js" ], "author": "Jobtalk Team", "license": "MIT", "publishConfig": { "registry": "https://npm.pkg.github.com/" // github package registryに公開するための設定 }, "repository": { "type": "git", "url": "git://github.com/orgname/{repo-name}.git" }, "dependencies": { ... }, "scripts": { ... } }
ここまでで公開するための設定とパッケージの中身ができたので、次は github actions で公開するためのワークフローを実装してゆきます。
ワークフローの実装
パッケージの内容を更新するためのパッケージ pubish 用ワークフローを ./github/workflows/
配下に追加します。
このワークフローはほぼ公式のサンプルそのままで動作しました。
name: publish on: release: types: - created jobs: test: # リリース前に行うテストジョブ runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: "12.x" - run: yarn - run: yarn lint publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: "12.x" registry-url: "https://npm.pkg.github.com" - run: yarn - run: npm publish env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # setup-nodeアクションでnpm認証用に利用される環境変数
ここまででリリース用の全ての実装が完了したので、次はリリース作業になります。
リリース作業
github action のパッケージ公開用ワークフローが created
という release イベントによって発火するようになっているので、ローカルで適当な git tag を作成し、github 上でリリースを作成します。
ローカルでタグを作成して push
$ git tag 0.0.1 $ git push origin 0.0.1
github 上でリリースを作成
無事リリースができたようなので、次は利用者側のリポジトリの設定に移ります。
利用者側の設定
利用者側で主に行うことは以下の3点です。
package.json
の更新.npmrc
の作成- eslint及びprettierの設定ファイルの作成
package.json の更新
package.json
{ ... "dependencies": { "@orgname/eslint-config": "0.0.1", ... }, }
.npmrc の作成
デフォルトのnpm package registry以外のレジストリ(GPR)からパッケージをダウンロードするために、.npmrc
ファイルを追加し、レジストリへの認証用の設定も記載しておきます。
.npmrc
registry=https://registry.yarnpkg.com/ @orgname:registry=https://npm.pkg.github.com //npm.pkg.github.com/:_authToken=${GPR_READ_TOKEN} //npm.pkg.github.com/orgname/:_authToken=${GPR_READ_TOKEN} always-auth=true
認証に利用するトークン(GPR_READ_TOKEN
)はgithubの設定ページから read:packages
権限を付与したトークンを生成して、環境変数経由で読み取るようにしています。
eslintおよびprettierの設定ファイルの作成
- eslint
.eslintrc.js
module.exports = { extends: '@orgname/eslint-config', rules: { // 利用者側で個別に設定したいルールを記載 } };
- prettier
.prettierrc.js
const prettierConfig = require('@orgname/eslint-config/prettier-config'); module.exports = { ...prettierConfig, // 利用者側で個別に設定したいルールを記載 };
おわりに
ここまでの作業で、「期待する挙動」に記載した要件を満たすことができ、その結果いままでよりも快適なJavaScriptの開発環境を構築することができました。 似たような課題感のある現場の方の参考になれば嬉しいです。