VuePressプロジェクトのためのlinter設定

はじめに

最初の記事で書いたようにこのサイトはVuePressで構築されており、基本的にVSCode上で編集しています。 このようなコードの可読性やらなんやらを良い状態で保つために各言語にlinter+formatterが存在する(ことが多い)んですが、JavaScriptのそれがやたら入り組んでいたり落とし穴に引っ掛かったりしたのでそれをまとめておきます。

結果だけ知りたい方はこちら

目標設定

VuePressプロジェクトに含まれる主要ファイルの種類はおおよそ

  • *.js, *.ts : JavaScript, TypeScriptのファイル

    .vuepress/config.jsとかプラグインとか

  • *.vue : Vuejsの単一コンポーネントファイル

    テーマとか

  • *.md, *.html : Markdown, HTML

    コンテンツ

のいずれかであると言っていいと思います。このうち*.js*.vueの文法チェック+フォーマットができる状態にまでもっていくのを目標とします。またVSCodeで保存時に自動的にフォーマットできるように設定もします。

前準備

EditorConfigでフォーマットに関するごく基本的な設定だけしておきます。まあこの辺はお好みで。

# .editorconfig
root = true

[*]
indent_style = space
indent_size = 4

end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

ライブラリのインストール・設定

インストール

JSのlinter+formatterの記事は少し調べればわかるように既に市井にあふれています。その中で比較的使っている人が多そうなESLint+Prettierを基準にやっていきます。これはJSのlinterであるESLintのプラグインとしてJSのフォーマッターであるPrettierを登録し、ESLint経由でフォーマットを行う設定となります。

yarn add -D eslint prettier eslint-plugin-prettier eslint-config-prettier

vueファイルも扱いたいのでそのプラグインもいれます。

yarn add -D eslint-plugin-vue

必要なものはこれだけです。

基本設定

ここまで済んだら.eslintrcを作りましょう。jsとしても書けますが凝ったものは必要なさそうなのでjsonで大丈夫です。

// .eslintrc
{
    "root": true,
    "env": {
        "node": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:vue/essential",
        "plugin:prettier/recommended"
    ],
    "plugins": ["prettier", "vue"],
    // お好みで
    "rules": {
        // フォーマットされてないと警告
        "prettier/prettier": 1,
        // 未使用変数はエラー(_から始まる変数は例外的に許可)
        "no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
    }
}

これで基本的な設定まで終わりました。概観する分には楽ですね。

VuePress固有の設定

この時点でyarn eslint --ext .js,.vue .とかすればそれぞれの拡張子を持つファイルをフォーマットしてくれるはずですが、何故だか上手くいきません。ここで最初に引っかかりました。

yarn eslint --ext .js,.vue .

# Oops! Something went wrong! :(
#
# ESLint: 6.1.0.
#
# No files matching the pattern "." were found.
# Please check for typing mistakes in the pattern.

いや普通にファイルたくさんあるけど。

こちらの原因はESLintそのものの仕様にあります。簡潔に言うとdotfilesはデフォルトで無視されるっぽくしかもundocumentedらしい。えー。

do not ignore files started with `.` by default · Issue #10341 · eslint/eslint · GitHub

VuePressで使えない!って報告している人もいたみたい。

仕方がないので.eslintignoreにホワイトリストを書くことでチェックできるようにしました。

# .eslintignore
!.vuepress

これでコマンドラインでちゃんと動くようになった。

yarn eslint --ext .js,.vue .

# Done in 2.31s.

VSCodeの設定

基本設定

VSCodeにはESLint及びPrettierと連携するための拡張機能があるため、基本的にはそれに乗っかるようにすればうまくいきます。ファイル保存時にESLintが有効なファイルをフォーマットするためには.vscode/settings.jsonに以下の設定を足せば十分。これも調べるとすぐ出てくる設定です。

// .vscode/settings.json
{
    // 競合を防ぐためVSCode全体の自動フォーマットは無効化しておく
    "editor.formatOnSave": false,
    // ESLintが有効なファイルでは自動フォーマット
    "eslint.autoFixOnSave": true,
    // ESLint経由でPrettierを使用
    "prettier.eslintIntegration": true,
}

vueファイル

ESLintの機能をvueファイルにも適用するためにeslint.validateにvueに関する設定を追加します。

// .vscode/settings.json
{
    // フォーマット対象にvueを追加
    "eslint.validate": [
        "javascript",
        {
            "language": "vue",
            "autoFix": true
        }
    ],
}

また、シンタックスハイライトのためにはVeturという拡張機能が必要です。ただこの拡張機能はこの拡張機能でPrettierと連携してフォーマットする機能があるので、それを無効にしてあげる必要があります。さて、ここで重要なのが

  • PrettierはStylusをフォーマットしてくれない

  • Veturは拡張機能Manta's Stylus Supremacyを経由してStylusをフォーマット可能

  • Veturのフォーマット機能はvueファイルそのもののフォーマットは行わない

  • ESLint+Prettierはvueファイルそのもののフォーマットも行う

という点です。VuePressはStylusを主に使用しているためフォーマットできるようにしておくのが無難でしょう(というか自分がStylusしかわからない)。ファイルそのもののフォーマットとは何かというと、例えば以下の自明なvueファイルを考えます。

<!-- Sample.vue -->
<template>
    <div></div>
</template>



<script></script>



<style></style>

この内容に対しVSCode上でVeturによるフォーマットをかけても内容が変化しないのに対し、Eslint+Prettierによるフォーマットを行うと以下のように改行の数が調整されます。

<!-- Sample.vue (Eslint+Prettier) -->
<template>
    <div></div>
</template>

<script></script>

<style></style>

つまり、ESLint+Prettierはvueファイルに含まれる基本的なタグ<template>, <script>, <style>の配置を含めてフォーマットするのに対し、Veturはこれらのタグの中身のみをフォーマットするということが分かります。以上を踏まえると、Stylusを使いつつなるべく良い出力にするためにはESLint+PrettierとVetur、両方のフォーマット機能を有効にしつつ衝突しないように設定する必要があります。

{
    // Veturのフォーマット機能自体は有効化
    "vetur.format.enable": true,
    // Stylus以外のフォーマット機能を全部無効に
    "vetur.format.defaultFormatter.html": "none",
    "vetur.format.defaultFormatter.js": "none",
    "vetur.format.defaultFormatter.ts": "none",
    "vetur.format.defaultFormatter.css": "none",
    "vetur.format.defaultFormatter.scss": "none",
    "vetur.format.defaultFormatter.less": "none",
    "vetur.format.defaultFormatter.postcss": "none",
    "vetur.format.defaultFormatter.stylus": "stylus-supremacy",
    // EditorConfigに合わせる
    "vetur.format.options.tabSize": 4,
    // お好みで
    "stylusSupremacy.insertBraces": false,
    "stylusSupremacy.insertColons": false,
    "stylusSupremacy.insertSemicolons": false,
    // *.vueに対してのみVSCodeの自動フォーマットを有効化
    // これによってVeturによるStylusのフォーマットのみ行われる
    "[vue]": {
        "editor.formatOnSave": true
    }
}

これにより、vueファイル保存時に

  • eslint.autoFixOnSaveで着火されるESLint+Prettierによるフォーマット(Stylus以外)

  • [vue]: editor.formatOnSaveで着火されるVetur+StylusSupremacyによる<style lang="stylus">のフォーマット

が走ることになりますが、これまでの構成によりこれら2処理は互いに競合せず、またフォーマットに関する警告・エラーも吐きません。めでたしめでたし。

npm scripts (ついで)

linterとformatterに対応するコマンドをnpm scriptとして用意しておくと何かと楽できます。

// package.json
{
    // ...
    "scripts": {
        // ...
        "lint": "eslint --ext .js,.vue .",
        "fix": "eslint --fix --ext .js,.vue ."
    }
}

ところで.eslintrcrulesprettier/prettier1(warning)に設定したのはエディタで編集中に文法的に正しいものにエラー出すのはおかしくね?という個人的な意見によるものなのですが、完成品を文法チェックする際にはwarningでなくerrorにしたい気がします。そういう時には--ruleオプションでルールを上書きすることができます。

// package.json
{
    // ...
    "scripts": {
        // ...
        "lint": "eslint --rule \"prettier/prettier: 2\" --ext .js,.vue .",
        "fix": "eslint --fix --ext .js,.vue ."
    }
}

結果

以下に示すような設定になりました。少し別の設定も足してるけど。

CIの文法チェックも今のところ普通に通っているので多分問題なさそう。

まとめ

この言語面倒すぎない?Rustのrustfmt+rlsが神々しく見えてくる。