🐈

【ネタ】<img /> と書いてESLintに怒られたValid HTMLおじさんの話

に公開

ESLintとPrettierが喧嘩してる

最近、個人でちょっとしたプロダクトを作ろうとしている。
モックをせっせと作っていたら、ESLint に怒られた。

<img /><img> に直せ、と。

「え、そうなん?」と思いつつ直したら、
保存した瞬間、Prettierが、即座に「/」を生やした。

……え、なにこれ。

20年前、僕は Webデザインの世界にいた

怒られるまで、僕は「正しいHTMLってこうだよね」と思ってた。

<img />
<br />

……かれこれ20年間。

20年前は、
いまみたいにフレームワークもなければ、
ビルドもトランスパイルもなかった。

代わりにあったのが、

  • XHTML
  • W3C Validator
  • 「Valid」っていう緑の文字

HTMLを書いて、
CSSを書いて、
最後に Validator に投げる。

エラーが0になるまで直す。
「Valid を出すこと」に、なぜか燃えてた。

そして、有名なサイトをこっそりValidatorにかけて、
Invalidじゃねえかw
と優越感に浸ったこともある。

たぶん、同世代には心当たりがあるはず。やってたでしょ?

今思うと、かなりしょうもない。
でも当時は本気だった。

  • <img /> を忘れたらエラー
  • <br /> も忘れたらエラー
  • warning が1個でも出ると落ち着かない

だから <img /> は、
身体に染みついた書き方になった。

時代は HTML5 に進んだ

HTML5の存在はもちろん知っていた。
ただ、システムエンジニアの世界に来ていたので、
正直、フロントエンドの世界は浦島太郎。
今や職業エンジニアでもなくて、ただの経営企画のおじさんになった。

気持ち悪いのでちゃんと調べてみたら衝撃だった。

HTML5 では、

<img>
<br>

が正解らしい。

<img />
エラーではないけど、
スラッシュに意味はない。

ただ、無視されるだけ。

けんかをやめて ふたりをとめて

わたしのためにあらそわないで もうこれいじょう

今回、僕が直面した問題は、単純に言えばこういうこと。

  • ESLint は言う
    「HTML的に正しいのは <img>

  • Prettier は言う
    「Vueっぽいし <img /> でしょ」

どっちも間違ってない。
結論としては、どっちでもいい。

  • <img> でも
  • <img /> でも

動くならOK。

20年前に、他人が書いていたHTMLをあざ笑っていた僕にはわかる。

大事なのは、

ツール同士が喧嘩しないこと

人が決めると揉めるから、
ESLint と Prettier に決めさせる。

それがいちばん平和。
僕は、VS Codeのsettings.jsonをそっと修正した。

settings.json
{
  // VueはESLintが整形の最終決定権を持つ(Prettierに戻されない)
  "[vue]": {
    "editor.defaultFormatter": "dbaeumer.vscode-eslint",
    "editor.formatOnSave": true
  },
  // TS/JSはPrettierでOK
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true
  },
  // ESLint拡張の基本設定
  "eslint.useFlatConfig": true,
  "eslint.validate": [
    "javascript",
    "typescript",
    "vue"
  ],
  "eslint.format.enable": true,
  "files.eol": "\n"
}

おわり。

終わったつもりが終わってなかった(最終決着)

ぜんぶ、dbaeumer.vscode-eslintに寄せれば良いのでは…。

ということで、もう少し、修正を続けた。

pnpm add -D @stylistic/eslint-plugin
eslint.config.mjs
// eslint.config.mjs
import withNuxt from './.nuxt/eslint.config.mjs'
import stylistic from '@stylistic/eslint-plugin'

export default withNuxt(
  stylistic.configs.customize({
    semi: false,
    quotes: 'single',
    indent: 2,
    jsx: false,
  }),
)
settings.json
{
  // ESLint(コード)はESLintに任せる
  "eslint.useFlatConfig": true,
  "eslint.validate": [
    "javascript",
    "typescript",
    "vue"
  ],
  "eslint.format.enable": true,
  // --- Code (ESLint) ---
  "[vue]": {
    "editor.defaultFormatter": "dbaeumer.vscode-eslint",
    "editor.formatOnSave": true
  },
  "[typescript]": {
    "editor.defaultFormatter": "dbaeumer.vscode-eslint",
    "editor.formatOnSave": true
  },
  "[javascript]": {
    "editor.defaultFormatter": "dbaeumer.vscode-eslint",
    "editor.formatOnSave": true
  },
  // --- Docs/Config (Prettier) ---
  "[markdown]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true
  },
  "[jsonc]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "editor.formatOnSave": true
  },
  "files.eol": "\n"
}

MarkdownとJSONだけは引き続きPrettierさんにがんばってもらうことにしました。

今度こそ ー完ー 。

次回予告:<br /> も怒られるのか?(書きません)

Discussion