WHATWGのドキュメントを読んで「へぇ」となった話

アイスタイル Advent Calendar 2021 2日目を担当しますsakaguchiiです。
今年はDOMの仕様とにらめっこすることが多く、 https://whatwg.org/ のドキュメントに大変お世話になった年でした
なんとなく知ったつもりでいたけど実は全然わかっていなかったな~ということもあり、そんな今年の思い出を書こうかなと思います😊

前振り

W3C

https://www.w3.org/
Webの長期的な発展を目的とし、wwwで用いられる規格の標準化を行う団体

WHATWG

https://whatwg.org/
W3Cの指針に対して不満を表明した各ブラウザのメンバーによって立ち上げられたコミュニティ

その昔、W3Cが公開している仕様とWHATWGが公開している仕様とで一部異なる仕様があり混乱を招いていた時代がありましたが
2019年にW3CとWHATWGは、HTML標準仕様の策定について合意を結びました。

W3C and the WHATWG signed an agreement to collaborate on a single version of HTML and DOM


この合意により、HTMLとDOMの標準仕様はWHATWGが主導権を握ることになりました。

そういった歴史的な経緯があり、HTMLやDOMの仕様を読む場合は https://whatwg.org/ のドキュメントを見るようにしていました。

2021年「へえ」となったこと

① インラインスクリプトにdefer/asyncをつけても無意味事件

アットコスメでのjs非同期読み込み対応を行ったときの話ですが、deferを<script>タグにつけるとHTMLパースの処理と並行しながらjsのダウンロードを行い、HTML解析が完了した後に実行する…というという仕様を読んだ時、
「インラインスクリプトにdeferを書いたらHTML解析が完了した後に実行してくれるのでは…!?」と期待しました。

そういう考えに至ったのも、この時の作業は子テンプレートにインラインスクリプトが書かれている状況で、外部化して親テンプレにあるheadタグ内に移さなければいけないとわかっていたものの、子テンプレートに散っている処理を精査して親テンプレの影響範囲を調べて整理するというのは、正直なところ避けたい作業でした。

なのでインラインスクリプトにdeferをつけることで、HTMLパーサーを中断せず、DOM構築後に処理を実行してくれないかな~ということを期待していました……………が、結論から言うとインラインスクリプトにdefer/asyncを指定することはできませんでした😢

WHATWGのドキュメントでは下記のように記載されています

The async and defer attributes are boolean attributes that indicate how the script should be evaluated. Classic scripts may specify defer or async, but must not specify either unless the src attribute is present.

https://html.spec.whatwg.org/multipage/scripting.html

訳:クラシックスクリプトではdeferまたはasyncを指定できるが、src属性がない限りどちらも指定してはならない

私の期待もむなしく散り、その後せっせとテンプレートの使用箇所を洗い出し、外部化しdeferを付ける対応を行いました😀

② セルフクロージングしていいタグなんだっけ事件

eslint-plugin-vueを使っているNuxt.jsでのPJTにて、lintの自動修正をかけたところ

<div></div>

と置いたコードが

<div />

のように修正されました。

このとき私は「divはセルフクロージング(/>)のように書いて良かったんだっけ?」「NGだったような気もするような…?」「でもこうしないとlintでワーニングが出てしまうしな…?」と混乱しました…。

色々悩みましたが「<div />」のコードをチェックしたほうが早いかなということでW3Cのバリデーション を使用したところ、「空要素ではないHTMLタグに対してセルフクロージングを使ってはならない」というエラーが発生しました。

▼ バリデーションの結果

Self-closing syntax (/>) used on a non-void HTML element. Ignoring the slash and treating as a start tag.

さらに「空要素にどれが当てはまるんだっけ…?」となったのでWHATWGのドキュメントをみたところ、空要素(Void elements)は下記のように記載されていました
https://html.spec.whatwg.org/multipage/syntax.html#elements-2

Void elements
area, base, br, col, embed, hr, img, input, link, meta, param, source, track, wbr

この中にはdivは無いため、結論としてdivタグのセルフクロージングは許容されていない仕様となります。

ではなぜeslint-plugin-vueがdivをセルフクロージングしたかというと、eslint-plugin-vueのデフォルトで「中身が無い要素はセルフクロージングにする」という設定がされているためです。
Vue.jsは中身を持たないコンポーネントのセルフクロージングを推奨しており、その設定がdivにも影響してしまっていた…ということでした。

まとめ

  • 空要素(Void elements)ではないタグでセルフクロージングを行うとw3cエラーとなる
  • eslintは中身がないタグのセルフクロージングをdefaultで行う

Vue.jsのお作法と、HTMLのお作法が混ざってしまったことにより大混乱しましたが、改めて整理してみると確かにな、という感じでしたね。
ちなみにeslint-plugin-vueではコンポーネントはセルフクロージングを行い、HTMLタグは行わない、という設定が出来るので適宜設定するのが良さそうです😀
https://eslint.vuejs.org/rules/html-self-closing.html#options

最後に

個人的に2021年はwebvitalsのためにDOMと向き合うことが多い年でした。
英語のドキュメントを読むのはいまだに抵抗感がありますが、それでも去年よりは進んで読めるようになったと思います。
最後まで読んでいただきありがとうございました🦔

デザイン部所属 / フロントエンドエンジニア