NuxtアプリのESLintをv9にアップグレードして@nuxt/eslintに全乗っかりしてみた
おつかれさまです。オウンドメディア担当の水谷です。
自チームで管理しているフロントエンドアプリ(Nuxtベース)に導入しているESLintをv9にアップグレードするとともに@nuxt/eslint
を導入したのでその紹介です。
TL;DR
Before
.eslintrc.js
module.exports = {
root: true,
env: {
browser: true,
node: true,
es2021: true
},
parser: "vue-eslint-parser",
extends: [
"plugin:@typescript-eslint/recommended",
"@nuxtjs/eslint-config-typescript",
"prettier",
"plugin:prettier/recommended",
"plugin:nuxt/recommended"
],
parserOptions: {
ecmaVersion: 13,
parser: "@typescript-eslint/parser",
warnOnUnsupportedTypeScriptVersion: false
},
plugins: ["prettier"],
rules: {
semi: ["error", "never"],
quotes: ["error", "double"],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": 1,
"@typescript-eslint/no-inferrable-types": [
"warn",
{
ignoreParameters: true
}
],
"no-redeclare": "off",
"@typescript-eslint/no-redeclare": ["error"],
"@typescript-eslint/no-unused-vars": "off",
// prettierと矛盾することがあるので、offにしている
indent: ["off", 4],
"vue/no-v-html": "off",
"prettier/prettier": [
"error",
{
singleQuote: false,
semi: false,
trailingComma: "none",
printWidth: 120
}
],
"nonblock-statement-body-position": "error",
"no-irregular-whitespace": [
"error",
{
skipTemplates: true
}
],
"vue/no-multiple-template-root": "off"
}
}
.eslintignore
.nuxt
generated
proto
nuxt.config.ts
dist
.eslintrc.js
node_modules
After
nuxt.config.ts
export default defineNuxtConfig({
...,
modules: ["@nuxt/eslint"]
})
eslint.config.mjs
import prettierConfig from "eslint-plugin-prettier/recommended"
import withNuxt from "./.nuxt/eslint.config.mjs"
export default withNuxt(
{
ignores: [
"generated",
"proto",
"coverage",
"**/browserMonitoringPrd.js",
"**/browserMonitoringStg.js"
]
},
prettierConfig,
{
rules: {
"prettier/prettier": [
"error",
{
singleQuote: false,
semi: false,
trailingComma: "none",
printWidth: 120
}
]
}
}
)
.override("nuxt/javascript", {
rules: {
"no-irregular-whitespace": [
"error",
{
skipTemplates: true
}
],
// NOTE: 以下は新たに顕在化したため暫定的に無効化したルール
"no-constant-binary-expression": "off"
}
})
.override("nuxt/typescript/rules", {
rules: {
"@typescript-eslint/no-redeclare": ["error"],
"@typescript-eslint/no-inferrable-types": [
"warn",
{
ignoreParameters: true
}
],
"@typescript-eslint/no-unused-vars": "off",
// NOTE: 以下は新たに顕在化したため暫定的に無効化したルール
"@typescript-eslint/consistent-type-imports": "off",
"@typescript-eslint/no-extraneous-class": "off",
"@typescript-eslint/unified-signatures": "off"
}
})
.override("nuxt/vue/rules", {
rules: {
"vue/no-v-html": "off"
}
})
.override("nuxt/vue/single-root", {
rules: {
"vue/no-multiple-template-root": "off"
}
})
.override("nuxt/rules", {
rules: {
// NOTE: 以下は新たに顕在化したため暫定的に無効化したルール
"nuxt/prefer-import-meta": "off"
}
})
追記
コメントいただいたご指摘を受け、@nuxt/eslint
から@nuxt/eslint-config
に乗り換えた記事を投稿しましたので併せてお読みいただけますと幸いです🙇
上記記事では本記事と重複する内容は割愛していますので本記事も引き続きご覧ください🙇
準備: .eslintrc.jsの内容を確認する
このプロジェクトの.eslintrc.jsは自分が参画前かつNuxt2時代[1]に作成されたものでした。
そもそも自分自身、eslintrcの内容を精査したこともなかったので各項目が何を意味しているかも含め確認しました。
root
root: true,
プロジェクトルートの.eslintrc.jsということを示すフラグです。
env
env: {
browser: true,
node: true,
es2021: true
},
利用を許可するグローバル変数のセットを提供するものです。
TypeScriptを使っていてplugin:@typescript-eslint/recommendedをextendsしている場合は、ESLintでグローバル変数のチェックはしないためenv設定は必要ないかもしれません。
(上記記事より)
本アプリはガッチリ当てはまるので不要な記述だったかもしれません😅
parser, parserOptions
parser: "vue-eslint-parser",
// (中略)
parserOptions: {
ecmaVersion: 13,
parser: "@typescript-eslint/parser",
warnOnUnsupportedTypeScriptVersion: false
},
名前の通りですが、parser
はソースコードを読み取る際のパーサー・parserOptions
はパーサーに渡すオプションです。
本アプリはNuxtベースということでソースファイルは.vue
が中心になっているのでこれをパースするためにvue-eslint-parser
が指定されているようです。
さらにparserOptions.parser
でVueファイル内のscriptタグのパーサーを指定できます。(参考)
本アプリではTypeScriptで記述するルールになっているので@typescript-eslint/parser
が指定されているようです。
🫤.。oO(envはes2021
(12相当)を指定してるのにこっちのecmaVersion
は13なの??)
extends
extends: [
"plugin:@typescript-eslint/recommended",
"@nuxtjs/eslint-config-typescript",
"prettier",
"plugin:prettier/recommended",
"plugin:nuxt/recommended"
],
ルールの定義に有効無効や対象ファイルなど、実行するための一通りを構成してくれます。
そのためextendsに設定する一式をconfig(=構成)と呼ぶようです。
plugin:@typescript-eslint/recommended
名前の通りTypeScriptに最適化されたconfigです。ルールの追加だけでなく、ESLintのcoreルールで競合するものやTypeScriptの書き方にそぐわないものは無効化してくれているそうです。
@nuxtjs/eslint-config-typescript
これも名前の通りですが、Nuxt(Nuxt2)用に調整されたconfigのTypeScript版です。
ソースコード[2]を見るとplugin:@typescript-eslint/recommended
をextendsしてくれています。
なのでアプリのeslintrc内で改めて宣言する必要はないようです😅[3]
🫤.。oO(Nuxt2用なのか……アプリはNuxt3に上げてあるのに)
上記リンクにあるようにこれはNuxt2用なのでNuxt3では@nuxt/eslint
を使えとのことです。
prettier, plugin:prettier/recommended
-
prettier
はeslint-config-prettier
の省略形で、Prettierルールと競合するESLintルールを無効化してくれるconfigです -
plugin:prettier/recommended
はeslint-plugin-prettier
に内包されるconfigでPrettierルールをESLintルールとして適用するためのconfigです
なお、plugin:prettier/recommended
によりeslint-config-prettier
のセットアップもまとめてしてくれるようです(参考)😅
plugin:nuxt/recommended
こちらはeslint-plugin-nuxt
ライブラリに内包されているconfigです。
(前述の@nuxtjs/eslint-config-typescript
との役割分担はよく分かりませんでした…)
なお、こちらもNuxt2用のため、Nuxt3では@nuxt/eslint
を使えとのことです😅
plugins
plugins: ["prettier"],
plugins
はルールの定義のみ追加しますが、extends
と異なり各ルールの有効無効についてはrules
(後述)で設定する必要があります。
"prettier"
prettier/prettier
ルールセットをインポートするための記述と思われます。
が、"extends": ["plugin:prettier/recommended"]
することでprettier/prettier
ルールセット有効化できるので不要な記述でした😅
rules
rules: {
// (長いので割愛)
},
ルールの有効化/無効化およびルールの調整を行うセクションです。extends
およびplugins
により利用可能になったルールも調整できます。
(調整方法はルールよってまちまちなので詳細は割愛)
.eslintignore
こちらは.eslintrc.jsとは別ファイルですが、移行に関連するために記載しておきます。
.nuxt
generated
proto
nuxt.config.ts
dist
.eslintrc.js
node_modules
ファイル名の通り、eslintを適用しないファイルおよびディレクトリを列挙するファイルです。
node_modulesとソースコードをビルドした際に作成されるディレクトリおよびルートディレクトリ直下の設定ファイルが記載されています。
移行: eslint.config.mjsを記述する
上記の既存設定を基本的には踏襲しつつ、必要に応じてライブラリを切り替えたり不要な記述を除去してESLint v9用の設定を記述してきます。
有効なConfigの確認方法について
移行後(eslint.config.mjs作成後)に使える機能になりますが、これを見ながら調整をしていくので先に紹介しておきます。
上記のESLint Config Inspectorによりプロジェクトでどんなconfigが取り込まれているのか、どのルールが有効になっているかをブラウザで確認することができます。npxから実行することですぐに立ち上がります。
$ npx @eslint/config-inspector@latest
Nuxt ESLint導入
「準備」セクションで確認した通り、Nuxt3では@nuxt/eslint
を使うことが推奨されているのでこの手順に従っていきます。
- まずは沿ってライブラリを追加
$ yarn add -D eslint $ yarn add @nuxt/eslint
- Nuxtのmodulesを追加nuxt.config.ts
export default defineNuxtConfig({ ..., + modules: [ + '@nuxt/eslint' + ], })
- いったん
yarn build
またはyarn dev
を実行-
.nuxt
配下にeslint.config.mjsが作成されます(次で参照します)
-
-
ルートディレクトリ直下にeslint.config.mjsを新規作成eslint.config.mjs
import withNuxt from "./.nuxt/eslint.config.mjs" export default withNuxt()
withNuxtの引数について
withNuxt
はFlat Configのオブジェクトを可変長引数で受け取るようになっています。そのため、一般的なFlat Configで配列としてexportする設定を展開してwithNuxt
の引数にすることができます。
export default [{...}, {...}, {...}]
↓↓↓
export default withNuxt({...}, {...}, {...})
ignores設定
上記の状態ではルートディレクトリ配下の全てのファイルがeslint適用対象になってしまうので、まずは適用不要なファイルを除外する設定を入れます。
以前の形式では別ファイル(.eslintignore)に除外設定を記載していましたが、Flat Configではconfig内に記述することになります。
ignores
プロパティにファイル名やディレクトリ名あるいはパスパターンを配列として列挙することでeslintの適用対象から除外することができます。
- export default withNuxt()
+ export default withNuxt(
+ {
+ ignores: [
+ "generated",
+ "proto",
+ "coverage",
+ "**/browserMonitoringPrd.js",
+ "**/browserMonitoringStg.js"
+ ]
+ },
+ )
ESLint Config Inspectorで確認するとnode_modules
や.nuxt
, dist
といった典型的なディレクトリはすでに除外設定が入っていたので、それ以外のディレクトリについて記述を追加しました。
また、nuxt.config.tsやeslint.config.mjsについてはESLint経由でフォーマッターを効かせたかったためにあえて除外設定に入れませんでした。
globals, parserについて
以前の形式でのenv
と直接互換性のあるプロパティはFlat Configにはありません。そもそもenv
の役割は「準備」で書いた通り利用を許可するグローバル変数の定義でした。
なので許可するグローバル変数を定義するプロパティlanguageOptions.globals
を記述することになります。
また、env
に相当するグローバル変数のセットがglobals
というライブラリにまとまっているのでそれを利用することで以前の記述内容に近づけることができます。
parserについてはlanguageOptions.parser
およびlanguageOptions.parserOptions
のプロパティで以前の形式と同様に指定することができます。
ただし、ESLint Config Inspectorで確認するとすでにglobalsおよびparserは拡張子ごとに設定されているので、今回はeslint.config.mjsへの明示的な記載は無しとしました。
Shareable Config導入 (旧extends)
次に以前の形式でextends
指定していたconfigを適用させます。
以前の形式ではconfig名を文字列で指定しましたが、Flat ConfigではアプリでカスタムしたConfigオブジェクトと並列させる形でライブラリからimportしたConfigオブジェクトを列挙します。
例えば、以前の形式で"plugin:prettier/recommended"
として指定していたconfigはeslint-plugin-prettierのREADME通りeslint-plugin-prettier/recommended
からimportできるConfigオブジェクトをeslint.config.mjsに追加すればよいです。
+ import prettierConfig from "eslint-plugin-prettier/recommended"
export default withNuxt(
{
ignores: [
"generated",
"proto",
"coverage",
"**/browserMonitoringPrd.js",
"**/browserMonitoringStg.js"
]
},
+ prettierConfig,
)
ESLint Config Inspectorで確認すると、アップグレード前に導入していたNuxt関連の2つのconfigと@typescript-eslint
に相当するルールは(概ね)反映されているようだったので、Shareble Configの追加は上記のprettierのみとしました。
pluginsについて
pluginsについては以前の形式と同様にplugins
プロパティで設定することができます。
ただし、prettier/prettier
については「準備」の項でも書いたとおりShareble Config(旧extends)経由で取り込まれているため、eslint.config.mjsへの明示的な記載は無しとしました。
rules移植
独自にオンオフをカスタマイズするルールについては以前の形式と同様にrules
プロパティで設定することができます。
まずは本アプリでの以前のprettierのルールを移植します。
export default withNuxt(
{
ignores: [
"generated",
"proto",
"coverage",
"**/browserMonitoringPrd.js",
"**/browserMonitoringStg.js"
]
},
prettierConfig,
+ {
+ rules: {
+ "prettier/prettier": [
+ "error",
+ {
+ singleQuote: false,
+ semi: false,
+ trailingComma: "none",
+ printWidth: 120
+ }
+ ],
+ }
+ },
)
次にprettier以外のルール移植なのですが、Nuxt ESLintにより設定されているルールの調整方法は2種類あります。
withNuxt
の引数のrulesに追加
1. 1つ目の方法はprettierと同様の場所に追加するやり方です。
export default withNuxt(
...,
{
rules: {
"prettier/prettier": [
...,
],
+ "@typescript-eslint/no-unused-vars": "off",
}
},
)
この場合は末尾(最も優先される)独自configにルールが追加されます。
(↓↓↓ESLint Config Inspectorで確認↓↓↓)
ただしこのconfigには現状file matchingに関する設定をしていないため除外設定されていないすべてのファイルに適用されてしまいます。(Applied generally for all files
)
withNuxt
の戻り値のoverrideを利用
2. もう1つはwithNuxt
の戻り値のoverrideメソッドを利用するやり方です。
ESLint Config Inspectorで確認すると上記でルールの無効化を試みた@typescript-eslint/no-unused-vars
は元々nuxt/typescript/rules
と名付けられたconfigによって有効化されていました。
なので、このconfig名を指定してrulesをoverrideすると該当configのルールがマージされ無効化されます。
export default withNuxt(
...,
{
rules: {
"prettier/prettier": [
...,
],
- "@typescript-eslint/no-unused-vars": "off",
}
},
)
+ .override("nuxt/typescript/rules", {
+ rules: {
+ "@typescript-eslint/no-unused-vars": "off"
+ }
+ })
(↓↓↓ESLint Config Inspectorで確認↓↓↓)
こちらの場合はfile matchingに関する設定は据え置きかつ明示していないルールには影響を与えずにマージできるので今回はこちらのやり方を採用し、元々入っていたrulesでwithNuxt
利用時のデフォルトルールと差異があるものをoverrideしました
export default withNuxt(
...
)
+ .override("nuxt/javascript", {
+ rules: {
+ "no-irregular-whitespace": [
+ "error",
+ {
+ skipTemplates: true
+ }
+ ]
+ }
+ })
+ .override("nuxt/typescript/rules", {
+ rules: {
+ "@typescript-eslint/no-redeclare": ["error"],
+ "@typescript-eslint/no-inferrable-types": [
+ "warn",
+ {
+ ignoreParameters: true
+ }
+ ],
+ "@typescript-eslint/no-unused-vars": "off"
+ }
+ })
+ .override("nuxt/vue/rules", {
+ rules: {
+ "vue/no-v-html": "off"
+ }
+ })
+ .override("nuxt/vue/single-root", {
+ rules: {
+ "vue/no-multiple-template-root": "off"
+ }
+ })
なお、デフォルトでオフになっていたルールを有効化する際はconfig名やfile matchingの設定内容を基に追加するconfigを決定しました。
rules追加(暫定対応)
以上で基本的な移行作業は完了なのですが、NuxtおよびTypeScriptまわりのShareable Configを@nuxt/eslint
に切り替えた影響で今までは出ていなかった箇所でエラーが検知されてしまうようになりました。
今回はeslintのアップグレードのみを目的としソースコードに変更を加えたくなかったため、いったん該当ルールをオフにする対応を取りました。
export default withNuxt(
...
)
.override("nuxt/javascript", {
rules: {
"no-irregular-whitespace": [
...,
],
+ // NOTE: 以下は新たに顕在化したため暫定的に無効化したルール
+ "no-constant-binary-expression": "off"
}
})
.override("nuxt/typescript/rules", {
rules: {
...,
+ // NOTE: 以下は新たに顕在化したため暫定的に無効化したルール
+ "@typescript-eslint/consistent-type-imports": "off",
+ "@typescript-eslint/no-extraneous-class": "off",
+ "@typescript-eslint/unified-signatures": "off"
}
})
.override("nuxt/vue/rules", {
...
})
.override("nuxt/vue/single-root", {
...
})
+ .override("nuxt/rules", {
+ rules: {
+ // NOTE: 以下は新たに顕在化したため暫定的に無効化したルール
+ "nuxt/prefer-import-meta": "off"
+ }
+ })
また、逆にデフォルトでルールが無効化されたためにeslint-disable-next-line
を付与していた箇所でUnused eslint-disable directive
というwarningが出るようになってしまいました。
こちらについてはそもそも(それなりの箇所で)無効化してしまっていたルールであったため、今回はデフォルト設定を採用することとしました。
以上で今回の移行作業は完了です!
所感
@nuxt/eslint
に切り替えたことで基本的な設定の部分で従来の設定をかなり省略し「巨人の肩の上に立つ」ことができたように感じました。
また、ESLint Config Inspectorで網羅的にルールを確認でき、調整の大きな助けになりました。
一方でprettierに関してはlintの実行時間が長くなってしまいました。prettier/prettier
ルールの分かりづらさもあるのでESLint Stylisticへの移行も検討したいと考えています。
最後に今回の移行作業で妥協・先送りした事項を残課題として列挙しておきます。
-
@nuxt/eslint
をdependenciesに追加している- devDependenciesに置きつつmodulesに追加する方法はあるか?
- もしくはmodulesに追加せずに
@nuxt/eslint
を利用できるか?
- 一部のルール抜き差し
- 有効化されたルールの対応
- いったん暫定で
off
にした - 順次リファクタリング予定
- いったん暫定で
- 無効化されたルールの対応
-
Unused eslint-disable directive
が発生したがいったん無視した - 順次無効化マークを除去予定
-
- 有効化されたルールの対応
Discussion
コメント失礼します!
こちらについては
@nuxt/eslint
ではなく@nuxt/eslint-config
を用いる方法が適切かと思います。この場合は今までのESLintの利用方法(CIやnpm scriptsで実行する)と似た使い方が可能です。日本語としては下記記事に記載しております。合わせてご一読頂けると幸いです!
コメントありがとうございます!
ご指摘のとおり
@nuxt/eslint-config
を用いる方法が適切でしたのでそちらに切り替えました!(別記事にしました)
ご提示いただいた記事も大変勉強になりました🙇