何をするのか
タイトル通りで、Next.jsでcommit時にステージング(git add)されているファイルにのみnext lint --fixとprettier --writeを実行します。
ググれば日本語の記事でもやり方は沢山出てくるのですが、意外と落とし穴があったので今回はその詳細を書いていこうと思います。
※大前提として初めからeslintが導入されている11以降のバージョンを使用しましょう。
よくあるやり方
huskyとlint-stagedを入れて.husky/_/pre-commitとpackage.jsonで以下のように設定して、commitする。
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn lint-staged
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint --dir src",
"lint:fix": "next lint --fix --dir src",
"format": "prettier --write ./src/**/*",
"prepare": "husky install"
},
"dependencies": {
}
"devDependencies": {
},
"lint-staged": {
"*.(js|ts)?(x)": [
"yarn format",
"yarn lint:fix"
]
}
}
これの何が問題かというと、これだと該当する全てのファイルにリントとフォーマットが効いてしまいます。
今回はステージングされているファイルにのみ反映したいのです。
lint-stagedという名前ですが、そこら辺をよしなにやってくれたりはしないようです。
やり方
「よくあるやり方」の問題点ですが、.lintstagedrc.jsを使ってコマンドを動的に生成する事で解決します。
ただその前にそこまでの一通りの手順を記載いたします。
また今回はJavaScript Standard Styleを適用したいと思います。(ここはお好みで何でも良いです)
パッケージの導入
※全部一気にやってもいいですが、今回はわかりやすく分けてインストールしています。
まずNext.jsのプロジェクトが作成し終わったら、そのディレクトリで、
yarn add --dev eslint-config-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-node
を実行して、JavaScript Standard Styleに必要なパッケージをインストールします。
次にコードフォーマッターのprettierと、eslintとprettierのルールの競合を回避してくれるeslint-config-prettierをインストールします。
yarn add --dev prettier eslint-config-prettier
そして最後にcommitやpushの前に特定の処理を挟んでくれるhuskyと、ステージングされているファイルの処理を行うためのlint-stagedをインストールします。
yarn add --dev husky lint-staged
パッケージの設定
次にeslintとhuskyの設定を行います。
まずeslintrc.jsonを以下のように編集します。
{
"extends": ["next/core-web-vitals", "standard", "prettier"]
}
このstandardとprettierはそれぞれJavaScript Standard Styleとeslint-config-prettierの設定です。
またこの時の注意点ですがフォーマッターのルールはnextより後に書きます。
次にhuskyの設定を行います。
以下のコマンドを実行してhuskyを初期化します。
npx husky-init && yarn
↑が実行されると、自動でルートディレクトリに.husky/_/pre-commitが作成されるので、そのファイルを以下のように書き換えます。
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn lint-staged
最後にlint-stagedの設定を行います。
ルートディレクトリに.lintstagedrc.jsを作成して、以下の内容を書き込みます。
module.exports = {
'*.(js|ts)?(x)': filenames => {
const rawFilePaths = filenames.map(file => {
const cwd = process.cwd()
const isMatchingPathFormat = file.includes(process.cwd())
const correctCwd = isMatchingPathFormat ? cwd : cwd.replace(/\\/g, '/')
const rawFilePath = file.replace(correctCwd, '.')
return rawFilePath
})
return [
`prettier --write ${rawFilePaths.join(' ')}`,
`next lint --fix --file ${rawFilePaths.join(' --file ')}`
]
}
}
以上でインストール&設定は完了です。
これでgit commitすると、自動でステージングされているファイルにリントとフォーマットが効くようになりました。
.lintstagedrc.jsについて
まず.lintstagedrc.jsが何をやっているのかですが、returnで返される配列のコマンドをcommitの前に実行してくれます。
つまり単純に
module.exports = {
'*.(js|ts)?(x)': filenames => [
'yarn format',
'yarn lint:fix'
]
}
のように書くと、ステージングされたファイルにjs/jsx/ts/tsxが存在すると、yarn formatとyarn lintが実行されます。
ただしこれでは「よくあるやり方」で書いたように全てのファイルにリントとフォーマットが反映されてしまいます。
そのためステージングされているファイルにのみ反映するコマンドを引数filenamesを使用して生成する必要があります。
コード解説
このfilenamesですが、こいつはステージングされているファイルの絶対パスを列挙した配列であり、中身は以下のようになっています。
[
'C:/Users/username/Desktop/my_project/.lintstagedrc.js',
'C:/Users/username/Desktop/my_project/next.config.js',
'C:/Users/username/Desktop/my_project/src/pages/members/index.tsx'
]
この配列をprocess.cwd(ここではC:/Users/username/Desktop/my_projectが出力される)を使ってプロジェクトルートまでを.に置換します。
const rawFilePaths = filenames.map(file => {
const cwd = process.cwd()
const isMatchingPathFormat = file.includes(process.cwd())
const correctCwd = isMatchingPathFormat ? cwd : cwd.replace(/\\/g, '/')
const rawFilePath = file.replace(correctCwd, '.')
return rawFilePath
})
[
'./.lintstagedrc.js',
'./next.config.js',
'./src/pages/members/index.tsx'
]
あとはこの配列を使ってお好みのコマンドが生成できます。
例えば上記のprettier --write ${rawFilePaths.join(' ')}であれば、prettier --write ./.lintstagedrc.js ./next.config.js ./src/pages/members/index.tsxとなります。
注意点
※isMatchingPathFormatとcorrectCwdですが、windowsの場合process.cwd()の出力がC:\Users\username\Desktop\my_projectのようにスラッシュがバックスラッシュになってしまう事があるのでそれの調整をしています。