网页
综合
更新时间 2023-03-29 00:00:00
Jumping HTML tags. Another reason to validate your markup — Vadim Makeev
自动蜘蛛
pepelsbey.dev
正文约 6000 字
Skip to main content
If you’re building for the Web, you’re most likely writing HTML. It could be JSX, Markdown, or even Dart in your code editor, but eventually, it gets compiled to some sort of markup. And the further away from the actual tags you get, the less idea you have of what gets there. For most developers, it’s just an artifact, like a binary file.
And this is fine, I guess 🤔 We use abstraction layers for solving complex problems. At least, that’s what they say. Don’t get me wrong, it often gets ugly: ask HTMHell for examples. Fortunately, in most cases, browsers are smart enough to handle our poor markup.
But sometimes browsers take our mistakes personally, and tags start jumping around 😳
I’m sure most of the developers haven’t read the HTML spec. They might have stumbled upon it, but I get it: it’s not something you read for fun. But somehow, they more or less know how HTML works and some basic rules, including tags nesting. It’s like our native language: we’ve learned it by listening to our parents and perfected it by speaking.
For example, we all have learned that <ul> can only contain <li>. Because, you know, it’s an unordered list and a list item. And just like in a natural language, we can move things around, and it will still make sense. We can use paragraphs instead of list items, and it will still be fine: no bullets and some extra margins, but nothing too scary.
In this case, the source markup will be represented exactly the same in the DOM tree. But it feels wrong, right? It does, but I wouldn’t rely on this feeling too much. HTML is a programming language, after all. It’s wrong because the HTML spec says so:
The <ul>’s content model allows zero or more <li> elements and nothing else, apart from some scripting elements. But browsers don’t care about it, so why should we? There are many good reasons to align your markup with the spec, but let me give you the one that’s rarely mentioned.
Let’s turn the whole thing upside down and put the <ul> inside the <p>:
The <p>’s content model allows only phrasing content, and <ul> is flow content. But who cares? Browsers are still going to render a list inside a para… What the hell? 😬
Yes, our <ul> just tore the <p> apart by being a wrong element. And this is a common behavior among modern browsers, all according to the spec. I couldn’t find a specific place in the spec that says “tear the <p> apart, but keep <ul> intact” (the parsing section is pretty huge), but it should be in there one way or another since browsers agree on this behavior.
My favorite section is “Unexpected markup in tables”, which starts with:
Error handling in tables is, for historical reasons, especially strange.
And then tries to explain how browsers should handle the following markup:
Look at the result, you’ll be fascinated. Now, this is something I’d read for fun! 😁
But it’s not just paragraphs that hate you. The tables are pretty picky, too. They don’t like to host random elements inside. A <div> inside of the <table> will jump out of it, but the <table> will hold it together and won’t split, unlike the <p>.
But if you decide to nest table parts outside of the <table>, they’ll just disappear 🫥 No tags, no problems.
Nesting interactive elements into one another is a bad idea on its own, but sometimes it comes with special effects. If you nest buttons or links, the inner one will jump out of it.
But if you nest a button inside a link or vice versa, nothing will happen. They don’t like to nest only the ones of their kind (some family issues, perhaps). But in this case, it obviously looks broken, right? We almost expect it to fail by common sense. Let’s look at something a bit more practical.
We all know this “product card” pattern: title, description, some picture, and the whole thing is a link. According to the spec, having this card wrapped in a link is fine. But once there’s a link somewhere in the description…
It’s even hard to describe what happens here. Just look at the DOM 😳
Another problem with this approach is that even if you avoid the nesting links, the whapper link’s content is not a good accessible description. On this website, I used the trick with an absolutely positioned pseudo-element. You can read more about it in Heydon Pickering’s “Cards” article.
Everything that we’ve just discussed was the plain markup. But I wonder what a DOM generated with JavaScript would look like. This would be useful to understand for all JS frameworks out there. Remember all those abstract layers? Yeah. But let’s start with the basics.
There are two ways of generating DOM with JavaScript: setting the whole thing to innerHTML (or similar) or one element at a time via createElement() from DOM API. Let’s start with the first one:
Here we’re asking the browser to make sense of this string and build a DOM tree based on that. You might even call it declarative. In this case, we’ll get the same result as with the plain markup before: the <p> is torn apart again 🫠
But if we specifically ask the browser to create elements, combine them in a certain way, and then append them to the <body>:
Then we’ll get exactly what we’ve asked for:
It means that by using the DOM API, we can force the browser to render any nonsense markup we want. Let’s see what JS frameworks chose to do with this.
As I mentioned initially, we often use abstraction layers to generate markup. Somewhere deep down the framework guts, the actual markup is produced. After some brief testing, I’ve found that all major frameworks could be split into three groups based on how they handle incorrect nesting:
I tested the p > ul example in a few major frameworks: React, Angular, Svelte, Vue, Preact, and Lit. It should give us a good idea of how things work across the board.
React Angular Svelte
First of all, why would they even care? One of the reasons is consistency between server-rendered and client-rendered markup. Yes, the framework will generate the same markup in both cases, but the server one will be tran
本页为82搜索生成的网页快照,内容来自站内索引;演示数据的原文链接仅作示例。