僕たちに本当に必要だったものは@nuxt/eslint-configだった件
おつかれさまです。オウンドメディア担当の水谷です。
前回の記事で自チームで管理しているフロントエンドアプリ(Nuxtベース)に導入しているESLintをv9にアップグレードするとともに@nuxt/eslint
を導入したのですが、@nuxt/eslint-config
を用いる方法が適切とのコメントをいただいたので、切り替えてみましたという紹介です。
TL;DR
Before
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"
}
})
After
eslint.config.mjs
import { createConfigForNuxt } from "@nuxt/eslint-config/flat"
import prettierConfig from "eslint-plugin-prettier/recommended"
export default createConfigForNuxt({
dirs: {
src: ["src"]
}
})
.override("nuxt/javascript", {
rules: {
"no-irregular-whitespace": [
"error",
{
skipTemplates: true
}
],
// NOTE: 以下は新たに顕在化したため暫定的に無効化したルール
// TODO: ルールを削除するか検討
"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: 以下は新たに顕在化したため暫定的に無効化したルール
// TODO: ルールを削除するか検討
"@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: 以下は新たに顕在化したため暫定的に無効化したルール
// TODO: ルールを削除するか検討
"nuxt/prefer-import-meta": "off"
}
})
.append(
{
ignores: ["generated", "proto", "coverage", "**/browserMonitoringPrd.js", "**/browserMonitoringStg.js"]
},
prettierConfig,
{
rules: {
"prettier/prettier": [
"error",
{
singleQuote: false,
semi: false,
trailingComma: "none",
printWidth: 120
}
]
}
}
)
@nuxt/eslint-configへ切り替え
@nuxt/eslint
ではなく@nuxt/eslint-config
を用いる方法が適切かと思います。この場合は今までのESLintの利用方法(CIやnpm scriptsで実行する)と似た使い方が可能です。
確かに前回の本アプリでの対応はESLintアップグレードを目的としており、実行方法や運用は従来のままでしたので@nuxt/eslint-config
が適切そうです。紹介いただいた記事を参考にeslint.config.mjsを変更していきます。
+ import { createConfigForNuxt } from "@nuxt/eslint-config/flat"
import prettierConfig from "eslint-plugin-prettier/recommended"
- import withNuxt from "./.nuxt/eslint.config.mjs"
- export default withNuxt(
- {
- ignores: [
- ...
- ]
- },
- prettierConfig,
- {
- rules: {
- ...
- }
- }
- )
+ export default createConfigForNuxt()
.override("nuxt/javascript", {
...,
})
... // overrideは変更なし
+ .append(
+ {
+ ignores: [
+ ... // withNuxtの引数内と同じ
+ ]
+ },
+ prettierConfig,
+ {
+ rules: {
+ ... // withNuxtの引数内と同じ
+ }
+ }
+ )
ベースとなるconfigを生成する関数をライブラリで定義されているcreateConfigForNuxt
に変更します。
また、追加の独自configについては関数の引数ではなく、append
メソッドで追加することになります。(今回は最終的なconfigリストの順番に沿うようにoverrideの後に追加しました。)
overrideについてはwithNuxt
を利用していた時と同じ使用方法になります。
エラー顕在化とその原因特定について
ルールのカスタマイズだけなのでこれだけで完了!と思いきや実際にeslintを実行するとvue/multi-word-component-names
のエラーが顕在化してしまいました。。。
$ yarn lint
/Users/kurukuruz/work/my-app/src/error.vue
1:1 error Component name "error" should always be multi-word vue/multi-word-component-names
/Users/kurukuruz/work/my-app/src/pages/foo/index.vue
1:1 error Component name "index" should always be multi-word vue/multi-word-component-names
/Users/kurukuruz/work/my-app/src/pages/bar/baz/index.vue
1:1 error Component name "index" should always be multi-word vue/multi-word-component-names
切り替え前後でなぜこのような差異が発生するのか、まずはESLint Config Inspectorで有効化されているルールを確認してみました。
$ npx @eslint/config-inspector@latest
- Before(
@nuxt/eslint
を利用しているとき)
- After(
@nuxt/eslint-config
を利用し上記configにしたとき)
どちらの場合もいったん**.*.vue
全体でエラーを有効化した後、特定のパスのみ無効化しているのですが、この「特定のパス」に差異があることがわかりました。Afterの方はerror.vueなどがrootディレクトリ直下にある前提なのに対して実際にはsrc配下にあるため無効化条件に引っかからないというのが顕在化した直接原因でした。
しかし@nuxt/eslint
も内部的には@nuxt/eslint-config
を利用しているはずなのになぜ……?残念ながら@nuxt/eslint-config
の詳細なカスタマイズ仕様はドキュメント化されていない[1]のでソースコードを読んでみることにします。
ソースコード探索詳細
※記事作成時点でのソースコードのため、閲覧されている時点での最新ソースでは行番号や構成が変わっている可能性があります。
まず、無効化するためのconfig名nuxt/disables/routes
でnuxt/eslintリポジトリ内を検索すると以下のソースがconfigを生成していそうなことがわかります。
上記ファイルの前半部分を見るとNuxtESLintConfigOptions
型のオプション(をresolveしたもの)からdirs
さらにその配下のsrc
というプロパティを使ってapp.vueやerror.vueの除外パスを計算していそうなことがわかります。
次にNuxtESLintConfigOptions
型の定義を確認します。
トップレベルのプロパティとしてdirs
があり、その下にsrc
を配列で設定できることがわかります。
また、dirs
と同列に存在するfeatures
は公式ドキュメントの「Customizing the Config」に登場するので、dirs
も同様にcreateConfigForNuxt
の引数内で設定できそうなことがわかります。
この結果createConfigForNuxt
の引数内でソースディレクトリを設定すればよいことがわかりました。
srcディレクトリの指定
- export default createConfigForNuxt()
+ export default createConfigForNuxt({
+ dirs: {
+ src: ["src"]
+ }
+ })
この変更を反映して再度ESLint Config Inspectorを確認すると
無効化対象のパスがsrc/
配下に更新されました。
また、eslintを実行しても顕在化していたエラーはなくなりました。
以上で切り替え作業は完了です。
@nuxt/eslint除去
最後に、@nuxt/eslint
モジュールを使う必要がなくなったのでNuxt側の設定から除去しておきます。
export default defineNuxtConfig({
...,
- modules: ["@nuxt/eslint"]
})
依存関係からも除去しておきます。
$ yarn remove @nuxt/eslint
以上で今回の作業は全て完了です。
関連記事
ESLint Config Inspectorやルールのoverride
といった本記事で説明を割愛した部分については前回記事をご参照ください🙇
-
2024-08-10時点 ↩︎
Discussion