このブログは Hugo という静的ジェネレータを使用していて、ブログテーマは拙作の harbor を使用している。

ありがたいことにそこそこスターを頂いていて、ポツポツと contribute してくれる人もいるので、継続してメンテや改善に取り組んでいきたいと思っているが、品質管理に SonarCloud というサービスを使うだけで、 linter や formatter の整備ができていなかった。ブログテーマで主に CSS や JavaScript を使用しているので、 ESLint や Prettier を導入してみた。

導入の手引きや細かいルールなどは公式ドキュメントを参照すればよい。ここでは harbor への導入履歴のようなかたちで記す。

ESLint

ESLint とは JavaScript の静的検証ツール。単純な構文チェックやコードスタイルの統一に使用できる。またユーザーが多くの検証ルールを追加することができるので、自分のプロジェクト独自のルールを設けることができる。

npm を利用してインストールする。

% npm install --save-dev eslint

eslintは別途作成する設定ファイルで定義された内容のルールで実行される。手入力で作成してもいいし、eslint --initというコマンドを用いると対話形式で定義内容を設定できる。

設定ファイルは.eslintrc.*という形式で作成する。eslintコマンドで作成する場合は、 JSON, YAML, JavaScript の形式から選択する。自分は JavaScript 形式で手入力で作成した。

module.exports = {
  env: {
    browser: true
  },
  extends:'eslint:recommended',
  parser: '',
  parserOptions: {
    ecmaVersion: 12,
    sourceType: 'module'
  },
  plugins: [],
  rules: {},
};

各設定項目の意味や何が設定できるのかはユーザーガイドに書かれているのでそれを参照する。軽く書くと、

Prettier

Prettier はソースコードを整形してくれるフォーマッター。 JavaScript だけでなく色々なファイル形式をサポートしている。業務では普段 Go を書いているので、言語がフォーマットをサポートしていることが当たり前だという感覚があったが、デフォルトでフォーマットをサポートしていない言語で OSS を作ってみるとそのありがたさが分かる。 harbor に時々もらう PR でコードスタイルが変だったり、そもそも結構バラバラであることに気づいた。

ESLint でもコードスタイルのチェックや整形は可能だが、「ちゃんとフォーマットしようと思うと設定項目が膨大になる」「eslint --fixを実行してルール違反を摘出する際、設定によってはコードが完全にフォーマットされない場合がある」など、微妙な点がある。 Prettier ではデフォルトの整形ルールが存在するため、よほどのこだわりがなければユーザーが多くの項目を設定する必要がないし、確実にソースコード全体を整形してくれる。

そこで、 format は prettier で、 lint は eslint で実行する、というように併用するのが便利(デファクトスタンダードっぽい)。

npm を利用してインストール。

% npm install --save-dev prettier

個別に設定したい項目がある場合は、.prettierrc.*の形式で設定ファイルを作成する。

module.exports = {
  "trailingComma": "es5",
  "semi": false,
  "singleQuote": true
};

「よほどのこだわりがなければデフォルトで問題ない」が、設定ファイルも使ってみたかったので、ここでは軽く作成してみた。設定できるオプションは公式ページを参照する。

go template も整形したい

Prettier 導入を検討し始めたのは、 HTML の <head> タグ内を修正する PR をもらったとき、 <script> タグ内のコードスタイルが気に入らなかったのがきっかけだった。 Prettier は HTML 形式もサポートしているが、 Hugo のテーマということで HTML 内には mustache 記法({{.}}で書くテンプレートの記法)がたくさんあり、このまま Prettier を実行すると mustache 部分が普通の HTML タグ内文字列として改行なしで一行になってしまう。

.prettierrc.jsで設定できるオプションでなんとかならないかと調べていたら、ある時期から Prettier のコア部分で新たにサポートする形式を増やすのではなく、プラグインで対応する方針に変わったようで、 go template 用のプラグインがあった。プラグインを導入して README に書かれている通りに.prettierrc.jsに追記することで対応。

module.exports = {
  ...
  "overrides": [
    {
      "files": ["*.html"],
      "options": {
        "parser": "go-template"
      }
    }
  ]
};

ESLint と Prettier を併用する設定

prettier-eslint というツールを使う。これはまず Prettier でコードを整形したあとに、eslint --fixを実行して検証してくれるというもの。これにより ESLint と Prettier での整形ルールの競合を防ぐ(実行順序的に ESLint の設定が優先される)。前は eslint-plugin-prettier というプラグインが使用されていて、 ESLint のプラグインから prettier を呼び出すなど、設定が煩雑になっていたようだが、prettier-eslintではその必要がない。

npm でインストール。

% npm install --save-dev prettier-eslint

package.jsonに以下のように script を定義する。prettier-eslintはコード整形用のツールのため、 ちゃんと Lint したい場合はeslintも実行する必要があるので注意。

{
  ...
  "scripts": {
    "format": "prettier-eslint --write $PWD/'static/src/**/*.js' $PWD/'layouts/**/*.html'; eslint $PWD/'static/src/**/*.js'"
  },
  ...
}

一応内容をみてみると、

コミット時に自動で prettier-eslint を実施

一応、ここまでで、npm run formatを実行することにより、はstatic/src以下のすべての JS ファイルと、layouts以下のすべての HTML ファイルに対して、コード整形(prettier)と Lint (ESLint)を実行することができるようになった。

だが、修正後にいちいちnpm run formatするのもめんどくさいし絶対に忘れる。なので次はgit commit時に自動で実行されるように設定する。

lint-staged

コミット時に毎回すべてのファイルに対して Prettier や ESLint を適用する必要はない。 lint-staged を使用することで、git addでステージングされたファイルについて特定のスクリプトを実行する。

npm でインストール。

% npm install --save-dev lint-staged

package.jsonに以下を追記。

{
  ...
  "lint-staged": {
    "*": [
      "prettier-eslint --write $PWD/'static/src/**/*.js $PWD/'layouts/**/*.html'; eslint $PWD/'static/src/**/*.js'"
    ]
  },
  ...
}

husky

やりたいことは Git hooks を作成することで Git コマンドに対して特定のスクリプトを実行、だがいちいちシェルスクリプトを作って.git/hooks以下において…とやるのはめんどう。設定を書くだけで Git hooks をよしなに準備してくれる husky を使う。

npm でインストール。

% npm install --save-dev husky

package.jsonに以下を追記。

{
  ...
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  ...
}

pre-commit(コミット前)にlint-stagedを実行する。lint-stagedはこの前に設定した内容。

git commitしたのになんか動かない

パッと導入してさて試そうと思ったのだが、なんかそのままコミットされてしまう。.git/hooksを削除してhuskyを再インストールしたり色々試してみたがわからず…なんでや…と思っていたら、 npm のバージョンが原因だった。

npm のバージョンを v7 から v6 にしてはじめから。そしたらうまくいきました。

参考情報

以下の参考情報をみながら試行錯誤して設定した。多分いらない設定とかもっとこうしたほうが良いというものもあると思う。

あと、 Hugo 使ってる人は harbor の導入、ご検討ください。