Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

はじめに

本書は、プログラミングスクール FjordBootCamp の教材として作成されたものです。FjordBootCamp では、単に文法や機能を暗記するのではなく、「なぜそうなっているのか」を理解し、自分で調べて判断できることを重視しています。本書も同じ方針で、HTML を表面的な書き方ではなく、その歴史、仕様、実装の背景から理解することを目指します。

この本は、HTML の書き方を学ぶ本ではありません。<div> の意味やフォームの作り方を最短で覚えたい人には、もっと適した入門書があります。本書が扱うのは、それより一段奥にある問いです。なぜ HTML はこういう形をしているのか。なぜブラウザはそこまで面倒を見るのか。なぜ「変」に見える仕様が、何十年も残り続けているのか。その理由を、歴史・仕様・実装の三つの面からたどります。

HTML はしばしば軽く見られます。プログラミング言語ではない。エラーを出さない。壊れたマークアップでも何となく表示される。そうした性質だけを拾うと、HTML は「適当な技術」に見えるかもしれません。しかし実際には、HTML は理想と現実の折衷を積み重ねながら、巨大な互換性の生態系を支えてきました。本書の目的は、その歴史を笑うことではなく、筋道を理解することです。

本書の想定読者は、HTML を一度は書いたことがある人です。Rails エンジニア、フロントエンド初中級者、Web 制作の経験者、教科書的な説明では物足りない人を主な読者として想定しています。厳密な仕様書を最初から読む必要はありませんが、仕様やブラウザ実装の話に踏み込むため、完全な初心者向けのやさしい導入書ではありません。

各章では、できるだけ次の順で話を進めます。まず短い具体例を置き、次にブラウザや仕様で何が起きているかを確認し、そのうえで「なぜそうなったのか」を説明します。最後に、よくある誤解や実務上の見方に触れます。知識を増やすより、HTML を見る解像度を上げることを目指します。

本書の仕様確認の基準日は 2026 年10月6日です。特記しない限り、例は text/html として処理する現行 HTML を対象とします。XHTML を XML として配信する場合、JSX、歴史的な HTML の規則は、その都度区別します。本文断片を試すための文書全体の雛形は第4章にあります。Living Standard は更新されるため、判断が必要なときは各章の一次資料で現在の定義を確認してください。

第1部 HTML とは何だったのか

第1部の役割は、HTML を「タグの集まり」ではなく、文書共有の問題に対する解として見直すことです。ここで扱うのは、HTML の歴史的な出発点と、その設計対象です。ブラウザの補完やエラー回復の話はまだ扱いません。それらは第2部でまとめます。

第1章では、HTML がプログラミング言語ではないことを確認します。第2章では、HTML が何を解決したかったのかを CERN(欧州原子核研究機構)の文脈から見ます。第3章では、PDF やワープロ文書との現在の用途の比較を通じて、HTML の特徴を整理します。ここで土台を置いておくことで、後の章で出てくる「なぜそんなに寛容なのか」という疑問に答えやすくなります。

第1章 HTML はプログラミング言語ではない

HTML には if も for もありません。変数もなければ、計算もできない。それでも、世界中の Web を支えています。プログラミング言語にできないことだらけなのに、なぜ基盤になれたのでしょうか。

この章では、HTML を「処理を書く言語」ではなく「文書構造を記述する言語」として捉えます。

1.1 HTML は命令ではなく文書の役割を書く言語

プログラミング言語と聞くと、多くの人は条件分岐や繰り返しを思い浮かべます。if があり、for があり、値を保持する変数があり、処理の流れを自分で制御できる。JavaScript や Ruby はその意味で典型的なプログラミング言語です。

HTML は違います。HTML にあるのは、見出し、段落、リンク、画像、表、フォームのような文書の役割です。HTML が書いているのは「何を実行するか」ではなく、「この部分は何であるか」です。ここで言う「文書構造」とは、要素どうしの並びや階層だけではありません。見出し、段落、表といった意味上の役割まで含んだ構造です。

まず最小の例を見てください。

<h1>お知らせ</h1>
<p>会場が変更になりました。</p>

ここには命令がありません。あるのは「これは見出しです」「これは段落です」という構造だけです。しかもその構造は、単に箱が並んでいるという意味ではありません。「この部分は見出しとして読むべきだ」「この部分は段落として続けて読むべきだ」という意味の手がかりも含みます。HTML は画面の上で起きる処理を書くのではなく、そうした読み方まで含んだ文書の骨格を記述します。

この時点で、HTML は JavaScript や Ruby と別の種類の言語だと分かります。比較対象は「他のプログラミング言語」だけではありません。むしろ近いのは、文書に見出しや段落や脚注を付けるためのマークアップ言語です。マークアップ言語は、文章そのものに印を付けて「ここは見出し」「ここは段落」と役割を示す言語だと思えば十分です。HTML の M は Markup です。ここを見失うと、以後の章で出てくる多くの違和感が、「なぜこんな中途半端な言語なのか」という誤解に吸い込まれます。

1.2 だから HTML は CSS と JavaScript に役割を譲る

それでも実際の Web ページは、見出しや段落があるだけでは終わりません。色が付き、余白が付き、ボタンを押すと動きます。すると自然に、「結局いろいろやるなら、HTML が全部書けてもよかったのではないか」と思いたくなります。

しかし Web では、役割を分けたほうが都合がよい場面が多くありました。次の 3 つを見比べてください。

<h1 class="notice-title">お知らせ</h1>
<p id="notice-message">会場が変更になりました。</p>
.notice-title {
  color: #c00;
  margin-bottom: 0.5rem;
}
document.querySelector('#notice-message').hidden = true;

HTML が担当しているのは、「見出し」と「段落」という構造です。CSS は色や余白のような見た目を担当します。JavaScript は表示・非表示の切り替えのような振る舞いを担当します。

ただし、すべての操作に JavaScript が必要という意味ではありません。リンクの移動、フォームの送信、details の開閉は、HTML の要素が持つ標準機能です。JavaScript は、独自の処理や状態管理を加えるときに使います。上のコードでは、実行すると表示中の段落が隠れます。

この分業は、ただ習慣でそうなったわけではありません。文書の構造、見た目、振る舞いは、変わる速さも、読む主体も、壊れたときの影響範囲も違います。見出しは検索エンジンや支援技術も読みますが、赤色かどうかはそうではありません。独自のクリック処理は JavaScript が担いますが、見出しであること自体は JavaScript がなくても成立します。

つまり HTML が全部を抱えないのは、弱いからではありません。むしろ、何を自分の責務として持ち、何を他に任せるかをかなり明確に切り分けているからです。

1.3 if や for がないことは欠陥ではなく前提

ここで、よくある違和感を、逃げずに扱っておきます。HTML を初めて本格的に触ると、「どうして条件分岐も繰り返しも書けないのか」と感じます。テンプレートエンジンや JSX に慣れていると、この違和感はなおさら強くなります。

たとえば Rails の ERB では、次のように書けます。

<% if @articles.any? %>
  <ul>
    <% @articles.each do |article| %>
      <li><%= article.title %></li>
    <% end %>
  </ul>
<% end %>

この例で if や each を担当しているのは Ruby です。HTML は、その結果として出力される ul や li の構造だけを担当します。React の JSX でも条件分岐や反復は JavaScript の仕事です。ただし、その結果がサーバーで HTML 文字列になる場合と、ブラウザ内で DOM の作成・更新に使われる場合があります。後者の結果は、そのまま View Source に出るわけではありません。第8章で区別します。

この事実は重要です。現代の開発者はしばしば、テンプレート言語やフレームワークの中で HTML を見ています。そのため、「HTML が条件分岐できている」ように見えることがあります。しかし本当は、条件分岐しているのは周辺の言語です。HTML 自体は最後まで、構造を表す役割に留まっています。

だから、HTML に if や for がないのは単なる未熟さではありません。HTML は最初から、処理系ではなく文書構造の共通表現として設計されていたからです。もし HTML 自体が複雑な処理を抱えていたら、ブラウザ以外の多くの読み手にとって扱いにくい形式になっていたはずです。

1.4 HTML が Web の基盤になれたのは読む主体が多かったから

「プログラミング言語ではないのに重要」という言い方には、まだ少し飛躍があります。そこで最後に、なぜ HTML が Web の基盤になれたのかを、読む主体の広さから押さえます。

ブラウザは HTML を読んで画面を作ります。これは直感的です。しかし HTML を読むのはブラウザだけではありません。検索エンジンは見出しやリンクの構造から文書の手がかりを拾います。スクリーンリーダーのような支援技術は、ブラウザが提供する見出しやリストや表の情報を頼りに、読み上げや移動を助けます。ここでいう支援技術は、画面を読み上げたり操作を助けたりして、情報へアクセスしやすくする道具です。アーカイブツールや変換ツールも、HTML を文書の骨格として読みます。

ここで大事なのは、これらの読み手が求めているのは「どう動くか」より先に「何であるか」だという点です。ある文字列が赤いかどうかより、それが見出しなのか、段落なのか、リンクなのかのほうが重要な場面が多くあります。HTML はまさにその情報を渡すための形式でした。

補足: もちろん現代の Web ページは JavaScript に強く依存することもあります。ただし、それでも最終的には多くの場合、ブラウザや他の利用者が読める HTML の構造へ落ちます。周辺技術が進化しても、HTML の役割が消えないのはこのためです。

誤解されやすいのは、「HTML は簡単だから基盤になった」という言い方です。正確には少し違います。HTML は文書構造に責務を絞っていたから、多くの主体が共有しやすい形式になりました。条件分岐や計算のような処理を抱え込まなかったからこそ、ブラウザ以外にも読まれやすい、長寿の土台になれたのです。

1.5 実務では「 HTML だけで何でもやる」発想を疑う

この章の話は歴史や思想の説明に見えますが、実務上の判断にもつながります。HTML を構造記述の言語として見る癖が付くと、マークアップが崩れているときの違和感が変わります。

たとえば、見た目だけを合わせるために何でも div で囲っていないか、見出しにすべき箇所がただの太字テキストになっていないか、表として読むべき情報を無理に別の要素で並べていないか、という見方ができるようになります。言い換えると、「この書き方は見た目しか満たしていないのではないか」と疑う視点が持てます。逆に、HTML に本来ない責務を押しつけ始めると、後から CSS、JavaScript、検索、支援技術のどこかで痛みます。

この章の段階で「こう書けば正解」という細則を覚える必要はありません。ここで押さえておきたい判断基準は一つです。HTML は処理や見た目を全部抱える言語ではなく、文書構造を共有するための言語だということです。この前提を持つだけで、後の章で出てくる table や div や h1 の議論の見え方がかなり変わります。

参考資料

第2章 HTML は何を解決したかったのか

HTML を単なるタグの集合ではなく、CERN(欧州原子核研究機構)における情報共有の問題への答えとして捉えます。

CERN(欧州原子核研究機構)は、スイスとフランスの国境近くにある、世界最大級の素粒子物理学の研究所です。巨大な実験装置を使って、物質を形づくるいちばん小さな粒を調べています。ここには世界中から大勢の研究者が出入りし、たくさんの計算機と文書が日々やり取りされていました。

「物理学の研究所から、なぜ Web が?」と意外に思うかもしれません。でも、これは偶然ではありません。これだけ大規模で国際的な現場だったからこそ、「情報はあるのに、必要なものへたどり着けない」という切実な問題を、研究現場で抱えていたのです。Web は、その困りごとへの答えとして生まれました。次章では、その問題に対して現代の読者になじみのある PDF や Word と比較し、HTML の用途を整理します。

2.1 HTML が必要になったのは、情報がないからではなく、つながっていなかったから

1980 年代末の CERN には、研究者も文書も大量にありました。問題は、情報そのものが不足していたことではありません。むしろ逆で、情報はあるのに参照しづらく、つながっていないことが大きな痛みでした。

研究グループごとに使う計算機が違い、保存場所も違い、文書形式も違う。ある装置の説明書、別の研究チームのメモ、さらにその関連論文がそれぞれ別の場所にあり、誰が何を管理しているかも分かりにくい。新しく来た研究者にとっては、「必要な情報が存在すること」と「必要な情報へたどり着けること」が別問題でした。

Tim Berners-Lee(ティム・バーナーズ=リー)が 1989 年の提案書で問題にしたのは、まさにこの点です。提案書では、組織の中に人、装置、ソフトウェア、文書の関係が「mesh(網)」のように広がっているのに、それを横断してたどる道具が足りないと説明されています。人や装置や文書の関係が複雑に絡み合っているのに、それを横断して参照する共通の仕組みがない。だから必要だったのは、豪華な文書編集ソフトではなく、異なる環境の情報どうしを結びつけるための最小の共通基盤でした。

ここで重要なのは、出発点が「きれいなページを作りたい」ではなかったことです。必要だったのは、まず届くこと、参照できること、別の文書へ飛べることでした。HTML の性格を理解するとき、この出発点を忘れると、後の仕様の寛容さや単純さが見えなくなります。

2.2 ハイパーテキストは、文書を閉じた完成品ではなく参照の網に変えた

CERN の問題に対して、核になった発想はハイパーテキストでした。これは、文書の中から別の文書や別の項目へ飛べるようにする考え方です。紙の文書にも「参考: 第3章」や脚注はありますが、ハイパーテキストでは参照先へそのまま移動できます。要するに、文書を単体で閉じた読み物としてではなく、別の文書とつながる入り口つきの文書として扱う発想です。

最小の形で書けば、HTML のリンクはこうです。

<p>
  装置の仕様は
  <a href="detector-spec.html">こちらの文書</a>
  を参照してください。
</p>

この例で大事なのは、見た目ではありません。下線が付くか、青く表示されるかは本質ではない。重要なのは、「この文書のこの部分は、別の文書への参照である」と機械にも人間にも分かることです。

ここで前章の話がつながってきます。HTML が構造や役割を書く言語であることは、リンクにとって特に重要でした。リンクは単に文字列を飾る装飾ではなく、文書どうしを結びつける役割です。だから HTML に必要だったのは、複雑な計算より、見出し、段落、リスト、リンクのような文書の部品でした。

この時点で、HTML の「小ささ」は制約というより戦略だったと言えます。解こうとしていた問題が、最初からアプリケーションの UI 構築ではなく、文書を相互参照可能にすることだったからです。

2.3 Web は URL ・ HTTP ・ HTML の分業で成り立っている

ここまで読むと、HTML だけ見ていても全体像が分からない理由がはっきりしてきます。Web は最初から、ひとつの技術で完結するようには作られていませんでした。少なくとも次の 3 つが分業しています。

  1. URL は「何を取りに行くか」を示す
  2. HTTP は「どう取りに行くか」を示す
  3. HTML は「取りに行った文書がどんな構造か」を示す

同じ参照でも、役割を混ぜると理解が崩れます。まず URL だけを取り出すと、見えているのは参照先の識別子です。

https://example.org/reports/beam-status.html

この文字列だけでは、そこに何があるかは分かっても、「その中身がどういう構造か」は分かりません。次に HTTP を考えると、ここで扱っているのは取得のやり方です。たとえば概念的には、クライアントは「この場所の文書をください」と要求し、サーバーは本文を返します。

GET /reports/beam-status.html HTTP/1.1
Host: example.org

補足: ここでは現在よく見る形で示しています。Web 黎明期の HTTP はもっと素朴で、バージョン表記も Host: 行もなく、GET /... の一行だけでした(いわゆる HTTP/0.9)。役割を説明するために、ここでは現代的な書き方を借りています。

ここでも、まだ返ってきた本文が見出しなのか、リンクなのか、本文なのかは分かりません。そこで初めて HTML が必要になります。たとえば次のリンクを見てください。

<a href="https://example.org/reports/beam-status.html">
  現在の運転状況
</a>

この一行の中には、三つの役割が重なっています。https://example.org/reports/beam-status.html という識別先は URL の仕事です。そこへ取りに行く通信の取り決めは HTTP の仕事です。そして、「この文字列は別文書への参照である」という構造を表しているのが HTML の仕事です。

HTML 単体では Web になりません。しかし逆に、URL と HTTP だけでも Web になりません。取得した中身がただのバイト列のままでは、人も機械も「どこが見出しで、どこがリンクで、どこが本文か」を共有できないからです。HTML が解決したかったのは、まさにこの「取得した文書を、参照可能な構造として共有すること」でした。

この分業を押さえると、「HTML は何を解決したかったのか」という問いに、少し精密に答えられます。HTML は単独で全部を解決する技術ではなく、URL と HTTP と組み合わさって、リンク可能な文書空間を成立させる役割を引き受けていました。

URL は場所、HTTP は取得方法、HTML は中身の構造という 3 つの役割を並べた図
図 2-1 URL・HTTP・HTML は、それぞれ別の問いに答える。

2.4 初期の HTML が小さかったのは、問題設定が小さかったから

現代の HTML 仕様書は巨大です。フォーム、埋め込み、スクリプト、パーサー、エラー回復、アクセシビリティ関連まで含めると、とても「小さな文書形式」には見えません。すると、「そんなに大きい技術が、最初はなぜこんなに単純だったのか」という疑問が出ます。

答えは単純で、最初に解こうとしていた問題がもっと限定されていたからです。初期の HTML で中心だったのは、見出し、段落、リスト、リンクのような文書の基本部品でした。研究者どうしが文書を行き来し、読めればよい。そこでは、今日のような複雑なアプリケーション UI や高機能フォームまではまだ主眼ではありません。

ここで誤解を潰しておきます。初期の HTML が小さかったからといって、「あとから場当たり的に巨大化しただけだ」と片づけるのは雑です。もちろん歴史の積み重ねで仕様は膨らみました。しかし出発点を見ると、HTML は最初から万能環境を目指していたのではなく、共有文書のための最小基盤として始まっていたことが分かります。

この視点は、後の章でも役に立ちます。なぜブラウザが不完全な入力を読めるようにするのか。なぜ古い要素が簡単には消えないのか。なぜ仕様書はパース規則も詳しく定め、フォームやナビゲーションなどの振る舞いも扱います。これらはどれも、「まず文書を読めるようにする」という原点から外れていません。

2.5 実務では、 HTML を単独技術として理解しないほうが見通しがよくなる

この章の内容は歴史の話ですが、実務にもそのままつながります。HTML を単独の文法としてだけ見ると、リンク、フォーム送信、埋め込み、ルーティングの話がばらばらに見えます。しかし、URL・HTTP・HTML の分業として見ると、責務の境界がかなり整理されます。

たとえば Rails アプリでも、リンクを置く、フォームを送る、レスポンスとして HTML を返す、という流れは日常的です。link_to で URL を組み立て、コントローラが HTTP リクエストを受け、ビューが HTML を返す。このとき HTML がやっているのは、サーバー処理そのものではなく、「返ってきた文書のどこが何であるか」を示すことです。逆に、HTML に本来ない責務まで押しつけて理解しようとすると、ルーティング、通信、構造の境目が曖昧になります。

この章で押さえておきたい判断基準は、HTML を文書の側の技術として見ることです。単独の万能技術として見るのではなく、URL と HTTP と並ぶ 1 つの役割として見る。その見方を持つと、次章で HTML と PDF や Word を比べるときも、「何が高機能か」ではなく「何が問題設定に合っていたか」で比べられるようになります。

参考資料

第3章 なぜ Web は HTML を選んだのか

帳票を配るなら PDF、文書を共同編集するならワープロ、リンクをたどって読むページなら HTML。同じ文章を運ぶ形式でも、守りたいものが違います。

ここでは PDF や Word を、現在の読者に分かりやすい比較対象として使います。1989 年の Web 提案、1990 年の最初の実装に対して、PDF の登場は 1993 年です。当時の開発者がこの三つを候補に並べて HTML を選んだ、という史実の再現ではありません。

3.1 版面を保つか、読む環境へ合わせるか

PDF は、ページ番号、改ページ、図版の位置などを固定して配る用途に向いています。印刷する申請書なら、同じ欄が同じ位置にあることに価値があります。

HTML は、見出しや段落の役割を伝え、文字の折り返しや配置を受け手の表示環境へ委ねます。画面幅や文字サイズが変わっても、文章を順に読み、リンク先へ進めるようにする。その代わり、どの環境でも同じ位置で改ページすることは中心の目標ではありません。

PDF にもリンクや構造情報を持たせられます。違いはリンクの有無だけではなく、固定したページを優先するか、文書の構造をもとに表示を組み立てるかにあります。

3.2 編集の作業物と公開する文書

Word のようなワープロは、一つの文書を書き、修正し、コメントや変更履歴を交わす作業に向いています。HTML でも編集アプリを作れますが、HTML 自体は編集手順や承認の流れを定める形式ではありません。

一方、Web では URL で文書を指し、HTTP で取得し、HTML で内容の構造とリンクを伝えます。ワープロ文書を Web で配布することもできますが、それだけでこの役割分担の代わりになるわけではありません。

たとえば、装置の運転記録から測定結果へ進めるページは、次のように書けます。

<h1>運転記録</h1>
<p>今日の測定結果は <a href="beam-status.html">運転状況</a>を参照してください。</p>

文章が何行に折り返されても、「運転状況」が別文書への参照であることは変わりません。第2章の CERN の課題に対して、こうした構造を異なる計算機で共有できることが重要でした。

3.3 形式を選ぶときに守りたいもの

実務では、形式の優劣より、受け手が何をするかから選びます。

目的選択肢の例確認する点
決まった版面を印刷するPDF改ページ、用紙サイズ、文字の再現
原稿を共同で編集するWord などの編集環境変更履歴、共同編集、受け手の環境
公開してリンクで読ませるHTML見出し構造、移動、画面幅や入力方法への対応

申請一覧を HTML で提供し、提出用の帳票を PDF で出力する設計も自然です。一つの形式にすべての役割を押し込む必要はありません。

HTML が Web の基盤になった背景には、接続された文書を環境差を越えて共有するという問題設定があります。この性格を押さえると、受け手のブラウザが文書を読み取って DOM を作る過程も、単なる表示の裏方以上のものとして見えてきます。次章では、その過程をソースと並べて観察します。

参考資料

第2部 ブラウザはなぜそこまで面倒を見るのか

第2部の役割は、HTML が「書かれた文字列のまま動く」のではなく、ブラウザによる補完と回復のうえで成立していることを示すことです。前の部で見た「HTML が文書共有のための技術だった」という前提を受けて、ここではブラウザ側の責任の重さを見ます。

この部では、html head body の補完、tbody の挿入、p 要素の暗黙の終了、パースエラーの扱い、そして View Source と DevTools の違いを扱います。仕様の細かい設計思想そのものは第3部で掘り下げるため、この部ではまず「何が起きるか」を中心に理解します。

第4章 ブラウザは HTML を完成させる

書いた覚えのない <html> や <body> が、開発者ツールにはちゃんといる。自分は <p> を 1 つ書いただけなのに、です。これは誰が、いつ足したのでしょうか。

この章では、HTML ソースがそのまま完成品ではなく、ブラウザがパース規則に従って組み立てられた結果として DOM ができることを見ます。

4.1 ソースコードと完成した文書の不一致

HTML を書いていると、つい「自分が書いた文字列がそのまま表示されている」と考えがちです。しかしブラウザが扱っているのは、受け取った文字列そのものではありません。ブラウザはその文字列を読み取り、規則に従って文書構造へ変換し、その結果として DOM を作ります。

この前提を外すと、あとで出てくる tbody や p の自動終了がすべて「謎の親切」に見えてしまいます。実際には、HTML は最初から、入力文字列を読みながら文書を組み立てる形式として設計されています。ソースは材料であり、ブラウザが扱う完成形そのものではありません。

ここでいう DOM は、ブラウザが内部で持つ文書構造です。英語では Document Object Model と呼ばれ、HTML を要素どうしの関係として扱うための形です。開発者ツールで見えているのは、受信した元の文字列ではなく、この構造化された結果です。つまり、HTML では「書いたもの」と「ブラウザが扱うもの」のあいだに、解釈の層が 1 枚入ります。 文字列が、そのままノードの木へ変換されているわけではなく、途中で「この要素は文書のどこに属するか」を判断する段階が入るのです。

4.2 title と p だけでも成立する文書外枠

最小の例で見てみます。

<!DOCTYPE html>
<meta charset="utf-8">
<title>sample</title>
<p>Hello</p>

このソースには、html も head も body も書かれていません。それでもブラウザは、これを壊れた断片として投げ捨てるのではなく、文書として読める形へ組み立てます。おおまかには、次のような構造として扱われます。

<html>
  <head>
    <meta charset="utf-8">
    <title>sample</title>
  </head>
  <body>
    <p>Hello</p>
  </body>
</html>

この順序では、パーサーは head を作って meta と title を収め、p の開始タグで body の処理へ進みます。これはタグ省略の条件と読み取り規則による結果です。上の表示は要素の関係を示しており、文書型ノードは省略しています。最小文書では html タグごと省略したため言語の指定もありません。validator が lang を勧める警告を出すことと、タグ省略の適合性は別です。実際の文書では、後述の雛形のように html に言語を指定します。

「title はどこに書いても head へ移動する」という意味ではありません。入力順やパーサーの状態が違えば結果も変わります。また、コンテンツモデルに違反した要素が常に正しい位置へ移されるわけでもありません。第7章で、この区別を確認します。

html/head/body を書いていないソースから、ブラウザがそれらを補完した DOM の木を組み立てる流れの図
図 4-1 ソースは材料。ブラウザが補って文書の木を組み立てる。

4.3 補完が文書の前提になる理由

この挙動を見ると、「ブラウザが開発者を甘やかしている」と言いたくなるかもしれません。たしかに、他のもっと厳格な形式に慣れていると、入力不足を埋めてくれる振る舞いは甘く見えます。

しかし HTML の文脈では、先にあったのは厳格さではなく、読めることを優先する問題設定でした。前章で見たように、Web が必要としていたのは、異なる環境にある人が同じ文書へ辿りつき、読めることです。その前提に立つと、文書の外枠が少し省略されていても、利用可能な文書として解釈するほうが自然です。

ここでのポイントは、「何でも好きに補ってよい」ではないことです。ここで見た補完は仕様なしの裁量ではなく、どの要素がどこへ入るか、どの時点で文書の外枠を作るかといった規則に従って行われます。だからブラウザごとに気分で別々の木ができるのではなく、同じ HTML に対しておおむね同じ DOM を作れるのです。

この意味でブラウザは、著者の代わりに文章を書く共同著者ではありません。著者が渡した材料を、読める文書構造へ落とし込む実装です。過度に擬人化しないほうが、このあと出てくる仕様の話も理解しやすくなります。

4.4 DevTools が見ているもの

実務で混乱しやすいのは、ここからです。ソースに body を書いていないのに、DevTools を開くと body が見える。すると「ブラウザが勝手に書き換えた」と感じます。

ですが、DevTools が見せているのは、受信した元の文字列ではなく、解釈後の DOM です。つまり、見えている対象が最初から違います。元のソースと DevTools の内容が一致しないこと自体は、異常ではありません。むしろ HTML では、それが普通に起こります。

この違いを 1 度でも正しく掴むと、後の章でかなり楽になります。table の中に tbody が現れることも、閉じていない p が DOM 上では閉じていることも、「ブラウザが勝手に直した」の一言で片づけずに済むからです。見ているものが文字列なのか、補完後の文書構造なのかを分けて考えられるようになります。

上の最小文書を UTF-8 の sample.html として保存して開き、View Source と DevTools の Elements パネルを比較してください。DOCTYPE は互換モードを避けるため、meta は文字コードを宣言するために入れています。

第5〜7章で本文断片を試すときは、次の文書の p を断片に置き換えます。日本語を含むファイルは UTF-8 で保存してください。validator にもこの文書全体を渡します。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>HTML の確認</title>
</head>
<body>
  <p>ここを確認したい本文断片に置き換える</p>
</body>
</html>

実務の調査でも、この切り分けは有効です。レスポンス本文を確認したいなら元の HTML を見るべきですし、CSS や JavaScript が相手にしている対象を確認したいなら DOM を見るべきです。同じ画面を見ていても、何を観察しているかで答えが変わります。

ここではまだ View Source と DevTools の違いを本格的には扱いません。その比較は第8章の役割です。この章ではまず、「ブラウザは受け取った文字列をそのまま見せているのではない」という土台だけ押さえれば十分です。

4.5 「ブラウザが勝手にやった」で終わる調査の危うさ

この章の話は、実務でもかなり役立ちます。Rails でテンプレートを書いていると、最終的にブラウザへ届くのは HTML 文字列です。そしてブラウザは、その文字列をそのまま画面へ貼り付けるのではなく、文書として解釈して DOM を組み立てます。

そのため、画面で起きたことを調べるときに、テンプレートの見た目だけで判断すると外しやすくなります。たとえば CSS セレクタが思ったように効かない、JavaScript で取れたノード構造が想定と違う、といった場面では、まず「ブラウザが最終的にどんな DOM を作ったか」を見る必要があります。

ここで重要なのは、ブラウザを責めることではありません。見るべき対象を切り分けることです。テンプレートの文字列を点検したいのか、補完後の DOM を点検したいのか。この区別ができるだけで、HTML まわりの調査はかなり整理されます。

次章では、表の tbody を使い、文字列のパースと DOM API による構築を比べます。

参考資料

第5章 tbody はなぜ生まれるのか

ソースには table と tr しかないのに、DevTools では間に tbody がある。その結果、table > tr という CSS が当たりません。この現象を説明する鍵は、「どうやって DOM を作ったか」です。

5.1 タグを省略しても要素はできる

次の断片を、第4章の確認用文書の body に入れます。

<table>
  <tr><td>A</td></tr>
</table>

HTML パーサーがこの文字列を読むと、要素の関係は次のようになります。

<table>
  <tbody>
    <tr><td>A</td></tr>
  </tbody>
</table>

これは不正な入力を修正した例ではありません。この位置では tbody の開始タグと終了タグを省略できます。タグをソースに書かないことと、DOM に要素が存在しないことは別です。 パーサーには、表の中で tr の開始タグを受け取ると tbody を挿入して処理を続ける規則があります。

tbody タグを省略した HTML 文字列をパースすると、table と tr の間に tbody 要素ができる図
図 5-1 この HTML 文字列のパースでは、tbody が挿入される。

tbody は表の本体行をまとめる行グループです。見出し側の行グループには thead、末尾側には tfoot があります。行を区分したいときに役立つ構造ですが、「すべての表で tr の親は必ず tbody」と一般化すると、次の例を説明できません。

5.2 DOM API は同じ補完をしない

DevTools のコンソールで、HTML 文字列を使わずに表を作ってみます。

const table = document.createElement('table');
const row = document.createElement('tr');
const cell = document.createElement('td');
cell.textContent = 'A';
row.append(cell);
table.append(row);
document.body.append(table);
console.log(table.firstElementChild.tagName); // "TR"

この場合、append は指定した親子関係でノードを追加し、tbody を自動挿入しません。table のコンテンツモデルも、tbody がない場合には直下の tr を許しています。したがって、この木が tbody を持たないこと自体は不適合ではありません。

一方、同じ表を HTML 文字列から作れば、HTML パーサーの規則が使われます。

const parsedTable = document.createElement('table');
parsedTable.innerHTML = '<tr><td>A</td></tr>';
console.log(parsedTable.firstElementChild.tagName); // "TBODY"

要素を直接追加したのか、文字列をパースしたのか。その違いが DOM に現れます。フレームワークが作った表を調べるときも、ソースの見た目から親子関係を決めつけず、実際の DOM を確認する必要があります。

5.3 CSS は実際の親子関係を選ぶ

最初の HTML 文字列からできた表に対して、次の指定は一致しません。

table > tr { background: #eee; }

> は直下の子を選ぶ記号です。この表の tr の親は tbody なので、本体行を選ぶなら次のように書けます。

table > tbody > tr { background: #eee; }

table tr でも行を選べますが、意味は同じではありません。こちらは thead や tfoot の行、さらにセルの中に入れ子にした表の行にも届きます。「本体行だけ」なのか「子孫にあるすべての行」なのかで使い分けます。DOM API の例の表なら、最初の table > tr が一致します。

5.4 行を扱いたいときは表の API を使う

HTML 文字列から作った最初の表なら、次の結果になります。

const sourceTable = document.querySelector('table');
console.log(sourceTable.children[0].tagName); // "TBODY"
console.log(sourceTable.rows.length);        // 1
console.log(sourceTable.tBodies[0].rows.length); // 1

children は直接の子要素を返します。rows は、その表の直下と thead・tbody・tfoot に属する行を集める表専用の API です。セル内に入れ子にした別の表の行までは含めません。特定の本体行グループだけを扱うなら tBodies[0].rows を使えますが、DOM API で作った例には tbody がないので、同じ式は使えません。

表を調べるときは、まず DOM がどう作られたかを確認し、次に「直接の子」「本体の行」「表全体の行」のどれが必要かを決めます。tbody を明示して書くと構造を読み取りやすくなりますが、挿入の理由はあくまで HTML のパース規則です。

次章では p の終了タグへ移り、正しい省略とエラー回復を比べます。

参考資料

第6章 p タグはなぜ勝手に閉じるのか

</p> を書いていないのに、DevTools では段落が終わっている。これは閉じ忘れの救済とは限りません。HTML には、終了タグを正しく省略できる条件があります。

6.1 段落の内容とタグ省略は別の規則

p 要素の内容は phrasing content(文章の中身になるテキストや要素)です。たとえばテキスト、em、a、img などを含められます。div はこの分類に入らないので、p の子にすることはできません。

ただし、「内容にできない要素が来れば、パーサーが必ず p を閉じる」という一般則があるわけではありません。著者が何を入れられるかというコンテンツモデルと、パーサーが各開始タグをどう処理するかは別々に規定されています。

6.2 div の前では終了タグを省略できる

第4章の確認用文書の body に、次の断片を入れます。

<p>前置き
<div>本文</div>

p の直後が div になる位置では、p の終了タグを省略できます。この入力は、その省略条件に適合します。要素の関係を終了タグ付きで表すと、次のようになります。

<p>前置き</p>
<div>本文</div>

ソースの改行や字下げで親子関係が決まるわけではありません。パーサーはこの div の開始タグを受け取ると、開いている p を閉じてから div を作ります。ここでは p と div は兄弟であり、div が p の中から「移動した」のではありません。

p の終了タグを省略した適合例から、p と div が兄弟として並ぶ DOM を作る図
図 6-1 div の前で p の終了タグを省略しても、段落の境界は決まる。

6.3 最後に終了タグを足すと別の入力になる

では、全体を p で囲むつもりで、末尾に終了タグを足すとどうなるでしょうか。

<p>前置き<div>本文</div></p>

この入力では、div の前で p はすでに閉じています。最後の </p> に対応する開いた p はありません。これはパースエラーです。パーサーはこの場面で p を作って閉じるため、結果は次のようになります。

<p>前置き</p>
<div>本文</div>
<p></p>

最後の空の p が、エラー回復の結果です。validator は余った終了タグを指摘します。「終了タグを足せば必ず正しくなる」とは限りません。段落と別のまとまりに分ける意図なら、p の終了タグは div より前に書きます。

<p>前置き</p>
<div>本文</div>

6.4 省略できても、境界を明示する価値がある

p の終了タグ省略は正当な構文です。しかしテンプレートの分岐や部品の組み合わせが入ると、何が直後に来るかを見失いやすくなります。終了タグを明示すると、段落の範囲をソースだけでも確認しやすくなります。

調査では、まず終了タグの省略条件に合うかを確認し、次に DOM に余分な要素ができていないかを見ます。正しい省略とエラー回復を分けると、「ブラウザが勝手に閉じた」という同じ見た目から、別々の原因を取り出せます。第7章では、さらにコンテンツモデルへの不適合を加えて整理します。

参考資料

第7章 壊れた HTML をどう扱うべきか

表示できた HTML は、正しい HTML でしょうか。逆に、ソースにない要素が DOM にあれば、入力が壊れていたのでしょうか。どちらも、それだけでは決まりません。

7.1 三つの問いを分ける

第5章の tbody タグ省略、第6章の p 終了タグ省略は、条件を満たせば正しい書き方です。タグと DOM の要素が一対一で見えないことは、エラーの証拠にはなりません。

HTML を調べるときは、次の三つを分けます。

問い調べるもの
著者が書いてよい内容か要素のコンテンツモデル、属性の値、タグ省略の条件
読み取り中にパースエラーになるかHTML パーサーの規則
実際にどんな木になったかDOM。スクリプトによる変更も含む

パースエラーは不適合な入力の一種ですが、すべての不適合がパースエラーになるわけではありません。パーサーは、著者向けの規則をすべて検査して正しい構造へ直す道具ではないのです。

7.2 同じ条件で三つの断片を比べる

第4章の確認用文書の body を、次の断片で一つずつ置き換えてください。Nu Html Checker には断片だけでなく、DOCTYPE や title を含む文書全体を渡します。

まず、li の終了タグを省略した例です。

<ul><li>項目 A<div>補足</div></ul>

li は div を内容にできます。また、この位置では li の終了タグを省略できます。したがって、この入れ子と省略は適合します。DOM でも div は li の子です。

次は、余った終了タグのある例です。

<p>前置き<div>本文</div></p>

これは第6章で見たパースエラーです。DOM には末尾に空の p ができます。入力の問題を指摘する validator と、回復結果を表示する DevTools の両方で原因を確認できます。

最後に、ul の直下へ div を置きます。

<ul><div>補足</div></ul>

ul の内容として許されるのは li と、script や template などのスクリプトを支援する要素です。div は許されません。しかしこの断片のパースでは、div は ul の子として残ります。ブラウザが li を作って包み直すわけではありません。

本文断片適合性の検査で確認する点DOM の要素関係
li の終了タグ省略この省略と入れ子に問題はないul → li → div
余った </p>対応する開いた p がないp、div、空の p
ul 直下の divul のコンテンツモデルに違反ul → div のまま

この表はエラーメッセージの逐語的な再現ではありません。表示文言や警告は検査ツールの版によって変わります。注目するのは、どの規則への指摘なのかです。

7.3 回復は閲覧を続けるための規則

HTML には、入力に問題があってもパースを続ける規則があります。余った終了タグをどう扱うか、表の不適切な位置にあるテキストをどこへ置くかまで、実装者向けに定められています。ブラウザが著者の意図を推測して、その場で最善の木を選ぶわけではありません。

この性質は、作者が更新を止めた文書も読み続ける Web で役立ちます。ただし、閲覧を続けられることは、文書の意味や操作性が正しい保証ではありません。ul 直下の div のように、不適合な関係がそのまま DOM に残ることもあります。

XML では整形式の違反が致命的なエラーになりますが、HTML はそれとは違う処理規則を持ちます。HTML で許される省略を、XML の閉じ忘れと同じ基準で判定してはいけません。配信形式による違いは第14章で扱います。

7.4 validator と DevTools を組み合わせる

実務では、まず生成された文書全体を validator へ渡し、著者向け規則への違反を確認します。次に DevTools で DOM を見て、意図した親子関係かを確かめます。表示がずれているなら、その DOM に対する CSS の指定も追います。

検査で問題が出なかったとしても、文章の意味、キーボード操作、代替テキストの適切さまでは保証されません。逆に、画面が期待どおりでも、余った終了タグを放置してよい根拠にはなりません。

「入力として適合するか」と「ブラウザが何を作ったか」を別々に調べる。この手順が、HTML の寛容さに頼りきらずに開発するための足場になります。

参考資料

第8章 View Source と DevTools はなぜ違うのか

View Source と DevTools が同じ HTML を別の形で見せているのではなく、最初から別の対象を見せているのだと整理します。

8.1 同じ文書でも観察している層が違う

View Source が見せるのは、ネットワークから受け取った元の HTML 文字列です。いわば、著者やサーバーがブラウザへ渡した入力そのものです。

一方で DevTools の Elements パネルが見せるのは、ブラウザがその入力をパースし、必要な補完や回復を行ったあとに扱っている DOM です。Elements パネルは、ブラウザがいま相手にしている要素の木を表示する画面だと思えば十分です。ここで見えているのは、文字列そのものではなく、ブラウザ内部で組み立てられた文書構造です。

この 2 つは、同じページに由来していても観察対象が違います。だから一致しないこと自体は異常ではありません。むしろ、第4章から第7章までで見てきた補完や暗黙終了や回復がある以上、違いが見えるほうが自然です。

8.2 tbody と p が差を目に見える形へ変える

もっとも分かりやすいのは、第5章と第6章で扱った例です。

<table>
  <tr><td>A</td></tr>
</table>
<p>前置き
<div>本文</div>

View Source では、この入力はほぼそのまま見えます。tbody は書かれておらず、p の終了タグも省略されています。この例の省略は適合する書き方です。

しかし DevTools では違います。表には tbody が立ち上がり、div の手前で p が閉じられた形の DOM が見えます。ここで起きているのは、ブラウザが勝手に別の文書を作っていることではありません。元の入力を HTML のパース規則に従って読み取った結果が、DOM として現れています。

この差を知らないと、「ソースを書き換えられた」「DevTools が嘘を表示している」といった誤解が起きます。実際には、View Source は入力を、DevTools は解釈結果を見せています。両者の不一致は、ブラウザが不安定だからではなく、役割が違うから起きます。

8.3 View Source は著者の入力を調べる道具である

では、View Source はいつ使うのでしょうか。役割は明快です。著者が何を書いたのか、サーバーが何を返したのか、元の入力を確認したいときに使います。

たとえば、テンプレートエンジンがどんな HTML を出力したのか、レスポンスに含まれていた属性や要素は何か、整形前の改行やコメントがどう入っていたのか、といったことは View Source のほうが向いています。これは「ブラウザがどう理解したか」ではなく、「そもそも何が渡されたか」を見るための道具です。

ERB をサーバーで評価した場合、View Source には生成された HTML が見え、ERB 自体は見えません。React の JSX はもう一段区別が必要です。サーバーで HTML を生成して送ればその内容が見えますが、ブラウザで createRoot などを使って作る DOM は、最初の HTML に含まれるとは限りません。View Source に空の容器しかなくても、Elements パネルにはスクリプトで追加された内容が現れます。

第7章で触れた validator との相性がよいのもこちらです。validator が検査するのは入力としての HTML なので、確認したい対象は回復前の文字列です。著者の入力に問題があるのかを詰めるなら、まず見るべきなのは View Source 側です。

8.4 DevTools はブラウザの現実を調べる道具である

一方で、CSS が効かない、JavaScript のセレクタが当たらない、思っていた場所に要素がない、といった実務上の問題を調べるときは DevTools が必要です。なぜなら、ブラウザが実際に描画し、スクリプトから参照しているのは DOM だからです。

たとえば table > tr が当たらない理由は、ソースに tbody がないからではなく、DOM では tr の親が tbody だからです。p の途中までを 1 つの段落だと思っていたのにスタイルの適用範囲がずれるのも、DOM 上では途中で暗黙終了しているからです。こうした問題は、入力を見ても最後までは分かりません。ブラウザが最終的にどんな構造を扱っているかを見ないと解けません。

さらに、現代のページでは JavaScript が DOM を書き換えることも珍しくありません。すると DevTools に見えているのは、パースと回復の結果だけでなく、スクリプト実行後の状態でもあります。View Source は最初に受け取った HTML を見せ続けますが、DevTools は「いまブラウザが相手にしている文書構造」を見せます。この違いを押さえると、「ソースにはない要素が見える」「属性値が違って見える」といった現象も整理しやすくなります。

ここで大切なのは、DevTools が「より正しい」わけではないことです。DevTools が見せているのは、ブラウザにとっての現実です。著者の入力を検証したいのか、ブラウザの現実を調べたいのかで、使う道具が変わります。

8.5 「どちらを見るべきか」は問いで決まる

実務で混乱しやすいのは、ソースと DOM を同じものだと思って調査を始めることです。そうすると、「ソースにはあるのに見つからない」「ソースにはないのに DOM にはある」という現象に毎回ひっかかります。

判断基準は単純です。何が送られてきたのか を知りたいなら View Source、ブラウザはいま何を相手にしているのか を知りたいなら DevTools を見る。この切り分けができると、第2部で見てきた多くの現象が、単なるブラウザの癖ではなく、観察層の違いとして整理できます。

ここまでで、HTML ではソースと完成形が一致しないことが珍しくないと分かりました。次の第3部では、その差がどんな設計思想から生まれているのかを、空要素やコメントや属性の省略といった個別の論点から見ていきます。

参考資料

第3部 HTML の設計思想

第3部の役割は、第2部で見たブラウザの挙動を、個別の仕様設計として読み解くことです。ここでは「空要素」「コメント」「属性値の省略」「仕様書の読者」という、地味ですが HTML の思想がよく表れる題材を選びます。

前の部との違いは、補完や回復の現象を見るだけでなく、その文法や文書設計の理由に踏み込む点です。次の第4部では、HTML が理想と現実のあいだでどのように変わってきたかを扱うため、この部はその前提となる「仕様そのものの考え方」を担います。

第9章 img はなぜ閉じタグを持たないのか

内容を囲む p や div と違って、img には終了タグを書けません。p では条件付きで終了タグを省略できますが、img は別の分類です。書き忘れても怒られないどころか、</img> と書くほうが間違いです。なぜ画像だけ、特別扱いなのでしょうか。

この章では、img に閉じタグがないのは書き忘れを許しているからではなく、最初から子を持たない要素として設計されているからだと見ます。

9.1 img は内容を包む要素ではない

p や div を見ると、開始タグと終了タグのあいだに内容が入るのが HTML の基本形に見えます。その感覚のまま img を見ると、「なぜここだけ閉じタグがないのか」と不思議に見えます。

しかし img は、文章や子要素を内側に抱える要素ではありません。文書の中に画像という外部資源を埋め込む場所を示す要素です。必要なのは、どの画像を参照するか、代替テキストをどう与えるかといった属性であって、開始タグと終了タグのあいだに入る本文ではありません。

ここで重要なのは、img が「中身を書かなくてもよい要素」なのではないことです。最初から子を持てない要素として定義されているので、閉じタグが省略されているのではなく、そもそも持たないのです。

9.2 空要素という区別

HTML には、img のほかにも br hr meta link input のように、子を持たない要素があります。これらは空要素として扱われます。

<p>前置き<br>補足</p>
<img src="photo.jpg" alt="港の写真">

この例で br は改行位置を、img は画像の埋め込み位置を表しています。どちらも、あいだに文章や別要素を抱えて意味を作る要素ではありません。だから </br> や </img> のような終わり方を期待するより先に、「この要素は内容を囲む型なのか、位置だけを示す型なのか」を見分ける必要があります。

第1章で、HTML は処理を書く言語ではなく文書構造を記述する言語だと見ました。その観点から読むと、空要素は「ここに何かがある」という構造上の印を置く要素です。文を開いて閉じる箱ではなく、文書の中に特定の役割を持つ点を打つ要素だと考えたほうが実態に近いです。

ここで見えてくるのは、HTML が「タグごとに見た目の書き方が違う言語」なのではなく、「要素ごとに持てる内容を分類している言語」だということです。閉じタグの有無は、その分類の結果として表に出ているにすぎません。

9.3 <img /> は HTML では何を意味するのか

ここでよく混乱するのが、<img /> という書き方です。XML や XHTML に慣れていると、自己終了記法に見えます。そのため、「HTML でも末尾の / が要素を閉じている」と理解したくなります。

しかし HTML では、本質はそこではありません。img が閉じタグを持たないのは、末尾の / があるからではなく、img 自体が空要素だからです。つまり img の終わりを決めているのは見た目の記号ではなく、要素の定義です。

この違いを見失うと、「では <div /> も同じように閉じられるのか」と考えてしまいます。HTML ではそうはなりません。

<div />
<p>本文</p>

これを「div がここで閉じた」と読むと分かりやすそうですが、HTML ではそうした自己終了にはなりません。div は空要素ではないので、/ が付いていても、通常要素として扱われます。子を持つ通常要素と、最初から子を持てない空要素は、見た目が少し似ていても文法上は別物です。/ が魔法の閉じ記号なのではなく、空要素にたまたま付いていても害が出にくいだけだ、と理解したほうが正確です。

補足: 1993 年の www-talk で、Marc Andreessen は画像を文書内に埋め込む IMG タグを提案しました。現在の img のすべての設計理由が、その一通で決まったわけではありません。提案と現在の仕様を分けて読むことが大切です。

もう 1 つ: 「<div /> の / は無視される」と書きましたが、これには例外があります。HTML の中に埋め込んだ SVG や MathML(外来要素) の中では、<rect /> のような自己終了が本当に効きます。これらは XML 由来の文法を持つためです。つまり / が無意味なのは「HTML の通常要素では」という条件付きで、同じ 1 文字が、文脈によって効いたり効かなかったりします。

9.4 終了タグがないことは「ゆるさ」ではない

img に閉じタグがないと聞くと、HTML のゆるさの例に見えがちです。しかし実際には逆です。何でもありだから閉じタグがないのではなく、子を持たない要素を最初から区別しているから、ここでは閉じタグが不要になります。

この視点は、第2部で見てきた HTML の挙動にもつながります。HTML は入力に寛容な場面がある一方で、内部では要素ごとの役割や置ける内容をかなりはっきり区別しています。img が閉じタグを持たないのも、その区別の一部です。ゆるい書式の偶然ではなく、コンテンツモデル(要素ごとに「中に何を置けるか」を定めたルール)を持つ文法の結果として読むべきです。

実務でも、この理解は無駄ではありません。たとえばテンプレートを読んでいるときに、img を p や div と同じ感覚で眺めると、「なぜここは閉じていないのか」という枝葉に引っ張られます。先に「これは内容を包む要素ではない」と分かっていれば、見るべき関心は閉じ方ではなく、src や alt など、その要素に本当に必要な情報へ向きます。

ここまでで、閉じタグの有無は書き方の癖ではなく、要素がどんな内容を持てるかという設計の結果だと分かりました。次章では、その視点をさらに地味な題材へ移し、コメントでさえ好き勝手に書けるわけではないことを見ます。

9.5 同じスラッシュでも処理系を確認する

処理系空の div空要素 img
HTML(text/html)<div></div>。<div /> は自己終了しない終了タグを書かない
XML としての XHTML<div /> で空要素として閉じられるXML の規則で <img … /> または終了タグを使う
React の JSX<div /> で内容なしを表せる<img … /> のように閉じる必要がある

JSX はブラウザの HTML パーサーへ直接渡す構文ではありません。また HTML では、引用符なしの属性値に続く / にも注意が必要です。

<img alt="港" src=photo.jpg/>
<img alt="港" src="photo.jpg" />

最初の src の値は photo.jpg/ です。二つ目の値は photo.jpg で、末尾のスラッシュは値の外にあります。属性値を引用符で囲むと、この境界を読み取りやすくなります。

参考資料

第10章 コメントにも仕様がある

<!-- ... --> の中は、ブラウザが読み飛ばすただのメモ欄——そう思っていませんか。ところが、この一見どうでもいい場所にも、ちゃんと文法があります。書き方しだいでは、コメントのつもりが文書を壊すことすらあります。

この章では、HTML コメントが「ブラウザが読まない自由欄」ではなく、どこで始まりどこで終わるかを文法で決められた構文だと見ます。

10.1 コメントは無制限のメモ欄ではない

HTML コメントは <!-- で始まり --> で終わります。見た目だけを追うと、そのあいだは何を書いても無視される、ただのメモ欄に見えます。

しかしパーサーの立場では、話はそれほど気楽ではありません。どこからどこまでがコメントなのかを判断できなければ、その後ろに続く要素やテキストを正しく読み取れないからです。コメントもまた、HTML の入力の一部として処理される構文です。

ここで重要なのは、「表示されない」ことと「文法を持たない」ことは別だという点です。画面に出ないからといって、好き勝手に壊してよい領域にはなりません。コメントの境界が曖昧になれば、文書全体の解釈まで崩れます。

10.2 終端と衝突する並び

現行 HTML では、次のコメント内の -- は禁止されていません。

<!-- 前半 -- 後半 -->

一方、コメントを入れ子にすることはできません。

<!-- 外側
  <!-- 内側 -->
  <p>非表示にしたかった本文</p>
-->

パーサーは内側の --> でコメントを終えます。そのため p はコメントの外に出て、本文として扱われます。著者向けの構文規則も、コメントの内容に <!-- や --> などを含めることを許していません。長い HTML 断片をコメントで囲むなら、すでにコメントを含んでいないか確認が必要です。

「-- が禁止」という説明は、XML のコメントの規則と混ざりやすいところです。XHTML を XML として扱う場合にはその制約がありますが、現行の text/html に同じ禁止を持ち込んではいけません。

歴史の例: Internet Explorer には、<!--[if IE]> … <![endif]--> のような条件付きコメントがありました。通常の HTML コメントに見える範囲を、IE が条件に応じて解釈する独自機能です。IE10 の標準モードではサポートされなくなりました。現在の HTML の標準機能として使うものではありません。

10.3 SGML の名残がいまも文法に影を落とす

では、なぜコメントにこうした細かな制約があるのでしょうか。理由の 1 つは、HTML が SGML の影響を受けて出発したからです。SGML は、文書の構造をどう記述するかを決める、HTML より前の世代の大きなマークアップの枠組みだと考えれば十分です。

初期の HTML は、いまの Living Standard だけで完結して生まれたわけではありません。文書構造を記述する仕組みとして、SGML の考え方を引き継いでいました。コメントの扱いにも、その時代の名残が残っています。現在の HTML は独自に整理され直されていますが、コメントを完全な自由欄にしなかった背景には、そうした歴史があります。

ここで大切なのは、「古い名残だから変だ」と切ってしまわないことです。むしろ、文書構造を壊さずに読み続けるためには、コメントのような脇役にさえ境界の規則が必要だった、と読むほうがこの本の筋に合います。前章の空要素と同じで、見た目が素朴でも、その裏には分類や制約があります。

10.4 実務ではコメントを文法の外に置かない

実務では、コメントを「どうせ表示されない領域」と見なして複雑な断片を詰め込みたくなることがあります。とくにテンプレートの途中で一時的に要素を消したり、長い説明を埋め込んだりすると、コメントは安全な退避場所に見えます。

しかし、ここまで見てきたように、コメントは文法の外側にあるわけではありません。HTML の中で処理される構文の 1 つです。だから、コメント内部の書き方しだいでは、パーサーや validator が気にする対象になります。画面がたまたま崩れなくても、入力としては parse error を含んでいることがあります。

この章で覚えるべきなのは、「コメントの細則一覧」ではありません。HTML は、見出しや段落や画像のような目立つ要素だけでなく、コメントのような地味な場所にも境界規則を持っている、という見方です。そう読むと、HTML は雑に許している言語ではなく、許すところと許さないところを選んで設計されている言語だと分かります。

ここまでで、表示されないコメントでさえ文法の一部だと分かりました。次章では、その延長として、属性値を省略できる場面があるのも無秩序な近道ではなく、条件つきの設計だということを見ます。

参考資料

第11章 属性値はなぜ省略できるのか

disabled だけでもボタンは無効になります。それなら disabled="false" と書けば有効になるでしょうか。HTML の属性では、構文と意味を分けて考える必要があります。

11.1 属性名だけなら値は空文字列

HTML には、値を明記せず属性名だけを書く構文があります。これは値が空文字列であることを表します。ブール属性専用の構文ではなく、たとえば title だけなら空の title 属性になります。ただし、その空文字列が適合する値か、どんな意味になるかは各属性の規則で決まります。

ブール属性は、存在すれば真、なければ偽です。

<input type="checkbox" checked>
<input type="checkbox" checked="">
<input type="checkbox" checked="checked">

この三つは、いずれも初期状態でチェックの付いたチェックボックスです。checked は適用対象も決まっているため、type を省略したテキスト入力に付ける例では説明できません。

11.2 false という文字列は偽にならない

次のボタンは無効になります。

<button disabled="false">送信</button>

disabled が存在するからです。ただし、この false はブール属性の適合する値でもありません。空文字列または属性名に一致する値を使います。無効化を解除するには disabled 属性を取り除きます。JavaScript なら button.disabled = false とすることで、その属性が除去されます。

一方、contenteditable="false" は編集不可を表します。contenteditable は列挙属性であり、ブール属性ではありません。「false と書いても効かない」という知識を、すべての属性へ広げてはいけません。

11.3 引用符の省略は値の省略とは違う

次の二つでは title の値は同じです。日本語であること自体は、引用符を必要とする理由ではありません。

<a href="/about" title=会社概要ページ>会社概要</a>
<a href="/about" title="会社概要ページ">会社概要</a>

しかし ASCII スペースを入れると結果が変わります。

<a href="/about" title=会社 概要>会社概要</a>
<a href="/about" title="会社 概要">会社概要</a>

最初の title は「会社」までで終わり、「概要」は別の属性名として読み取られます。意図した値を渡せるのは二つ目です。

引用符なしの属性値は空にできず、ASCII 空白、二重引用符、一重引用符、等号、小なり記号、大なり記号、バッククォートを含められません。全角スペースと ASCII スペースも構文上は同じ扱いではありません。境界を毎回判断するより、値を引用符で囲むほうが保守しやすくなります。

11.4 HTML とテンプレートの規則を混ぜない

XML としての XHTML では属性値を引用符で囲みます。React の JSX では disabled={false} の波括弧内は JavaScript の値です。HTML 文字列の disabled="false" とは違います。

省略可能という仕様上の条件と、チームで省略するかどうかは別の判断です。整形器で引用符を統一しても、HTML の規則に反しません。読める表記を選びつつ、トラブル時には属性が存在するか、その値が何か、属性の種類は何かを順に確認します。

次章では、このように異なる種類の規則を HTML 仕様書でどう探すかを見ます。

参考資料

第12章 HTML 仕様書は誰のためにあるのか

HTML 仕様書を「著者向けの正しい書き方集」とだけ見るのではなく、ブラウザ実装者、著者、標準化に関わる人たちがそれぞれ別の目的で読む文書だと整理します。

12.1 仕様書の中心読者はブラウザ実装者である

HTML 仕様書を初めて開くと、多くの人はまず長さに驚きます。要素一覧や属性一覧だけなら、あそこまで巨大になる必要はなさそうに見えるからです。実際、1 ページにまとめた版はブラウザで開くだけでも重く感じるほどの分量で、読みやすさのために章ごとに分割した複数ページ版がわざわざ別に用意されているほどです。

しかし仕様書の中心には、著者への助言だけでなく、ブラウザが同じ入力をどう処理するかをそろえるための規則があります。パーサーがどのトークンをどう読むか、壊れた入力をどう回復するか、どの DOM を組み立てるか。第2部で見てきた細かな挙動の多くは、まさにそのために書かれています。

たとえば、tbody をいつ立ち上げるのか、p をどこで暗黙に閉じるのか、コメントや属性値の境界をどこで切るのかは、どれも「雰囲気でだいたい同じ」にして済む話ではありません。1 文字ぶん解釈がずれるだけで、DOM も CSS も JavaScript もずれていきます。だから仕様書は、そうした細部を実装者どうしでそろえるために長くなります。

つまり HTML 仕様書は、「人間が HTML を学ぶための本」より先に、「実装がばらつかないようにするための規約書」です。長いのは、話が回りくどいからではなく、ブラウザ同士で結果をそろえるために細部まで決める必要があるからです。

12.2 著者にとっての仕様書は最終根拠である

では、著者にとって仕様書は不要なのでしょうか。そうではありません。ただし、使いどころが違います。

著者が日常的に読む入口としては、MDN のような整理された解説のほうが向いています。どの要素をいつ使うか、どういう属性があるか、まずはそちらのほうが読みやすいからです。仕様書は最初から学習教材として最適化されているわけではありません。

それでも仕様書が重要なのは、通説やブログ記事が食い違ったときに、最後の根拠へ戻れるからです。h1 は 1 個だけなのか、tbody はなぜ DOM に現れるのか、コメントや属性の省略はどこまで許されるのか。そうした問いに対して、「誰かがそう言っていた」ではなく、一次情報へ戻るための場所が仕様書です。

たとえば、要素の基本的な用途を知りたいだけなら MDN で十分なことが多い。MDN は、Web 技術を開発者向けに整理して解説している資料集だと思えば十分です。一方で、「この終了タグは本当に省略できるのか」「この入力でパーサーはどの DOM を作るのか」といった境界の話になると、最終的には仕様書の定義へ戻る必要があります。入口と根拠を分けて考えるほうが、仕様書との付き合い方として現実的です。

この意味で、著者にとっての仕様書は毎日通読する本ではなく、判断が割れたときに戻る基準点だと言えます。

12.3 標準化する側にとっての仕様書は合意の文書である

仕様書には、実装者向けの規則と著者向けの根拠だけでなく、標準化の合意を残す役割もあります。どの要素をどう定義するか、どこまで後方互換性を守るか、何を obsolete(仕様が「もう使わない」と定めた状態)と見なすか。そうした判断は、単なる豆知識ではなく、Web 全体の振る舞いを左右します。

ここで仕様書は、「理想的な文法を書く紙」では終わりません。すでに広く使われている実装や既存ページとの整合も背負います。だから仕様書は、美しい理論だけでなく、現実の Web を壊さないための条件まで書き込んでいきます。HTML 仕様書が大きくなりがちなのは、そのためでもあります。

この視点を持つと、第2部で見たエラー回復や、第3部で見てきた細かな文法が、単なる技術仕様ではなく「実装と互換性の合意」でもあると分かります。仕様書は、ブラウザと著者と既存の Web のあいだで、どこまでを守るかを書いた文書でもあるのです。

12.4 「読みにくい仕様書」には役割の違いが混ざっている

HTML 仕様書が読みにくく感じられるのは自然です。1 つの文書に、実装規則、著者向けの定義、標準化の判断材料が同居しているからです。読み手が自分の目的を決めずに開くと、必要な部分より先に大量の細則へぶつかります。

だから実務では、最初から仕様書だけで学ぼうとしないほうがよい。日常の理解には整理された解説を使い、判断が割れたら仕様書へ戻る。仕様書を読むときは、「自分はいま著者として確認したいのか、実装の根拠を知りたいのか」を意識する。そのほうが、仕様書の長さに飲まれにくくなります。

ここまでで、HTML 仕様書は誰か 1 種類の読者だけのためにあるのではなく、複数の立場を同時に相手にする文書だと分かりました。次の第4部では、その仕様が理想だけでは進めず、既存の Web とどう折り合ってきたのかを、廃止要素や XHTML の配信上の課題から見ていきます。

12.5 p の規則を三か所から読む

たとえば「p の中に div を書けるか」と「div の前で終了タグを省略できるか」は、仕様書でも参照箇所が違います。

  1. p 要素の定義の Content model を見ると、内容が phrasing content と分かります。ここで div がその分類に入るかを確認します。
  2. 同じ定義の Tag omission を読むと、div などが直後に来る場合に p の終了タグを省略できることが分かります。
  3. 実際の DOM を説明したいなら、“in body” 挿入モードの div 開始タグと p 終了タグの処理を読みます。第6章の余った </p> は、こちらのエラー回復で説明できます。

先に問いを決めてから、その規則へ進む。コンテンツモデルだけを読んでパーサーの動作まで推測しないことが、仕様書を使うときの要点です。

参考資料

第4部 理想と現実のあいだ

第4部の役割は、HTML の歴史を「きれいな進化」ではなく、現実との折衝として捉えることです。ここでは廃止要素、XHTML、描画モード、標準化された互換性の例外を通して、Web がどれだけ壊せない基盤になっているかを見ます。

第3部で見た設計思想は、現実の Web ではいつもそのまま通るわけではありません。そこでこの部では、理想が押し切れなかった例を集めます。次の第5部では HTML を巡る議論を扱うため、この部は「単純な善悪で語れない」という感覚を育てる位置づけです。

第13章 廃止されたタグたちは何を残したのか

文字を点滅させる <blink>、文字を流す <marquee>、文字を大きくする <font>。いまでは笑い話のようなこれらのタグも、かつては真剣に使われていました。現在は新しい文書で使うべきではありませんが、font・center・marquee には互換性のための処理が残っています。「著者向けには廃止」と「ブラウザ実装から削除」は別です。

この章では、blink marquee center font のような廃止要素を、単なる黒歴史として笑うのではなく、当時の Web が何を欲していて、標準が何を学んだのかの痕跡として見ます。

13.1 font と center は著者の即時需要に応えた

いまの感覚で見ると、font や center は「見た目を HTML に埋め込んだ悪い例」に見えます。たしかに、文書構造と見た目の分離という観点からは後退でした。

しかし当時の著者にとって重要だったのは、理念より先に「文字を大きくしたい」「中央寄せしたい」をすぐ実現できることでした。CSS がまだ十分に普及していない時代に、見た目を制御する需要だけが先に消えるわけではありません。font や center は、その即時需要にまっすぐ応えた要素でした。

当時は、見た目を分離する理想よりも、ページをいま見栄えよくしたい圧力のほうが直接的でした。しかも、著者ごとに別々の回避策へ流れるくらいなら、要素として用意されている近道のほうが使われやすい。font や center は、そうした現場の圧力を吸収する役目も果たしていました。

ここで見えてくるのは、要素がまずかったというだけではありません。標準が、著者の需要に対して十分な手段をまだ提供し切れていなかった、という事情です。だからこそ、見た目を直接いじる近道が広まりました。

blink や marquee は、さらに分かりやすい例です。文字を点滅させたい、流したい。その要求自体は、いま見ると派手で落ち着きがないかもしれませんが、当時の Web では「目立たせたい」という切実な需要でもありました。

<blink>新着情報</blink>
<marquee>セール開催中</marquee>

問題は、こうした要素が文書の意味を深めるより先に、表示効果そのものを要素化してしまったことです。しかも blink や marquee は、標準化された文書構造の一部というより、ブラウザや実装都合の強い拡張として広まりました。ここでは「需要があったこと」と「その解決が長持ちする設計だったこと」は別です。

それでも重要なのは、こうした要素を笑って終わらないことです。標準の外で広まる拡張は、「そんな需要はない」の証拠ではなく、「標準がまだ受け止めきれていない需要がある」の証拠でもあります。

13.3 廃止された要素が残したのは、需要を無視しないという教訓である

廃止されたタグたちが残した教訓は 2 つあります。1 つは、見た目や効果をそのまま要素へ閉じ込めると、文書構造が濁り、長期的な保守や再利用が難しくなること。もう 1 つは、著者の需要を「低レベルだから」と切り捨てても、その需要自体は消えず、独自拡張や回避策として別の場所から噴き出すことです。

この 2 つを同時に見ないと、歴史を後知恵で裁いてしまいます。font や center を deprecated にする判断は筋が通っていますが、それは「最初から不要だった」ことを意味しません。むしろ、「その需要をもっと持続可能な形で受け止める必要があった」と読むほうが、現在の標準化にもつながります。

この意味で、廃止された要素は失敗の残骸というより、標準がどこで著者の需要と衝突したかを示す痕跡です。

13.4 古い要素を笑わないことが第4部の入口になる

実務で古い HTML を見ると、font や center を見つけて反射的に「時代遅れだ」と言いたくなります。その判断自体は現在の推奨として間違っていません。ただし、この章で大切なのは、現在の推奨と、当時その要素が広まった理由を分けて考えることです。

ここで分けたいのは、「便利だったこと」と「長持ちする設計だったこと」です。font や marquee は前者としては機能しましたが、後者としては問題を残しました。この区別を入れないと、過去を必要以上に擁護するか、逆に必要以上に嘲笑するかのどちらかに傾きます。

古い要素を笑うだけでは、Web がどうやって現在の設計へ寄ってきたのかは見えません。重要なのは、「なぜそれが必要とされたのか」「なぜ新しい文書には使わないのか」の両方を見ることです。そうすると、HTML の歴史は単純な進歩史ではなく、理想と現場の要求を何度もすり合わせてきた歴史だと分かります。

ここまでで、廃止された要素たちは単なる恥ずかしい過去ではなく、標準が著者の需要をどう受け止めるべきかを残したと見えてきました。次章では、その逆側で「もっと厳密に、もっときれいに」を押し進めた XHTML が、なぜ Web 全体の現実とは噛み合いきらなかったのかを見ます。

参考資料

第14章 XHTML はなぜ Web の主流にならなかったのか

タグを閉じ忘れたら文書を処理し続けない。XML の整形式の規則で HTML を扱う XHTML は、入力を厳密に管理したい場面には魅力的でした。しかし公開 Web を一斉にその方式へ移すことはできませんでした。

ここで区別したいのは、XHTML 1.x の記法が使われたこと、XML として配信されたこと、XHTML 2.0 の開発が終了したことです。この三つをまとめて「XHTML は誰も使わなかった」とするのは不正確です。

14.1 XML の厳密さは何を検査するのか

XML は、開始タグと終了タグの対応や入れ子などの整形式を要求します。たとえば次の断片を文書の本文に置き、後ろに p の終了タグを補わなければ、XML では整形式の違反になります。

<p>前置き
<div>本文</div>

HTML では、第6章で見たように div の前で p の終了タグを省略できるので、この本文断片は適合する書き方です。同じ記号列でも、処理系によって許される構文が違います。

ただし XML の整形式検査と、XHTML の要素の意味・配置の検査は別です。<p><div>本文</div></p> はタグが対応しているため整形式ですが、p の内容として div を許すわけではありません。「XML として読めた」だけでマークアップ全体が適合すると判断してはいけません。

p の終了タグ省略を HTML は受け入れるが、XML では整形式の違反になる対比
図 14-1 HTML の正しい省略も、XML の規則ではエラーになる。

14.2 記法だけでは XML の処理に切り替わらない

サーバーは Content-Type という HTTP ヘッダーで文書の種類を伝えます。text/html として配信すれば HTML パーサー、application/xhtml+xml として配信すれば XML パーサーで処理されます。

XHTML 風に小文字のタグを使い、属性を引用符で囲み、空要素に / を付けても、text/html で送れば HTML として処理されます。XHTML 1.0 には、互換性のガイドラインに従った文書を text/html で提供する扱いもありました。記法の利用と XML 配信は同じではなかったのです。

XML 配信では、整形式の違反が致命的なエラーとなり、通常の処理を続けられません。入力を一元管理できるシステムでは早期検出に役立ちますが、複数のテンプレートや外部の断片を組み合わせる公開サイトでは、ページを読めなくする影響も考慮する必要がありました。

14.3 当時のブラウザ対応という障壁

厳密さへの好みだけでは説明できない事情もあります。W3C の当時の XHTML FAQ は、Internet Explorer が application/xhtml+xml をサポートしないことを挙げています。これは当時の配信判断に関する史料であり、現在のブラウザ対応表ではありません。

主要な閲覧環境へ届ける必要があれば、著者は記法を XHTML に寄せながら text/html で配信する選択を取り得ます。その場合、XML の処理へ移行したわけではありません。XHTML が主流にならなかった理由を、著者が厳密さを嫌ったからだけで説明することはできないのです。

14.4 XHTML 2.0 の終了と HTML の継続

W3C は 2009 年、XHTML 2 Working Group の任期を更新せず、HTML5 に資源を振り向ける方針を示しました。これは XHTML 1.x の既存文書がその日に無効になったという意味ではありません。

HTML5 では、現実のブラウザが扱う入力と互換性を踏まえ、HTML のパースやエラー回復を詳しく規定する方向が進みました。著者向けの適合性を捨てたのではなく、不適合な入力にも共通の処理規則を与えたのです。

実務で確認すべきなのは、文書の見た目の記法だけでなく、何の種類として届き、どのパーサーが読むかです。末尾のスラッシュだけで処理系を判断せず、レスポンスの Content-Type を確認すると、この歴史が現在の調査にもつながります。

参考資料

第15章 互換性はなぜ重要なのか

後方互換性を「古いものを切れない保守性」とだけ見るのではなく、公開済みの文書を読めるまま保つための Web の責務として見ます。後方互換性は、新しい実装になっても古いページや古い書き方をなるべく読めるままにする性質だと思えば十分です。

15.1 公開されたページは作者の手を離れて生き続ける

多くのソフトウェアでは、新しい版に合わせて古い利用者が移行することがある程度前提になります。アプリケーションなら、提供者が更新のタイミングをある程度コントロールできます。

しかし Web ページは違います。いったん公開された文書は、作者が更新を止めても、リンクされ、検索され、保存され、あとからも読まれ続けます。しかも、そのページの作者にもう連絡できないことも珍しくありません。

この条件では、ブラウザの新しい版が「古い書き方は切り捨てる」と決めた瞬間に、誰にも直せないページが壊れます。だから Web における互換性は、古い実装への情けではなく、公開された文書空間を維持するための責務になります。

しかも失われるのは、個人の趣味ページだけではありません。昔の技術記事、研究資料、企業の告知、行政情報のように、あとから参照され続ける文書も含まれます。互換性の問題は、開発者の都合だけではなく、社会に残った記録をどこまで読み続けられるかという問題でもあります。

15.2 Quirks Mode は不格好でも必要だった

この責務がよく見えるのが Quirks Mode です。これは、古いページを当時に近い癖で読むための互換モードだと思えば十分です。理想だけを言えば、すべてのページを同じ標準モードで処理したほうが単純です。けれど現実には、過去のブラウザ依存の挙動を前提に作られたページが大量にありました。

そこでブラウザは、条件によって標準モードと互換モードを切り替え、古いページでは過去の挙動へ寄せる道を残しました。標準モードは現在の標準に沿って読むモード、互換モードは古い挙動へ寄せて読むモードです。Quirks Mode は、美しい設計の勝利ではありません。むしろ、理想だけでは既存ページを守れなかったからこそ生まれた緩衝材です。

ここで重要なのは、Quirks Mode を単なる恥ずかしい互換レイヤーとして笑わないことです。あれは「標準に従わなかったページを甘やかした」だけではなく、「すでに公開され、現に読まれている文書を急に壊さない」ための現実的な責任の取り方でもありました。

15.3 互換性は実装の負債ではあるが、 Web の価値も支えている

もちろん互換性にはコストがあります。実装は複雑になり、仕様書も長くなり、設計をきれいに保ちにくくなります。第12章で見た仕様書の巨大さの一部も、この負債と無関係ではありません。

それでも Web が長く使える基盤でいられたのは、その負債を引き受けてきたからです。昔のリンクが急に全部無効にならない。古い記事が、数年後もだいたい読める。企業サイトも個人ブログも研究ページも、完全ではなくても残り続ける。この性質は、単なる技術選好ではなく、Web の価値そのものです。

XHTML が広がりにくかったのも、ここにつながります。入力の厳密さを優先して古いページが壊れやすくなるなら、公開文書空間としての Web の価値が下がってしまう。だから Web は、きれいさだけではなく、壊さない責任を優先する方向へ寄りました。

15.4 「古いものを残す」と「何も変えられない」は別である

ここで誤解しやすいのは、互換性を重視するなら何も改善できないのではないか、という見方です。実際にはそうではありません。Web は、新しい要素や API を足しながら、古いページもなるべく壊さないよう進んできました。

大事なのは、変更のしかたです。既存ページの意味を壊す形で一気に切り替えるのではなく、新しい仕組みを足し、古い仕組みは読めるまま残す。この慎重さが、Web を遅く見せることもあります。しかしその遅さは、公開済みの文書と利用者を置き去りにしないための遅さでもあります。

ここまでで、互換性は古いものへの未練ではなく、公開された文書を基盤として引き受ける責務だと分かりました。次章では、その責務がさらに進んで、仕様上あまり好ましくないバグさえ簡単には消せなくなる理由を見ます。

15.5 DOCTYPE の有無を観察する

次の内容を UTF-8 で保存し、先頭の DOCTYPE があるファイルと、その行だけを削ったファイルを比べます。

<!DOCTYPE html>
<html lang="ja">
<meta charset="utf-8">
<title>描画モードの確認</title>
<style>
  #box { width: 100px; padding: 10px; border: 5px solid; }
</style>
<div id="box">箱</div>
</html>

コンソールで次の二つを調べます。

document.compatMode
document.querySelector('#box').getBoundingClientRect().width

この例では、DOCTYPE ありの標準モードは "CSS1Compat"、箱の外幅は 130px です。DOCTYPE なしの Quirks Mode は "BackCompat"、外幅は 100px になります。独自のリセット CSS や box-sizing の指定を追加せずに比較してください。

Limited-Quirks Mode も compatMode は "CSS1Compat" を返すので、この値だけで三つのモードすべてを区別できるわけではありません。新しい文書には <!DOCTYPE html> を書き、古い描画を調べるときは DOCTYPE も確認する、という使い分けです。

参考資料

第16章 バグはなぜ残るのか

「変な挙動だから直せばよい」と思っても、その挙動を前提にしたページがあると簡単には変えられません。ただし、互換性のために残った挙動を、すべて未修正のバグと呼ぶのも不正確です。document.all は、その境界をよく示します。

16.1 存在するのに false になるオブジェクト

ブラウザのページ上で、次の式を DevTools のコンソールへ一つずつ入力してみます。Node.js など、document のない環境の例ではありません。

Boolean(document.all)          // false
typeof document.all           // "undefined"
document.all == null          // true
document.all === undefined    // false

通常のオブジェクトなら真偽値への変換は true になるはずです。しかし document.all は、要素のコレクションを返すにもかかわらず、このような特別な振る舞いをします。

最後の厳密等価比較が false であることからも、単に undefined が返っているのではないと分かります。値がないのではなく、特定の判定で「ない」ように見せる例外なのです。

16.2 古いブラウザ判定と共存するための例外

document.all は Internet Explorer 由来の古い API です。かつては、この API の有無をブラウザ判定にも使うコードがありました。

if (document.all) {
  // 古い IE 向けの処理を選ぶつもりの分岐
}

互換性のため API を実装すると、今度はこのような分岐で IE 向け処理へ入る問題が生じます。API を使うコードとの互換性と、判定に使うコードとの互換性が衝突するのです。

現在の HTML Standard は document.all のコレクションに特別な内部スロット [[IsHTMLDDA]] を持たせます。ECMAScript の Web ブラウザ向け追加規定は、それに対する真偽値変換、typeof、緩い等価比較を例外扱いします。JavaScript と HTML の両方にまたがって、互換動作が明文化されています。

16.3 未修正の不具合とは区別する

この挙動は奇妙ですが、現在では仕様に書かれた互換性の例外です。「現行仕様に違反するバグを放置している」という説明では合いません。既存の利用が、何を仕様どおりとするかにも影響した例として読む必要があります。

一方、すべての不具合がこのように固定されるわけではありません。変更の影響を調べて修正できるものもあれば、使われていない機能を削除できる場合もあります。残っている理由を判断するには、現在の規定と、変更の議論や実装記録を照合します。

16.4 新しいコードは必要な機能を調べる

新しく書くコードで document.all を使ったブラウザ判定をまねる必要はありません。要素を探すなら getElementById や querySelector を使い、機能の利用可否を判定するなら必要な API 自体を調べます。

「このブラウザなら動くだろう」という名前による推測を減らすことは、新しい互換性の負担を増やさないためにも有効です。ただし API の存在確認だけで、すべての操作やアクセシビリティが保証されるわけではありません。第26章では、標準機能を採用する際の確認へ進みます。

参考資料

第5部 HTML を巡る議論

第5部の役割は、HTML にまつわる有名な言説を、歴史と仕様の両方から整理することです。h1、table、div、b のように、実務で何度も蒸し返される話題を取り上げます。

ここで重要なのは、単に「都市伝説は間違いです」と言って終わらないことです。なぜその言説が広まり、どの条件では部分的に正しかったのかを見ます。そうすることで、次の第6部で扱う標準化や実装者の意思決定も理解しやすくなります。

第17章 h1 は 1 個だけなのか

「ページに h1 は 1 個だけ」という有名な言い方を、そのまま正誤で裁くのではなく、編集上の助言、仕様上の事実、HTML5 時代の議論に分けて整理します。

17.1 「1 個だけ」は仕様の絶対規則ではない

まず事実から押さえると、HTML では h1 が複数あるだけでただちに不正になるわけではありません。見出し要素は文書の構造を示すものであり、ページ全体に 1 個だけという単純な個数制限で定義されているわけではないからです。

この点を外すと、h1 を 2 個書いた瞬間に仕様違反だ、という雑な理解になります。しかし仕様上の話としては、問題は個数そのものより、見出しの構造がどうなっているかです。

つまり「h1 は 1 個だけ」は、少なくとも仕様の絶対規則として読むと粗い。ここを最初に切り分けておかないと、あとの議論が全部ずれます。

17.2 それでも「1 個だけ」が広まった理由

では、なぜこの言い方がここまで広まったのでしょうか。理由の 1 つは、編集や執筆の感覚としては、それなりに筋が通っていたからです。

1 本の文書に大見出しが 1 つあり、その下に h2 h3 と階層が続く、という構成は分かりやすい。教える側にとっても、「まず h1 はページの主題」と言ったほうが説明しやすい。だから h1 を 1 個にしておく実務上の助言は、一定の整理として長く使われました。

しかもこの助言は、人間の読者だけでなく、テンプレート設計やレビューでも扱いやすい既定値になりやすい。ページの主題はここ という合意を 1 つ置くほうが、チーム内でも説明しやすいからです。だから標語としては生き残りやすかった。

ここで大事なのは、それが仕様の強制と同じではないことです。編集上の助言としては有効でも、HTML の絶対規則だと言い切ると雑になる。h1 を 1 個にするのが常に悪いわけでも、複数あるのが常に悪いわけでもありません。

17.3 HTML5 とアウトラインアルゴリズムが混乱を増やした

話をややこしくしたのが、HTML5 時代のアウトラインアルゴリズムをめぐる議論です。一時期は、各セクション(article や section のような、文書を意味のかたまりに区切る要素。仕様では sectioning content と呼びます)の中で h1 を使ってもよい、という説明が広まりました。

<article>
  <h1>記事タイトル</h1>
  <section>
    <h1>節タイトル</h1>
  </section>
</article>

この発想では、文書全体で 1 個の h1 を守るより、セクションごとに見出しを立てるほうが自然に見えます。理屈としても一貫しています。しかし実務では、このモデルが期待されたほど単純には定着しませんでした。支援技術、既存の解説、開発者の理解、検索やツールの現場が、必ずしもその説明にきれいには揃わなかったからです。

つまり問題は、「理論として破綻していた」ことより、「現場の道具と理解がその理論を前提に揃わなかった」ことでした。仕様の中にある説明と、実際に人が書き、ツールが読み、支援技術が伝える文書構造が、きれいに一体化しなかったのです。

決定的だったのは、このアウトラインアルゴリズムが どのブラウザにも支援技術にも実装されなかった ことです。仕様の中だけに存在する理想で、実際の見出しレベルを組み替えてくれる実物はどこにもありませんでした。2022 年7月には、仕様変更 #7829で、見出しレベルに基づく説明へ置き換えられました。いま現実的な結論は単純で、「セクションごとに h1 を置けば自動でレベルが調整される」とは考えず、h1 から h6 までを自分で明示することです。

その結果、「h1 は 1 個だけ」と「各セクションに h1 でよい」が、時代によって別々に広まり、現場には混乱だけが残りやすくなりました。

17.4 実務では個数より見出し構造を見る

では、いま実務ではどう考えるべきでしょうか。いちばん重要なのは、h1 の個数だけで良し悪しを決めないことです。見るべきなのは、ページの主題が伝わるか、見出し階層が読者にとって自然か、その構造が支援技術や人間の読解にとって分かりやすいかです。

多くのページでは、結果として h1 が 1 個に落ち着くことはあります。それは自然な設計であることが多い。しかしそれを「絶対にそうしなければならない」という都市伝説へ変えると、判断基準が逆に貧しくなります。

この章の要点は、h1 を何個置けるかというクイズの答えではありません。編集上の助言、仕様上の事実、HTML5 時代の議論を分けて読む視点です。そうすれば、「h1 は 1 個だけ」という短い標語が、なぜ部分的には便利で、同時にそのままでは雑なのかを説明できます。次章では、同じように強い標語で語られがちな table の善悪を見直します。

17.5 階層を明示した完成例

先ほどの記事と節を、実際の見出しレベルで区別するなら次のように書けます。

<article>
  <h1>記事タイトル</h1>
  <section>
    <h2>節タイトル</h2>
    <p>節の本文。</p>
  </section>
</article>

文字の大きさは CSS で変えられます。見出しの数字は表示サイズではなく、文書内の階層から決めます。DevTools のアクセシビリティ情報やスクリーンリーダーの見出し一覧で、記事と節の区別が伝わるかを確認します。

参考資料

第18章 table は悪者だったのか

いまは CSS でレイアウトするのが当たり前ですが、CSS がまだ未熟だった時代には、ページ全体の配置を table(表)で組むのが普通でした。やがてその反動で「table は悪」という強い言い方が生まれ、いまでも古い記事や先輩エンジニアの口から聞くことがあります。でも、料金表や時刻表のような表は、いまでも table で書くのが正解です。同じ要素が、ある場面では悪者で、ある場面では正解。何が違うのでしょうか。

この章では、「table は悪」という有名な言い方を、そのまま要素の善悪として受け取るのではなく、レイアウト用途での乱用と、データ表としての本来の役割に分けて整理します。

18.1 批判されたのは table そのものよりレイアウト用途だった

table が嫌われた背景には、ページ全体を表の入れ子で組む時代がありました。ヘッダー、サイドバー、本文、余白まで、全部を table と tr と td で配置する。そうした書き方は、たしかに長く、読みづらく、保守しにくいものでした。

ただし、そうした書き方が広まったのは、単に書き手が意味を軽視していたからではありません。当時はページを安定して並べる手段が限られ、複数のブラウザで似た見た目を確保する必要も強かった。table は、その条件の中では実用的な選択肢でもありました。この時代を象徴するのが、spacer.gif と呼ばれた 1×1 ピクセルの透明画像です。表のセルにこれを置いて幅や高さを指定し、余白を作る——いまから見れば驚くような手法が、レイアウトを安定させるために広く使われていました。

このとき批判されていたのは、「表という要素が存在すること」ではありません。データの行列関係を持たないものまで、レイアウト都合で表へ押し込んでいたことです。CSS レイアウトが普及してくると、その不自然さが一気に見えるようになり、「table は悪」という反動の強い言い方が広まりました。

つまり最初に切り分けたいのは、悪かったのが要素そのものではなく、用途とのずれだったという点です。

18.2 データ表にとっては table がいまも自然である

一方で、行と列の関係を持つ情報には、いまでも table が最も自然です。売上一覧、料金表、比較表、時刻表のように、見出しと値の対応が二次元に並ぶ情報では、表として読むこと自体が内容の一部だからです。

<table>
  <caption>文具の価格</caption>
  <thead>
    <tr><th scope="col">商品</th><th scope="col">価格</th></tr>
  </thead>
  <tbody>
    <tr><th scope="row">ノート</th><td>500円</td></tr>
  </tbody>
</table>

caption は表全体の題名、th は見出しセルです。scope="col" は列、scope="row" は行に対応する見出しであることを明示します。この例では「500円」が価格列の値であり、ノート行の値であると分かります。複雑な結合セルを含む表には追加の設計が必要ですが、まずはこの対応を崩さないことが大切です。

この種の情報を div だけで組み直して見た目を似せても、文書が伝える意味は同じにはなりません。表としての関係が薄れ、支援技術や機械処理にとっても読み取りにくくなります。つまりデータ表の文脈では、table は古い要素ではなく、意味をはっきり渡せる要素です。

たとえば、どの見出しがどの値に対応するのか、行と列の交点が何を意味するのか、といった情報は、見た目だけではなく構造として渡る必要があります。そこを div の並びへ崩すと、人間には何となく表に見えても、読む側にとっての関係が弱くなります。

ここで見えてくるのは、第5章で見た tbody の話ともつながります。table は単なる箱ではなく、表モデルという内部構造を持った要素です。だからレイアウトの汎用コンテナとして乱用すると無理が出る一方、表として使うと強い意味が出ます。

18.3 「禁止」ではなく、何を表すかで判断する

table 論争が長引くのは、「レイアウト用途での乱用」と「データ表としての正当な利用」が、短い標語の中でまとめて潰されやすいからです。table を禁止すると言ったほうが教えやすい。しかしそれでは、本来の役割まで巻き添えにします。

ここで必要なのは、要素を見る視点を「新しいか古いか」から「何を表すか」へ戻すことです。レイアウトのために行列関係のないものを表へ押し込むなら不自然です。逆に、行と列の関係が内容の核心にあるなら、table を避けるほうが不自然になります。

この判断は、HTML の他の話題とも同じです。h1 の個数だけでは決められないように、table も要素名だけで善悪を決められません。文脈を落とした標語は、部分的な真実を乱暴に広げただけのものになりやすいのです。

18.4 実務では「表らしさ」を先に問う

実務で迷ったときに先に問うべきなのは、「これは見た目が表っぽいか」ではなく、「利用者にとって表として読むべき情報か」です。列見出しと行見出しの関係があり、その交点に値が入るなら、table は有力です。逆に、ページの骨組みを並べたいだけなら、table ではなく別のレイアウト手段を選ぶべきです。

ここで押さえたいのは、「table を使ってよいか悪いか」という単純な答えではありません。批判された文脈と、いまでも正しい文脈を分けて考える視点です。そうすれば、「table は悪」という標語が、どこまで本当で、どこから雑になるのかを説明できます。次章では、同じように悪者扱いされやすい div を見直します。

参考資料

第19章 div は本当に悪なのか

「div は悪」という言い方を、そのまま要素の価値判断としてではなく、意味を持たせられる場所まで汎用コンテナへ崩してしまう問題として整理します。

19.1 div soup が批判される理由

div は、ほとんど意味を持たない汎用コンテナです。用途を限定しない点が便利ですが、配置はコンテンツモデルの制約を受けます。たとえば p の子にはできません。しかしその便利さが行きすぎると、見出し、ナビゲーション、主要領域、補足領域まで全部が div だらけになり、文書構造の手がかりが薄くなります。

<div class="header">...</div>
<div class="nav">...</div>
<div class="main">...</div>

この種の書き方が div soup と呼ばれて嫌われるのは、見た目が冗長だからだけではありません。そこに何があるのかを、要素自体があまり伝えなくなるからです。header や nav や main のような要素で渡せる意味まで、全部を無地の箱へ押し込んでしまうと、HTML が持てる構造上の情報が減ります。

しかも現代では、テンプレートやコンポーネントの都合で div が増えやすい。だから div soup は、手書き HTML の古い癖というより、いまの実装でも油断すると起きる問題です。便利な箱が増えるほど、「本当にここは無地の箱でよいのか」を意識しないと、意味はすぐ薄くなります。

19.2 それでも div が必要な場面は多い

ただし、ここで div 自体を悪者にすると、話は逆に雑になります。現実の UI は、意味要素だけですべて組み切れるほど単純ではありません。

スタイリングのためのまとまり、レイアウト上の包み、JavaScript のフック、再利用部品の外枠。そうした場面では、div が最も自然です。意味を足すべきではない箱にまで、無理に意味要素を当てはめるほうが不自然になることもあります。

たとえば、カード部品の内側で余白や境界線をまとめてかけたい、複数要素をまとめて並べたい、スクリプトがまとめてつかむ外枠が欲しい。こうした場面では、「ここに新しい文書上の役割がある」とまでは言えません。だから div のほうが素直です。

つまり問題は、div を使うことそのものではありません。固有の意味を持つべき場所を、何となく全部 div で済ませてしまうことです。汎用コンテナが悪いのではなく、意味の切り分けを放棄したまま使うことが問題になります。

19.3 セマンティクスは「固有要素があれば使う」から始まる

ここで必要なのは、div を禁止することではなく、順番を逆にしないことです。まず「ここに意味のある要素があるか」を考える。header nav main section article などで表せるなら、そちらを使う。そのうえで、意味を増やす必要がないまとまりには div を使う。この順番なら、div は敵ではなく、汎用要素として適切な位置に収まります。

この考え方は、第18章の table とよく似ています。table も要素名だけで善悪を決められなかったように、div も要素名だけで良し悪しは決まりません。何を伝えたいかに対して、固有要素で足りるのか、汎用コンテナが自然なのかを見分ける必要があります。

だから div への批判が妥当になるのは、「この場所にはもっと意味のある要素があったのに、全部を無地の箱へ落としてしまった」ときです。逆に、意味を持たせる必要のないラッパーまで責めると、セマンティクスを誤解した議論になります。

19.4 実務では「何を伝えたい箱か」を問う

実務で div を見るときは、「div がある」という事実より先に、「この箱は何を伝えるためにあるのか」を見るほうが有効です。文書構造の役割を持つなら固有要素を検討する。役割ではなく単なるレイアウトや包みなら div でよい。この判断ができれば、div soup への違和感も、単なる美意識ではなく説明可能なものになります。

この章で言いたいのは、「div は使うな」という標語ではありません。汎用要素が悪いのではなく、意味を持つ場所まで汎用化してしまうことが問題です。そうすれば、「div は悪」という言い方が、どこまで本当で、どこから雑になるのかを整理できます。次章では、見た目だけの要素だと思われがちな b が、なぜ今も仕様の中に残っているのかを見ます。

19.5 意味のある領域と表示用の包みを分ける

<div class="page-layout">
  <nav aria-label="主なページ">
    <a href="/">ホーム</a>
    <a href="/news">お知らせ</a>
  </nav>
  <main>
    <article>
      <h1>新しい会場のお知らせ</h1>
      <div class="notice-body">
        <p>次回は市民ホールで開催します。</p>
      </div>
    </article>
  </main>
</div>

nav は主要な移動先、main はページの主要内容、article は独立したお知らせを表します。一方、page-layout と notice-body は配置や余白のための包みなので div を残しています。すべての div を section へ置き換える必要はありません。包みに固有の意味が必要かを一つずつ判断します。

参考資料

第20章 b タグはなぜ生き残ったのか

b を「見た目だけの古い要素」と切り捨てるのではなく、なぜ削除ではなく再定義という道が選ばれたのかを見ます。

20.1 b は「太字にするタグ」だけでは終わらなかった

かつて b は、ほとんど見た目のための要素として使われました。その文脈だけを見ると、CSS で太字を指定できる現在では、役目を終えたように見えます。

しかし HTML は、古い要素を何でも単純に消すわけではありません。互換性の問題もあるし、実際の文章の中には「強い重要性ではないが、周囲から視覚的に区別したい」テキストが残ります。そこで b は、単なる太字指定ではなく、実用的な都合で読み手の注意を向けたいテキストを、周囲からスタイル上ずらして示すための要素として再定義されました。

ここで重要なのは、b が「見た目のための古い要素」から「重要性ではなく区別を担う要素」へ置き直されたことです。削除ではなく、役割の調整が選ばれたわけです。

20.2 strong と b は似て見えて同じではない

この違いは、strong と並べると見えやすくなります。

<p><strong>重要:</strong> 申込期限は明日です。</p>
<p>比較した製品は <b>ノート A</b> と <b>ノート B</b> です。</p>
<p>締切は <em>明日</em>です。来週ではありません。</p>

strong は重要性・重大性・緊急性を表します。em は発話の強勢を表し、この例では「明日」に力点があります。b は重要性を追加せず、実用的な目的で注意を向ける語句を示します。製品比較に出てくる製品名などが例です。単に太くしたいという装飾上の都合だけなら CSS を使います。

この差を無視すると、「太字なら全部 strong」「見た目なら全部 CSS」で二分したくなります。しかし現実の文章には、そのどちらにもきれいに収まらない区別があります。b が残ったのは、その隙間を埋める必要があったからです。

20.3 HTML は古い要素を再定義して残すことがある

ここで見えてくるのは、HTML が古い要素を扱うときの流儀です。第13章では、廃止された要素たちが需要の痕跡を残したことを見ました。一方 b では、完全に消すより、現代の文脈に合わせて意味を調整して残す道が選ばれました。

これは後方互換性だけの問題ではありません。もし b を単に obsolete にして、「以後は使うな」で済ませていたら、現実に存在する弱い視覚的区別のニーズを、全部 CSS や別の要素へ押し込むことになります。そこには、それはそれで別の不自然さが出ます。

つまり b の生存は、古いものへの未練というより、「現実の文章で必要な区別を、どの要素が引き受けるか」という再配置です。HTML がしているのは、過去の遺物をただ温存することではなく、役割の棚卸しでもあります。

20.4 実務では「重要なのか、区別したいだけなのか」を問う

実務で b と strong を迷ったときは、見た目ではなく、何を伝えたいかで切るほうがよい。重要性や警告の重みを伝えたいなら strong。重要性や強勢を加えず、実用的な目的で注意を向けたい語句なら b。意味を持たせず見た目だけ変えたいなら CSS も選択肢に入ります。

この切り分けを入れると、b が CSS に完全に置き換わるわけではないことも見えてきます。CSS は見た目を変えますが、その語句を「周囲から少し区別したい」という文章上の意図までは表しません。逆に、その意図すらないなら、b を使わず CSS だけで済ませたほうが素直です。

この章の要点は、「b は古いから使うな」という標語の逆です。b が生き残ったのは、単なる慣性ではなく、HTML が古い要素を再定義しながら現代の役割へ置き直すことがあるからです。そう読むと、b は仕様の中で何となく生き残ったのではなく、調整された居場所を与えられたと分かります。次の第6部では、こうした居場所の調整を誰がどのように決めてきたのかを見ます。

参考資料

第6部 HTML を作り続ける人たち

第6部の役割は、HTML を抽象的な仕様ではなく、人が作り、人が争い、人が維持してきた技術として描くことです。ティム・バーナーズ=リー(Tim Berners-Lee)、ブラウザ戦争、WHATWG を通して、HTML の背後にいる主体を見ます。

ここでは英雄譚に寄せすぎず、制度と衝突の話として整理します。次の第7部では「それでも HTML が生き残った理由」を扱うため、この部はその歴史的な基盤を作る位置づけです。

第21章 ティム・バーナーズ=リーが残したもの

ティム・バーナーズ=リー(Tim Berners-Lee)を「HTML を作った人」とだけ覚えるのではなく、なぜ Web が公開された文書の網として出発したのかを、その初期設計から見ます。

21.1 HTML だけでは Web にならない

ティム・バーナーズ=リーの名前は、しばしば HTML の発明者として紹介されます。それは間違いではありません。しかし、この本の流れで重要なのは、HTML だけを取り出しても Web は成立しないことです。文書の中にリンクを書けても、そのリンク先をどの名前で指し、どう取りに行くかが決まっていなければ、世界に散らばる文書はつながりません。

そこで初期の Web では、文書の構造を記す HTML、場所を指し示す URL、文書をやり取りする HTTP が一体の仕組みとして設計されました。残されたものは単独のタグ集合ではなく、「どこにある何を、どう参照し、どう受け取るか」という公開文書の最低限の約束です。

もちろん、後の Web を育てたのは一人ではありません。ブラウザ実装者、標準化団体、著者、サーバー側の開発者がそれぞれに形を作ってきました。それでも出発点として、この三つを公開された組として置いたことは、後の発展方向を強く縛る初期条件になりました。

この一体性を先に見ておくと、HTML が最初から単なるマークアップ言語の話ではなかったことも分かります。HTML は、リンクされる文書という前提の中で生まれた。だから後の章で見る互換性や標準化の議論も、文書の記法だけでなく、公開された網を保つ話として読めるようになります。

この第6部から第7部にかけて扱う歴史は、ざっと次のように流れます。細部はこれからの章で見ていきますが、先に全体像を持っておくと迷いません。

1989 年の提案、1990 年の実装、1993 年の img 提案と CERN の公開、WHATWG 発足、XHTML 2 の終了、HTML5 勧告、2019 年の合意、現在を並べた年表
図 21-1 HTML は完成したのではなく、調整され続けてきた。

21.2 解決したかったのは閉じた文書管理ではなかった

第2章では、CERN(欧州原子核研究機構)の情報共有問題から Web が始まったことを見ました。ここで改めて重要なのは、解決策が巨大で複雑な文書管理システムではなく、比較的単純な公開と参照の仕組みとして組み立てられたことです。

最初のサーバーやサイトは CERN で作られました。CERN の記録は、最初のサイトとともに、1993 年4月30日に Web のソフトウェアをパブリックドメインへ置いたことを伝えています。これは Web 上のすべての文書の権利をなくしたという意味ではなく、基盤となるソフトウェアを利用できるようにする措置でした。

たとえば、ある研究資料を読み、その中の別文書への参照をたどり、その先へ移る。初期の Web が目指したのは、その動作を特別な契約や専用端末に依存せずに成立させることでした。そのためには、文書はある程度単純で、場所は共有できる形で表され、通信方法も公開されている必要があります。

ここでティム・バーナーズ=リーが残したものは、「最初の Web サイトを作った」という出来事より、公開された約束で回る仕組みを最初から選んだことです。もしここで、特定ソフトウェアの中だけで通じる文書形式や、閉じた識別子や、独自の通信方式が選ばれていたら、後の Web はかなり違う形になっていたはずです。

21.3 公開された仕様と利用できる実装

公開された通信や文書の約束に加え、実装を利用できる条件も普及を支えました。1993 年の CERN の公開文書は、この制度上の判断を確かめる資料です。第2章の「情報をつなぐ必要があった」という問題設定に対し、ここでは「他の人も参加できる条件をどう用意したか」を見ています。

もちろん現実の Web は、その後ずっと理想どおりだったわけではありません。商用化も起きたし、ブラウザ間の競争も起きたし、独自拡張も大量に生まれました。それでも「公開された文書の網」という出発点があったからこそ、Web は完全に誰か一社の製品にはなりませんでした。

この意味で、ティム・バーナーズ=リーが残したものは発明品の数ではありません。HTML、URL、HTTP を通して、文書公開の仕組みを閉じた製品ではなく、共有された土台として始めたことです。後の標準化団体や仕様策定者が何を守ろうとしたのかも、この初期条件を踏まえると見えやすくなります。

21.4 人物伝ではなく、初期条件として読む

この章で人物を大きく扱うのは、英雄譚を書きたいからではありません。本書の主題は、HTML がなぜ今の形になったのかです。その答えの一部は、最初にどんな条件で Web が始まったかにあります。

ティム・バーナーズ=リーを「偉人」として消費してしまうと、話は豆知識で終わります。しかし「HTML は公開された文書の網の一部として設計された」と読むと、その後の互換性重視、標準化、実装の苦労まで一本の線でつながります。各社のブラウザが競い合えたのも、そもそも同じ Web を読むという共有土台があったからです。第22章では、その土台の上で起きたブラウザ戦争が、なぜ混乱であると同時に標準化の必要性を浮かび上がらせたのかを見ます。

参考資料

第22章 ブラウザ戦争が残したもの

「Best viewed in Netscape」——かつての Web には、こんなバッジが堂々と貼られていました。同じ Web のはずなのに、どのブラウザで見るかで表示が変わる。なぜ、そんな時代があったのでしょうか。

この章では、ブラウザ戦争を「昔は大変だった」で終えるのではなく、なぜ HTML とブラウザ実装が互換性にそこまで神経質になったのかを、その歴史的な理由から見ます。

22.1 競争は Web を速く広げたが、同時に割った

1990 年代後半のブラウザ戦争では、Netscape と Internet Explorer が新機能を激しく競い合いました。短期的には、画像、スクリプト、レイアウト、埋め込み表現など、Web でできることが一気に増えました。その意味で、競争は Web を広げる推進力でもありました。

しかし同時に、その競争は「同じ Web を見る」という前提を壊しかけました。あるブラウザで動くページが別のブラウザでは崩れる。あるタグや API が片方にしかない。著者は、文書を書くというより、どのブラウザへ合わせるかを選ぶ作業へ引き寄せられていきます。

ここで問題だったのは、競争そのものではありません。共有された土台の上で、互換性より囲い込みが優先されやすくなったことです。Web は本来、別々の組織が公開した文書を相互に読めることに強みがありました。その基盤が、実装ごとの差で揺らぎ始めたわけです。

22.2 独自拡張は便利さと引き換えに分断を生んだ

この時代には、各社が独自タグや独自 API を打ち出しました。象徴的なのが、テキストを点滅させる Netscape の <blink> と、文字を横へ流す Microsoft Internet Explorer の <marquee> です。blink の点滅効果は現在のブラウザでは使えません。一方 marquee は、著者が新しく使うべきでない obsolete な要素ですが、現行 HTML に互換性のための実装要件が残っています。両者を一括して「標準に一度も書かれず、実装も消えた」とは扱えません。著者から見れば、こうした独自機能は魅力的でもあります。新しい見た目や振る舞いをすぐ使えるからです。しかし、その便利さはしばしば「このブラウザで見てください」という条件とセットでした。当時のページに『Best viewed in Netscape』『Internet Explorer 推奨』といったバッジが当たり前のように貼られていたのは、文書が閲覧ソフトに縛られていたことの裏返しです。

たとえば、ブラウザごとに使える機能が違えば、著者は分岐を書いたり、場合によっては片方のブラウザを切り捨てたりします。文書を公開するはずの Web が、実際には閲覧ソフトごとの別世界へ分かれ始めるわけです。

あるページが Internet Explorer 向けの機能に依存して作られていれば、Netscape では意図どおりに読めません。逆も同じです。すると Web 文書は、公開された文書でありながら、実質的には特定の閲覧ソフトに縛られます。これは、第21章で見た出発点とかなり緊張します。

さらに厄介なのは、いったん広く使われた独自挙動は、あとで簡単に消せないことです。互換性のために残され、他社も追随せざるを得なくなる。第16章で見た「壊せない Web」は、こういう歴史ともつながっています。ブラウザ戦争が残したのは一時的な混乱だけではなく、長く尾を引く互換性負債でもありました。

22.3 標準化は理想論ではなく、疲弊への対策だった

この時代が残した最大の教訓は、互換性を市場競争だけに任せると、Web 全体が疲弊するということでした。著者は分岐だらけのコードを書くことになり、利用者はブラウザごとの差に振り回され、実装者は他社の独自挙動まで無視できなくなります。

だから標準化は、きれいごとのために強調されたのではありません。共有された仕様がなければ、同じ文書を相互に読めるという Web の基盤が持たないからです。今の HTML 仕様が、理想的な文法だけでなく、実際のパースやエラー回復、既存実装の挙動まで深く書くのは、この反省抜きには理解しにくい。

現在でもブラウザ間の競争は続いています。新機能の実装順、実験機能、開発者体験では差が出ます。ただし、少なくとも「各社が好き勝手に別々の HTML を作る」方向は、過去の失敗として共有されるようになりました。競争を否定したのではなく、競争を共有土台の上へ戻そうとしたのです。

22.4 ブラウザ戦争は現在の仕様の書き方を説明する

この章で伝えたいのは、ブラウザ戦争を昔話として覚えることではありません。HTML 仕様がなぜそこまで実装寄りで、互換性にうるさく、既存の挙動を無視できないのか。その理由の一部は、この時代の混乱にあります。

つまりブラウザ戦争が残したものは、独自機能の一覧ではなく、「共有された Web を保つには、共有されたルールが要る」という教訓です。第23章では、その教訓を受けて、いま HTML を誰がどのように決めているのかを見ます。

参考資料

第23章 HTML は誰が決めているのか

歴史的には HTML 4.01 や HTML5 といった版がありました。現在の HTML Living Standard は、そのような完成版の番号で更新を区切りません。いつ完成したのか、誰が決めているのか、よく考えると不思議です。実は HTML は、完成して配られる仕様ではなく、いまも書き換えられ続けている仕様なのです。

この章では、HTML を「どこかの偉い組織が完成版を配る仕様」としてではなく、実装者・編集者・標準化団体のあいだで継続的に運用される仕様として見ます。

23.1 HTML は一度決めて終わる仕様ではない

現在の HTML では、WHATWG の HTML Living Standard が中心的な参照点になっています。WHATWG は、ブラウザベンダーや関係者が Web 標準を継続的に整備するためのコミュニティです。名前の通り、この仕様は版で固定されず、継続的に更新されます。ここでまず重要なのは、「最新版が出たら古い Web を捨てる」という発想ではないことです。既存の Web を壊さずに、新しい現実を少しずつ取り込むために、仕様そのものが動き続けています。

この形が必要になったのは、第22章で見たように、実装と仕様がずれたまま放置されると Web 全体が疲弊するからです。逆に言えば、仕様は理想だけを書いてもだめで、実際に動いているブラウザ、著者が書いている HTML、既存ページの互換性を引き受けなければなりません。HTML が「生きている仕様」になったのは、気分の問題ではなく、運用上の必要でした。

23.2 組織名より、誰が何を持ち寄るかが大事

ここで WHATWG や W3C の名前が出てきますが、本書で伝えたいのは組織図そのものではありません。W3C は Web 標準を公開・議論してきた標準化団体です。かつては W3C と WHATWG がそれぞれ別に HTML の仕様を出していた時期もありましたが、2019 年に両者は合意し、HTML と DOM の標準を WHATWG の Living Standard へ一本化しました。いま単に「HTML 仕様」と言えば、この WHATWG 版を指します。大事なのは、ブラウザベンダー、仕様編集者、実装者、著者コミュニティ、標準化団体が、それぞれ別の制約を持ち寄っていることです。少なくとも現在の読者にとって重要なのは、「別々の HTML が並立している」と考えるより、HTML が継続的な標準化と実装の往復の中で保たれていると捉えることです。

ブラウザベンダーは実装と互換性を気にします。仕様編集者は曖昧さを減らし、実装可能な形へ落とし込みます。著者側の需要も無視できません。標準化団体は、それらを公開された形で議論し、文書化し、合意形成の場を整えます。提案、議論、実装、フィードバックの往復を経て仕様が更新されるのであって、会議室の中だけで完結するわけではありません。つまり HTML は、誰か一人が決めるのでも、投票だけで決まるのでもなく、現実に動いている Web の制約をすり合わせながら決まっていきます。

だから HTML は誰が決めているのか という問いに、単一の固有名詞だけで答えると少し外します。正確には、「どの組織の下で、どの編集者が、どの実装と互換性を見ながら、どの議論を経て決めているのか」を見る必要があるわけです。

23.3 仕様は法律集ではなく、合意形成の痕跡でもある

この制度の理解が役立つのは、HTML 仕様を固定された法律集として見なくてよくなるからです。仕様書には、理想の設計だけでなく、過去との互換性、実装上の判断、失敗を繰り返さないための細かな規定が折り重なっています。第4章から第8章で見たようなパーサーやエラー回復の細かさも、まさにその産物です。

この視点に立つと、「仕様にそう書いてあるから従う」というだけでは足りません。なぜそこまで細かく書かれているのか、なぜ版で止めずに更新し続けるのか、なぜ実装者の現実が強く反映されるのかまで見えてきます。HTML は、合意形成の過程そのものを内部に抱えた技術です。

23.4 HTML を決める仕組みも、 HTML の性格を映している

ここで押さえたいのは、WHATWG と W3C の関係を制度豆知識として覚えることではありません。HTML を決める仕組み自体が、HTML という技術の性格をよく表しているということです。つまり、閉じた場で完成品を決めて配るのではなく、壊せない既存 Web と、新しく出てくる要求のあいだを調整し続ける。その運用の形が、Living Standard です。

そう考えると、HTML が今も生きている理由も見えやすくなります。完成して終わる技術ではなく、公開されたまま調整され続ける技術だからです。第7部では、その HTML がなぜ長生きし、誰に読まれ、どこへ向かうのかを見ます。

23.5 変更の理由を追う入口

2019 年の役割分担は、W3C と WHATWG の合意文書で確認できます。HTML と DOM の開発は WHATWG のリポジトリと Living Standard で進め、W3C のコミュニティもレビューや課題提起に参加する枠組みです。

具体的な変更を追うなら、HTML の公開リポジトリで、関連する issue、変更案の pull request、最終的な本文の差分を順に読みます。たとえば第17章の #7829では、旧来のアウトライン説明をどう置き換えたかを確認できます。

実装結果の比較には web-platform-tests も関わります。ただし、仕様の変更がマージされたこと、テストが追加されたこと、各ブラウザで利用できることは別の状態です。仕様の日付だけで利用可否を決めず、実装の対応も調べる必要があります。

参考資料

第7部 なぜ HTML は今も生きているのか

第7部の役割は、本書全体の締めくくりとして、HTML が今も使われ続けている理由と、これからの変化の仕方を考えることです。前の各部で見てきた歴史、実装、標準化、議論を一つに束ねます。

第24章では HTML を読む主体の広さを確認します。第25章では長寿の理由を、後方互換性・公開性・単純さから整理します。第26章では HTML が「完成する技術」ではなく、更新され続ける技術であることを示して閉じます。

第24章 HTML は誰に読まれているのか

画面で読める画像でも、画像を見ない人に同じ情報が届くとは限りません。HTML の役割がよく見えるのは、一つの内容を違う方法で利用する場面です。

24.1 情報が届く経路は一つではない

ブラウザは HTML から DOM を作り、CSS などと組み合わせて表示します。また、要素の役割・名前・状態などをアクセシビリティ情報として支援技術へ伝えます。スクリーンリーダーはこうしたブラウザや OS の提供する情報を使い、読み上げや見出し間の移動を助けます。単に HTML ソースを直接読み上げるだけの道具ではありません。

一方、検索サービスや保存ツールは、取得した HTML や、場合によってはスクリプトを実行した結果から内容を扱います。どの段階を利用するかはサービスによって違います。「ブラウザで見えたから、すべての読み手にも同じ情報が渡る」とは限りません。

HTML からブラウザを介して支援技術へ情報が渡る経路と、HTML を取得する検索・カード生成などの経路を分けた図
図 24-1 同じ HTML でも、読み手へ情報が渡る経路は異なる。

24.2 alt は画像の用途から決める

代替テキストは、ファイル名や画面の説明を機械的に付ければよいものではありません。その画像が、この文脈で何を伝えているかから決めます。

情報を伝える画像なら、その情報を短いテキストで置き換えます。

<img src="entrance.jpg" alt="入口は建物の右側にあるスロープの先">

装飾だけで、取り除いても情報を失わない画像なら、空の alt を使います。

<img src="divider.png" alt="">

alt="" と、alt 属性を書かないことは同じではありません。空の値は、この文脈では代替テキストを提示する必要がないことを示します。説明がないというだけで省略すると、支援技術がファイル名などを読み上げる場合があります。

画像だけのリンクなら、見た目より行き先や機能が分かる名前が必要です。

<a href="/search"><img src="search.png" alt="検索"></a>

「虫眼鏡の画像」より「検索」のほうが、このリンクを使う目的を伝えます。同じ画像でも、隣に「検索」というリンクテキストがあれば、画像側を空の alt にして重複を避ける設計もできます。

複雑なグラフなどは、短い alt だけでは情報を運びきれません。本文の説明やデータ表も用意します。alt は画像の万能な解説欄ではなく、周囲の内容と合わせて情報を届けるための一部です。

24.3 本文の意味とカード用メタデータ

見出しや表の構造は、本文の読み方を伝えます。nav や main は主要な領域への移動を助けます。これらを見た目だけの div にすると、画面が似ていても渡せる意味が減ります。

SNS のカード生成は、Open Graph の title・description・image などのメタデータを参照する場合があります。これは本文の h1 や表の構造とは別の情報です。見出しを直しただけでカードの説明まで直るとは限りません。利用するサービスの確認画面で、題名・概要・画像を別途確認します。

検索エンジンについても、正しい要素を使うことだけで検索順位が決まるわけではありません。内容の有用性と、機械に渡せる構造は、関連していても同じ評価項目ではないのです。

24.4 見た目の確認から一歩進める

画像を選ぶときは、まず用途が情報・装飾・機能のどれかを考え、周囲の文章と重複しない代替テキストにします。見出しや領域は、ブラウザのアクセシビリティ情報でも意図した名前と役割になっているかを見ます。さらにキーボードや支援技術を使って、実際に移動・操作できるかを確認します。

HTML の意味づけは、仕様の問題をなくすためだけのものではありません。画面以外の経路でも、利用者が同じ情報へたどり着けるようにするための手がかりです。

参考資料

第25章 HTML はなぜ長生きなのか

HTML が単に古いのに残っているのではなく、なぜ今も基盤として生き残っているのかを見ます。

25.1 古い文書を捨てないことが Web の資産になった

第一の理由は、後方互換性です。昔の文書をいまもある程度読めることが、Web を単なる配信手段ではなく、巨大な公共アーカイブにしました。数年前のページだけでなく、もっと古い文書まで参照し続けられることは、利用者にとっても、著者にとっても、実装者にとっても大きな価値です。

もちろん、その代償としてブラウザは古い挙動まで面倒を見る必要を負います。本書の前半で見たエラー回復や互換性重視の姿勢は、まさにその表れでした。それでも Web がここまで巨大になれたのは、昨日のページも十年前のページも、なるべく捨てずにつなぎ止めてきたからです。

ここで重要なのは、後方互換性が単なる保守性ではないことです。古いものを守ること自体が、新しい利用者を呼び込み、引用や参照を積み上げ、Web 全体の価値を増やしてきました。HTML の長寿は、過去を切り捨てなかったことの結果でもあります。

25.2 開かれていたから、誰か一社の都合で終わらなかった

第二の理由は、オープン性です。HTML は特定企業だけが実装できる閉じた形式ではありませんでした。仕様が公開され、複数のブラウザやツールが実装し、周辺の開発者も参加できる。この条件があったからこそ、たとえ一社が失速しても、Web 全体までは止まりません。

第21章から第23章で見たように、Web は最初から公開された土台として始まり、競争の混乱を経て、継続的な標準化の形を整えてきました。この公開性は、きれいな理念というだけでなく、長期的に見たときの生存条件でもありました。複数の実装者が参加できたからこそ、古い文書を読み続ける責任も一社に閉じずに持ちこたえられました。閉じた形式なら、製品の寿命と一緒に消えていたかもしれません。

25.3 中心が比較的単純だったから、周辺の変化を吸収できた

第三の理由は、シンプルさです。もちろん、現在の HTML 仕様書はまったく単純ではありません。パーサー、互換性、詳細な規則まで含めれば巨大です。それでも中心にあるのは、文書構造を記述するという比較的単純な役割です。

この中心が保たれていたからこそ、周辺の技術が大きく変わっても HTML は残れました。CSS が伸び、JavaScript が伸び、フレームワークが入れ替わっても、最終的に多くの UI は HTML に落ちます。HTML が長生きするということは、周辺の技術変化を吸収する共通基盤が残り続けるということでもあります。

25.4 長寿は美しさではなく、使われ続ける条件の積み重ねだった

HTML は完璧だから生き残ったのではありません。むしろ不格好な部分を抱えたまま、壊さず、開き続け、中心を保ち、利用者を増やしてきたから生き残りました。この長寿は、美しい設計だけでは得られない種類の強さです。

つまり HTML の長寿は、後方互換性、オープン性、シンプルさがばらばらに働いたのではなく、互いに支え合ってきた結果です。古い文書を捨てず、誰でも実装でき、周辺の変化を吸収できる。だから HTML は、古い技術なのに残ったのではなく、残る条件を備えていた技術だと言えます。最終章では、その HTML がこれからも完成せず、どこへ向かっていくのかを見ます。

25.5 長く読めることは、保存が不要という意味ではない

形式の後方互換性が保たれても、サーバーがなくなれば URL は取得できず、外部画像が消えれば表示できません。HTML が長寿であることと、個々のページが自動で保存されることは別です。

長く残す資料なら、意味のある見出しやリンクで本文を構成し、必要な画像なども保存対象に含めます。本文を読むだけなのに外部のスクリプト実行を必須にしていないかも確認します。公開形式の強さを生かすには、配信と保存の運用が伴う必要があります。

参考資料

第26章 HTML はどこへ向かうのか

開閉する説明欄を作るとき、最初からクリック処理を書いていないでしょうか。HTML の変化を追う価値は、新しいタグを暗記することだけではありません。自分で実装していた処理を、ブラウザの標準機能に任せられるかを判断することにもあります。

26.1 details で標準機能を試す

次は、補足説明を開閉する本文断片です。

<details>
  <summary>持ち物を確認する</summary>
  <p>筆記用具と参加証をお持ちください。</p>
</details>

details は開閉できる補足情報、summary はその見出しです。独自の JavaScript を書かなくても、summary を操作して開閉できます。初めから開くなら details に open 属性を付けます。open="false" では閉じないことは、第11章のブール属性の話と同じです。

汎用的な div から作るなら、開閉の処理に加え、キーボード操作や状態の通知も設計する必要があります。用途が details に合うなら、標準の意味と操作を利用できるため、自作する部分を減らせます。ただし、タブ切り替えやメニューなど、異なる UI の代用品にしてよいわけではありません。

26.2 存在確認と利用確認を分ける

基本的な API の存在は、たとえば次の式で調べられます。

'open' in document.createElement('details')

この結果が true でも、すべての操作が期待どおりであることや、支援技術との組み合わせまで保証するわけではありません。採用する際は、対象ブラウザの対応を確認し、実際のページで次を試します。

  1. JavaScript を追加せず、クリックとキーボードで開閉できるか。
  2. 見出しへフォーカスした位置が分かり、開閉状態を支援技術で確認できるか。
  3. CSS を付けたあとも操作や内容の読み取りを妨げていないか。
  4. 印刷など別の利用経路で、閉じた内容をどう扱う必要があるか。

標準機能を使えば、設計と確認が不要になるのではありません。基本の振る舞いをブラウザに任せたうえで、自分の用途との適合を確認するのです。

26.3 新機能を追うときの順序

仕様に名前が載ったこと、実装されたこと、利用者の環境で使えることは別です。新しい機能を見つけたら、まず仕様で用途と制約を読み、次に対応状況を調べ、最後に対象環境で小さく試します。変更の理由を知りたいときは、第23章で紹介した issue や pull request をたどります。

Living Standard という更新方式は、未来の機能をすべて予告しているわけではありません。ブラウザの実装、利用者の要求、互換性の問題に応じて、説明や処理を更新し続ける仕組みです。将来の予想を固定した一覧より、この調べ方を持っているほうが長く役立ちます。

26.4 不思議な挙動を、次の判断へつなげる

ソースと DOM が違えばパース規則を調べ、古い要素を見つければ著者向けの状態と互換実装を分け、要素を選ぶときは読み手へ渡す意味を考える。本書で扱った知識は、こうした判断のために使えます。

HTML の奇妙さには、タグ省略の構文、既存ページとの互換性、複数の読み手への情報提供など、異なる理由があります。すべてを一つの設計思想で片づけず、目の前の挙動がどの規則によるものかを確かめる。その姿勢が、これから変わる HTML にも付き合う方法になります。

参考資料

付録

付録では、本編を読み終えたあとに振り返りやすい形で情報を再構成します。本編の代わりになる要約ではなく、本編の理解を助ける補助線として使ってください。

  • 付録 A は、本編の論点を短く引き直せる補助メモです。
  • 付録 B は人名の一覧ではなく、HTML の歴史のどこで何を担ったかを短くまとめます。
  • 付録 C は広まりやすい言説を、どこまで本当でどこから雑かという観点で整理します。
  • 付録 D は、本編で扱いきらなかった論点を追加でたどる補論です。

付録 A HTML を読み解くための 50 のメモ

本編で扱った論点を、振り返りやすい短いメモにまとめました。気になった番号から読み直し、本編のどの章とつながるかを確かめてください。

ブラウザの中で起きていること

書いたものと、ブラウザが実際に扱うものは違う、という話。

  1. HTML 文字列から表をパースすると、省略した tbody タグに対応する要素が DOM に現れる。(→ 第5章)
  2. html head body は省略しても補完される。(→ 第4章)
  3. p の終了タグは条件付きで省略できる。余った終了タグのエラー回復とは別。(→ 第6章)
  4. <table> の直下に書いたテキストは、表の中ではなく表の前へ飛ばされる(foster parenting)。(→ 付録D)
  5. View Source と DevTools は、同じページの違う段階を見ている。(→ 第8章)
  6. HTML 文字列から作った表では tbody が間に入り、table > tr は一致しない。DOM API で直接作った表とは区別する。(→ 第5章)
  7. HTML 仕様書のかなりの部分は、人間向けの説明ではなくパーサーの規則。(→ 第12章)

HTML という言語の正体

そもそも HTML は何をする言語なのか。

  1. HTML はプログラミング言語ではない(if も for もない)。(→ 第1章)
  2. それでも Web の基盤になれたのは、読み手が多かったから。(→ 第24章)
  3. HTML は URL・HTTP と分業して、はじめて Web になる。(→ 第2章)
  4. HTML が重視したのは、完成した紙面より「接続された文書」。(→ 第3章)
  5. ブラウザは壊れた入力でも止まらず、回復して読み進める。(→ 第7章)
  6. それは甘さではなく、閲覧を止めないための設計。(→ 第7章)

省略と空要素のからくり

短く書けるのには、ちゃんと理由と条件がある。

  1. img は最初から子を持たない「空要素」。閉じ忘れではない。(→ 第9章)
  2. br も hr も空要素。(→ 第9章)
  3. <img /> の / は HTML では無視される。でも SVG / MathML では本当に効く。(→ 第9章)
  4. ブール属性(disabled など)は値を書かなくても成立する。(→ 第11章)
  5. しかし disabled="false" と書いても、無効化は解除されない。(→ 第11章)
  6. 引用符の省略には、かなり狭い条件がある。(→ 第11章)
  7. 属性やタグの省略可能性は、HTML の祖先 SGML の名残。(→ 第11章)
  8. コメントの中にも、書いてはいけない並びがある。(→ 第10章)

歴史のひとこま

いまの HTML は、たくさんの偶然と現場の都合でできている。

  1. CERN は、世界初のサイトを紹介・保存している。(→ 第21章)
  2. 1989 年の Web 提案は、CERN の情報のつながりを扱っていた。(→ 第2章)
  3. 最初の Web の実装は CERN で作られた。(→ 第21章)
  4. img タグは 1993 年にマーク・アンドリーセンが提案した。(→ 第9章)
  5. blink と marquee は著者向けに廃止されているが、現在の実装要件は同じではない。(→ 第22章)
  6. 文字を流す <marquee> は Internet Explorer の独自タグだった。(→ 第22章)
  7. かつてはコメント <!--[if IE]> が、IE 専用の分岐に使われていた。(→ 第10章)
  8. spacer.gif(1×1 の透明画像)でレイアウトを整えた時代があった。(→ 第18章)
  9. font や center は obsolete だが、当時の需要は本物だった。(→ 第13章)
  10. obsolete は「なかったこと」ではなく、歴史の層として残る。(→ 第13章)

標準化のドラマ

HTML は、誰かが一度決めて終わり、ではない。

  1. XHTML 1.x の記法の利用と、XML としての配信は別の問題。(→ 第14章)
  2. XML の整形式違反は致命的なエラーになるが、HTML の正しいタグ省略とは分けて考える。(→ 第14章)
  3. 2009 年に W3C は XHTML 2 Working Group の任期を更新しない方針を示した。(→ 第14章)
  4. text/html の <!DOCTYPE html> は、主に標準モードへ切り替えるために書く。(→ 付録D)
  5. Quirks Mode は、古いページを壊さないために今も残っている。(→ 第15章)
  6. WHATWG の HTML は、版で止まらない Living Standard。(→ 第23章)
  7. W3C と WHATWG は 2019 年に HTML 標準を一本化した。(→ 第23章)
  8. ブラウザ戦争の混乱が、標準化の必要性を強くした。(→ 第22章)

HTML を読むのは誰か

HTML は、人間の目だけのためにあるのではない。

  1. HTML の読者は、ブラウザだけではない。(→ 第24章)
  2. 検索エンジンも HTML の構造を読む。(→ 第24章)
  3. スクリーンリーダーは、ブラウザが渡す役割・名前・状態などの情報を利用する。(→ 第24章)
  4. alt は画像の用途から決める。装飾だけなら空、画像だけのリンクなら機能や行き先を伝える。(→ 第24章)
  5. <details> と <summary> だけで、JavaScript なしに開閉する UI が作れる。(→ 第26章)
  6. <script> の中に </script> と書くと、文字列の途中でも壊れる。(→ 付録D)
  7. & は常に変換されるわけではない。文字参照の条件と、確実に & を表す &amp; の表記を分ける。(→ 付録D)
  8. フレームワークが変わっても、最終的に HTML へ落ちることは多い。(→ 第8章)
  9. HTML は「古い」のではなく「古くても読める」技術。(→ 第25章)
  10. HTML は完成しない仕様。これからも調整され続ける。(→ 第26章)
  11. HTML の奇妙さの多くには、ちゃんと歴史的な理由がある。(→ 第26章)

付録 B HTML を作った人たち

HTML は一人で完成させた仕様ではありません。ここでは、発明、仕様の編集、表現の分業に関わった三人を、残された一次資料から紹介します。

Tim Berners-Lee(ティム・バーナーズ=リー) — Web の仕組みを提案した人

1989 年の Information Management: A Proposalで、CERN 内の人・装置・文書の関係をたどれる情報管理を提案しました。最初の Web の実装へ進み、HTML、URL、HTTP を組み合わせた仕組みを形にしました。

公開の条件を整えたのは個人だけではありません。1993 年に CERN が Web のソフトウェアをパブリックドメインに置いた判断も普及を支えました。個人の発明と組織の判断を分けると、第21章の歴史が見えやすくなります。

Ian Hickson(イアン・ヒクソン) — 実装の振る舞いを仕様へ整理した人

HTML5 の編集を担った一人です。W3C の2014年 HTML5 勧告でも、編集者として名前を確認できます。パースやエラー回復を共通の規則へ整理する仕事は、本書の第2部と深く関わります。ただし、仕様全体を一人の成果とするものではありません。

ブラウザの実装を検査する Acid3 の開発にも関わりました。仕様の文章と、実装がその規則を満たすかを調べるテストの両方が必要だったことを示す例です。

Håkon Wium Lie(ホーコン・ウィウム・リー) — スタイルシートを提案した人

1994 年の Cascading HTML Style Sheets — A Proposalで、HTML の要素へ表示上の指定を与えるスタイルシートを提案しました。著者の希望だけでなく、利用者やブラウザの設定との調整も含む提案です。

構造を記す HTML と、表示を指定する CSS の分業は、その後も多くの人によって発展しました。第13章の font や center の話を読むときも、個々のタグの廃止だけでなく、表現をどの仕組みに担わせるかという変化として捉えられます。

付録 C HTML を巡る通説と誤解

広く言われているけれど、そのままだと雑——という HTML の「言い伝え」を、判定つきで並べます。多くは半分ホントです。なぜ半分なのかが分かると、HTML の見え方が変わります。

「h1 はページに 1 個だけ」 — 判定: 半分ホント

大見出しを 1 つに保つ設計は自然で、編集上の助言としては有効です。でも仕様の絶対規則ではありません。本当に問うべきは個数ではなく、見出し階層が論理的かどうか。「2 個書いたら違反」は言いすぎです。(→ 第17章)

「table は使うな」 — 判定: ウソ

批判されたのは、ページ全体を表で組むレイアウト用途の乱用です。料金表や比較表のようなデータの表には、table がいまも最適。要素そのものに罪はなく、用途を取り違えていただけです。(→ 第18章)

「div は悪」 — 判定: ウソ

意味のある要素で置けるところまで div にするのは問題ですが、汎用コンテナとしての div は必要です。悪いのは要素名ではなく、意味を渡せる場所まで全部ただの箱にしてしまうこと。(→ 第19章)

「b タグはもう使うな」 — 判定: 単純すぎ

b は削除されず、HTML5 で意味を持つ要素として再定義されました。強い重要性を表す strong とは役割が違い、使いどころもあります。標語だけでは足りません。(→ 第20章)

「alt は画像のおまけ」 — 判定: ウソ

alt は「画像が出ないときの代わりの文字」だけではありません。画像の用途に応じて代わりの情報を伝えます。情報画像なら内容、画像だけのリンクなら行き先や機能を表します。装飾だけなら alt="" とする場合もあり、すべてに詳しい説明を付ければよいわけではありません。(→ 第24章)

「<!DOCTYPE html> は書式の宣言」 — 判定: ほぼウソ

いまの DOCTYPE は、文書型を宣言する仕事をほとんど失っています。text/html として読む現行 HTML では、主に標準モードへ切り替える役割があります。DTD による検証の指示と考えると誤解します。XML の文書型宣言とは区別が必要です。(→ 付録D)

「 HTML は簡単」 — 判定: 入口だけホント

書き始めるのは簡単です。でも互換性、パース、エラー回復、仕様、支援技術まで含めると、背後はかなり深い。本書で見てきた「変な仕様」の数々は、その深さが表に出たものです。簡単に書けることと、単純な技術であることは別です。

付録 D 本編の外側にある HTML の補論

本編の流れには入れなかったものの、HTML の設計や実装を理解するうえで補助線になる論点を集めました。本文断片は、第4章の確認用文書の body に入れて試せます。DOCTYPE と文字コードの例は文書全体に関わります。

なぜ <!DOCTYPE html> と書くのか

HTML ファイルの先頭にある <!DOCTYPE html>。多くの人は「おまじない」として書いていますが、これにはちゃんと理由があります。しかも、見た目より変な事情を抱えています。

text/html として読む現行 HTML では、<!DOCTYPE html> は主にブラウザを標準モードにするために必要です。DTD を取得して文書を検証させる指示ではありません。XML の文書型宣言の働きと混同しないようにします。

昔の DOCTYPE は、こんなに長いものでした。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">

HTML5 では短い <!DOCTYPE html> が使われるようになりました。歴史的な DOCTYPE の有無や種類が描画モードの切り替えに使われてきたため、現行 HTML でもその互換性を踏まえた指定を残しています。

では、DOCTYPE を書き忘れるとどうなるのか。ブラウザは Quirks Mode に入り、1990 年代の挙動をわざと再現します。とくに有名なのが箱モデルの違いで、Quirks Mode では width に指定した値の中に padding と border が含まれてしまいます。同じ CSS なのに、DOCTYPE の有無だけでレイアウトが静かにずれる——これが昔の Web 制作者を苦しめました。

ちなみにモードは 2 つではなく、<!DOCTYPE html> の「標準モード」、何もない「Quirks Mode」、その中間の「Limited-Quirks(ほぼ標準)モード」の 3 つがあります。<!DOCTYPE html> という短い呪文は、スキーマの宣言ではなく、互換性のスイッチだったわけです。

</script> は文字列の中でも効いてしまう

script 要素の内容は、HTML パーサーのスクリプト用の状態で処理されます。JavaScript の文字列の境界を理解して読み飛ばすわけではないため、次の例では文字列中の </script> が終了タグになります。

だから、次のコードは壊れます。

<script>
  document.write("</script>");
</script>

人間には「</script> は文字列の中身」だと分かりますが、パーサーは文字列の途中でもおかまいなしに、最初の </script> でスクリプトを閉じてしまいます。回避するには、終了タグに見えないように書きます。

<script>
  document.write("<\/script>");
</script>

ここでの document.write は境界を示すための例で、新しい UI を作る方法として勧めるものではありません。

<\/script> の \/ は JavaScript では / と同じ意味なので、JavaScript が作る文字列の値は同じです。それでいて、HTML パーサーには </script> として見えなくなります。

& を書くだけでは & と表示されないことがある

HTML の & は文字参照の開始になり得ますが、すべてが変換されるわけではありません。

<p>A & B</p>
<p>&copy;</p>
<p>&amp;copy;</p>
<a href="?a=1&b=2">条件を指定</a>

最初は「A & B」、次は「©」、三つ目は文字列「©」として表示されます。最後の引用符付き属性値では、この &b は文字参照に変換されません。HTML ソースでアンパサンドを確実に表す表記として &amp; を使えますが、「そのままの & は必ず化ける」という説明は誤りです。

一部の古い名前付き文字参照は、本文中の &copy のようにセミコロンがなくても解釈されます。ただし、この場合はパースエラーです。属性値ではさらに、セミコロンのない名前の直後が ASCII 英数字や等号なら変換しないという互換規則があります。著者は省略に頼らず &copy; と書きます。

HTML の文字参照と、URL のパーセントエンコードは別の処理です。href="?a=1&amp;b=2" は DOM 上で ?a=1&b=2 になりますが、URL の値そのものへアンパサンドを含めたい場合のエンコードとは目的が違います。

<table> の直下に書いたテキストは表の外へ飛ぶ

第5章で、表は行グループの入れ子構造だと見ました。では、その構造を無視して、table の直下にいきなりテキストを書いたらどうなるでしょうか。

<table>おっと<tr><td>A</td></tr></table>

直感的には「table の中に "おっと" がある」と思いたくなります。ところが DevTools で DOM を見ると、こうなります。

おっと
<table>
  <tbody>
    <tr><td>A</td></tr>
  </tbody>
</table>

"おっと" は表の中ではなく、表の前へ追い出されます。 これは foster parenting(里親付け) と呼ばれる、HTML パーサーの正式な動作です。表の中に置けないものが来たとき、パーサーはそれを捨てず、表の直前へ「里子に出す」のです。

table の直下に書いたテキストが、DOM では table の中ではなく table の直前へ移動する様子の図
図 D-1 表の中に置けないテキストは、表の前へ追い出される。

第4章から第7章で見た補完やエラー回復は、ふわっとした「親切」ではなく、こうした名前の付いた規則(パーサーの挿入モードという状態機械)の集まりでした。foster parenting は、その機械じかけがいちばん見えやすい瞬間です。手元のブラウザで、ぜひ DevTools を開いて確かめてみてください。

文字コードを間違えると、文字が化ける

日本語の HTML は、実際のファイルを UTF-8 で保存し、head の早い位置に <meta charset="utf-8"> を置きます。宣言を書くだけで、別の文字コードで保存したバイト列が UTF-8 へ変換されるわけではありません。

宣言全体は文書の先頭から 1024 バイト以内に収める必要があります。ブラウザが早い段階で文字コードを判断できるようにするためです。

文字コードの情報源は meta だけではありません。通常の HTML の判定では BOM(先頭のバイト列による印)が優先され、HTTP の Content-Type の charset も文書内の meta より優先されます。利用者による明示的な指定などもあるため、文字化けの調査では実際の保存形式、レスポンスヘッダー、BOM、meta を照合します。UTF-8 の文書に対し、サーバーが別の文字コードを宣言していないかが確認点です。

半角スペースをいくら並べても、1 個にまとめられる

通常の CSS の空白処理では、連続する半角スペースは表示時に一つへ畳み込まれます。タブや改行の処理にも CSS の規則があり、DOM の文字列から空白そのものがすべて削除されるわけではありません。

<p>あ        い</p>

これを開いても、「あ」と「い」のあいだは 半角スペース 1 個相当しか空きません。スペースを並べて余白を作ろうとしても効かないのは、このためです(<pre> 要素や CSS の white-space を使えば別です)。HTML は、ソースの見た目の空白と、表示上の空白を、わざと切り離しています。

template の内容は別の DocumentFragment にある

template の中身は、パースされて DOM ノードになります。ただし、通常の子要素として表示されるのではなく、template.content が返す DocumentFragment に格納されます。複製しただけではページへ表示されません。

<template id="notice-template">
  <p>受付を開始しました。</p>
</template>
<div id="notices"></div>
<script>
  const content = document.querySelector('#notice-template').content;
  const copy = content.cloneNode(true);
  document.querySelector('#notices').append(copy);
</script>

内容の取得、複製、文書への挿入という三段階です。最後の append で、複製した p が表示側の DOM に入ります。元の template は雛形として残ります。

内容をその場で編集できる領域にする

要素に contenteditable を付けると、その部分はブラウザ上で直接書き換えられるようになります。

<p contenteditable>ここをクリックして書き換えてみてください。</p>

メモアプリのような編集欄を、textarea を使わずに作れます。ちなみに、開発者ツールのコンソールで document.designMode = "on" と打つと、現在の文書の内容を編集できる状態にできます。別フレームやアプリ固有の制御には制限があります。これは手元の表示の変更であり、サーバーへ保存されるわけではありません。保存の仕組みがなければリロードで失われます。HTML が「読む専用」ではないことを、いちばん手軽に体験できる小ネタです。

参考資料

あとがき

HTML は、学び始めると簡単に見え、深く見ようとすると急に歴史と実装の重さが現れる技術です。本書では、その奇妙さを笑うのではなく、理由を追いかけることを目指しました。

もし読み終えたあとに、ブラウザの補完や古い要素や仕様書の長さを見て「雑だから」ではなく「そうせざるをえなかった歴史がある」と思えるなら、この本の役目は果たせています。HTML は目立ちませんが、Web 全体の前提を今も支えている技術です。

こうした細部を「変だから」で済ませず、「なぜそうなったのか」まで掘る癖こそ、実は技術者の体力になります。tbody の補完や <blink> のような歴史的な逸話を、単発の豆知識ではなく、仕様と実装の文脈で読めるようになること。それが HTML 以外の技術を学ぶときにも効いてきます。本書が、その見方を手渡せていたならうれしいです。

FjordBootCamp について

FjordBootCamp は、現役エンジニアが運営するオンラインのプログラミングスクールです。Rails エンジニアコースとフロントエンドエンジニアコースがあり、文法や答えを暗記するより、調べながら理解し、自分で考えて学び続ける力を重視しています。

本書で扱った HTML の見方も、その姿勢とつながっています。特定のタグや通説を覚えて終わるのではなく、背景、仕様、実装を踏まえて「なぜそうなったのか」を考える。その習慣は、HTML 以外の技術を学ぶときにも役立ちます。

FjordBootCamp について詳しくは、公式サイトを参照してください。