Доводы против самозакрывающихся тегов в HTML

Источник: «The case against self-closing tags in HTML»
Хотя мы, как индустрия, в целом отказались от большинства требований XHTML, оформление самозакрывающегося тега, похоже, сохранилось, несмотря на то, что является пережитком спецификации, от которой отказались более 10 лет назад. Люди даже добавляют пробел перед /, который был добавлен для совместимости с браузерным движком из прошлого тысячелетия.

Давайте поговорим о />:

<input type="text" />
<br />
<img src="" />

Факты

Появление XHTML

В конце 90-х и начале 2000-х годов W3C очень любила XML и считала, что он должен заменить синтаксис HTML.

На это были веские причины. В то время не существовало спецификации парсинга HTML, поэтому, когда дело доходило до чего-то нетривиального, часто оказывалось, что 4 браузерных движка интерпретируют один и тот же HTML-документ четырьмя разными способами. С другой стороны, у XML есть полностью определённый синтаксический анализатор.

Но это было бы огромным изменением, чтобы сделать всё сразу, поэтому в 2000 году XHTML 1.0 стал рекомендацией и предложил писать HTML таким образом, чтобы он был совместим как с существующими парсерами HTML, так и с парсерами XML.

Это означало:

<!-- Вместо: -->
<HTML LANG="en">

<!-- Вы бы написали: -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

Ведь мы должны отпугивать новичков, верно?

<!-- Вместо: -->
<option value=foo selected></option>

<!-- Вы бы написали: -->
<option value="foo" selected="selected"></option>

Потому что в XML атрибуты требуют значений, и они должны быть заключены в двойные кавычки.

А также:

<!-- Вместо: -->
<img src="">

<!-- Вы бы написали: -->
<img src="" />

Потому что в XML теги должны явно закрываться, и в XML есть сокращение для самозакрывающихся тегов: />.

В XML это обычно оформляется как <this/>, без пробела перед /, но Netscape Navigator 4 не мог справиться с <input type="text"/>, где / сразу следует за атрибутом, поэтому спецификация рекомендовала ставить пробел перед /.

Но браузерам было всё равно

Эти правила предназначались исключительно для XML-парсеров, а так как документы обслуживались в формате HTML (если вы тот самый парень, который обслуживает свой сайт в формате application/xhtml+xml, можете мне не рассказывать), эти синтаксические "излишества" игнорировались.

При использовании <option selected="selected"> значение атрибута игнорировалось, поэтому <option selected=""> тоже работает, как и <option selected="false"> (false будет игнорироваться), но для "согласованности" было решено, что повторение имени атрибута — это хорошая идея.

Если вы забывали заключить атрибут в кавычки, браузер не жаловался, он просто продолжал работу по рендерингу страницы.

Если вы завершали тег символом />, браузер воспринимал это как ошибку парсинга и игнорировал её. И вот тут я начинаю с этим не соглашаться.

<br /> br закрыт. Этот текст не находится внутри br.

Но также:

<br> br закрыт. Этот текст не находится внутри br.

И вот здесь возникает путаница:

<div /> div открыт. Этот текст находится внутри div.

В XML <div /> будет самозакрывающимся div, но не в HTML. В HTML не /> закрывает br, а "br". Он входит в специальный список элементов, у которых не может быть дочерних элементов, и поэтому они самозакрываются. <div /> не самозакрывается, потому что "div" не входит в этот список.

Уход XHTML

"Переходная" фаза XHTML закончилась с выходом XHTML 1.1. На этом этапе спецификация требовала, чтобы документ обслуживался и разбирался как XML. Правила разбора XML были чётко определены, за исключением случаев, когда встречался неверный синтаксис. Лучшее, что могли сделать браузеры, — это просто показать страницу ошибки, иначе мы вернулись бы к тому, что браузеры просто придумывают что-то своё, и каждый ведёт себя по-своему. И на это браузеры ответили… нет, спасибо.

Ну, они не совсем отказались, они поддержали его, и поддерживают до сих пор. Вот валидный XHTML-документ, представленный как application/xhtml+xml, а вот не валидный. Но браузеры не рассматривали его как будущее.

Спросите себя: Если вы заходите на сайт местной больницы, чтобы узнать часы работы, какой браузер лучше выбрать: тот, который отображает часы работы клиники, или тот, который выводит сообщение об ошибке парсинга XML?

Одна из главных особенностей браузеров — их толерантность к ошибкам, и браузеры не были заинтересованы в отказе от этого.

В конце концов от XHTML отказались, потому что появилась новая вещь, с которой браузеры были более счастливы:

Появление HTML5

HTML5 появился на сцене в 2008 году, и одной из главных его особенностей была спецификация парсинга. И, в отличие от спецификации парсинга XML, в ней подробно описывалось, что должны делать браузеры, когда они сталкиваются со странной и некорректной разметкой.

Он отказался от всех XML требований, введённых в XHTML, и опирался на слабость HTML парсеров, существовавших в то время. Он учитывает />, но только для того, чтобы специально игнорировать его.

Появление SVG-в-HTML

В начале 2010-х возможность включения <svg> в HTML была прописана в спецификации и начала появляться в браузерах.

<div>
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="50" />
</svg>
</div>

Хотя SVG — это формат XML, при встраивании в HTML-документ он анализируется парсером HTML. Однако для повышения совместимости с копируемым содержимым SVG, когда парсер HTML находится внутри тега <svg>, он переключается в режим "чужого содержимого", где /> действительно имеет значение.

<div/> div открыт. Этот текст находится внутри div.

В то время как:

<svg>
<g><text>Это текст внутри группы</text></g>
<g/><text>Это текст вне группы</text>.
</svg>

Аналогично ведёт себя и другой внешний контент, например MathML.

И именно так обстоят дела сегодня. /> в HTML-документах в основном не имеет смысла, исключение составляет внешний контент.

Мнения

Хотя мы, как индустрия, в целом отказались от большинства требований XHTML, это оформление самозакрывающегося тега, похоже, сохранилось, несмотря на то, что является пережитком спецификации, от которой отказались более 10 лет назад. Люди даже добавляют пробел перед /, который был добавлен для совместимости с браузерным движком из прошлого тысячелетия.

Я считаю, что это непонятный пережиток прошлого, и не думаю, что такие инструменты, как Prettier, должны его продвигать. Чтобы доказать свою точку зрения, я отвечу на контраргументы, которые появились во время обсуждения в Twitter.

"Самозакрывающиеся теги облегчают чтение и полезны для новичков! Не нужно запоминать, какие теги являются самозакрывающимися"

За пределами внешнего контента элементы, которые самозакрываются, всегда будут самозакрываться. Все остальные элементы — нет.

<input />Этот текст находится за пределами input.

<input>Этот текст находится за пределами input.</input>

<div> Этот текст находится внутри div.</div>

<div />Этот текст находится внутри div.

Приведённые выше примеры /> просто ничего не делают. Единственный способ узнать, что <input /> допустим, а <div /> — нет, — это выучить и запомнить, какие элементы самозакрываются. Это отстой, но так оно и есть.

Но должен ли /> работать, чтобы быть полезным? Комментарии к коду не "работают". Как и />, они являются указанием, они могут вводить в заблуждение, но это не является веским аргументом в пользу удаления комментариев к коду. Проблема с /> в том, что он не выглядит как комментарий, и, что ещё хуже, он не всегда ведёт себя как комментарий из-за правил, касающихся внешнего контента.

Я думаю, это особенно плохо для новичков. Представьте, что вы никогда раньше не видели <img src="...">. Вы увидите, что, в отличие от других элементов, у него нет закрывающего тега. Отладчики и валидаторы не жалуются на это, предполагая, что в этом элементе есть что-то особенное, что вам нужно узнать — его не нужно закрывать, он закрывается сам, и это особенное поведение.

А теперь представьте, что вы никогда не видели <img src="..." />. Вы изучаете этот новый синтаксис и узнаете, что он означает, что тег "самозакрывающийся". Почему бы вам не предположить, что <iframe /> тоже самозакрывающийся? Или что <img src="..."></img> является допустимым? Учитывая это, мне становится грустно от того, что MDN использует самозакрывающиеся теги в своей документации для новичков.

"Это соответствует JSX"

JSX и HTML — это разные форматы. Они не соответствуют друг другу. Притворяться, что они соответствуют друг другу — значит вводить в заблуждение.

<div>
<span>Hello</span>
<span>world</span>
</div>

Приведённый выше HTML отобразится как "Hello world".

const Component = () => (
<div>
<span>Hello</span>
<span>world</span>
</div>
);

Приведённый выше JSX отображается как "Helloworld". Форматы работают по-разному.

<main>
<div />
Hello
</main>

В приведённом выше HTML текст находится внутри div.

const Component = () => (
<main>
<div />
Hello
</main>
);

В приведённом выше JSX текст находится вне div. Это другая система!

<div classname="foo"></div>

В приведённом выше HTML создаётся div с атрибутом classname.

const Component = () => <div classname="foo"></div>;

Приведённый выше JSX создаёт div с атрибутом class. Ок, это скорее особенность React, чем JSX, но это действительно распространённый способ использования JSX.

Я не думаю, что здесь есть аргумент в пользу последовательности. Несмотря на визуальное сходство, это разные форматы, работающие по-разному.

"Это значит, что я могу парсить HTML с помощью парсера XML"

Считайте меня пуристом, но если я хочу парсить HTML-документ, я буду использовать HTML парсер. Я бы не стал пытаться написать JSON так, чтобы его можно было парсить YAML парсером, поэтому я не вижу причин, почему я должен делать то же самое с HTML и XML.

Существует множество отличных библиотек парсинга HTML, практически для всех языков. И поскольку парсер указан, результаты будут последовательными.

"Это ускоряет парсинг разметки"

Я слышал подобное несколько раз, и думаю, это происходит из предположения, что чем больше информации вы даёте, тем лучше процессор может оптимизировать, или что-то в этом роде. Но давайте сравним их:

Парсинг <br>:

  1. <: О, вот и новый тег!
  2. br: Теперь я знаю, какой элемент нужно создать.
  3. >: Конец тега, и br не попадает в стек открытых элементов, потому что это один из самозакрывающихся элементов.

Парсинг: <br/>:

  1. <: О, вот и новый тег!
  2. br: Теперь я знаю, какой элемент нужно создать.
  3. /: Я просто проигнорирую это.
  4. >: Конец тега, и br не попадает в стек открытых элементов, потому что это один из самозакрывающихся элементов.

Так что технически <br/> разбирается медленнее, поскольку содержит лишний /. Он не медленнее в любом значимом смысле, но точно не быстрее.

"Выглядит симпатично"

Конечно, это субъективно. Я думал, что /> выглядит уродливо, когда впервые работал с кодовой базой, требующей его, но я привык к нему. Я также привык к его отсутствию.

Если целью является красота, мы можем использовать <input type="text" 🛑>!

Но если серьёзно, я думаю, что эстетика должна отойти на второй план, учитывая, насколько вводящим в заблуждение является синтаксис.

Prettier должен быть более категоричным

Я уважаю подход Prettier "наш путь или дорога", но не думаю, что в данном случае он соответствует действительности.

Он изменяет <br> на <br />, но ничего не сделает с <div />. На самом деле, если вы дадите ему <div/>, он переформатирует его в <div />.

Я думаю, что Prettier должен либо отказаться от /> в случаях, когда это не имеет смысла для парсера, либо исправить случаи, когда /> активно вводит в заблуждение. Например:

<div />
Hello

…необходимо переформатировать в:

<div>Hello</div>

…аналогично тому, как он уже обрабатывает незакрытые теги.

Или может HTML разрешит закрывающие теги везде

Основная часть проблемы заключается в том, что /> иногда игнорируется, а иногда нет, в пределах одного и того же HTML-документа. Можно ли сделать так, чтобы правила парсинга переключались таким образом, чтобы /> всегда был значимым? Например, чтобы <div/> действительно был самозакрывающимся. Я отправил запрос на это, но подозреваю, что это невозможно из-за несовместимости с существующими библиотеками, особенно чувствительными к безопасности.

Дополнительные материалы

Предыдущая Статья

Использование CSS :has() в реальных условиях

Следующая Статья

Шпаргалка по TSConfig