vuejs / core

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
https://vuejs.org/
MIT License
47.65k stars 8.33k forks source link

`<component>` of void element with content causes hydration mismatch #12136

Open Ingramz opened 1 month ago

Ingramz commented 1 month ago

Vue version

3.5.11

Link to minimal reproduction

https://play.vuejs.org/#__SSR__eNqFVNtu00AQ/ZXBLwEptUnKTVZSCapKwEOpAPGCK+TYE2fLene1u3ZTRf53ZnxJ3CgKebAyZy4+M3PGu+CjMWFdYRAHC5dZYTw49JW5SpQojbYerrXyqPytzhHWVpcwCaMRxsmTRCUq08p5oCd7YAm/EwWw4weAfzIYwwQlluScTDtUpSWjZrBT761YVR5dDLumB/uCcVePf33NUV2P233RFiab4M8opYZHbWX+gih2vqHuqTpH/EYcRVmM0WdMwdmMQjbeGxdHUVqnPrUuLITfVKvKoe1bCDNdRlX0bjb/MHv9fjKFVDLLXxWGD24yIjZu+n6PDv4OYes+UYuoWxqtiwyPpZGpR7IAFrmoIZOpc8sk6AsmQesi53ir9cVaWwpSbAg1vD0JIGao95AZdYUjqkz/FtHofcE08I4S16KgbrQiPbXz5VeXRki034wXJJEkoJF1JJIgpQU9fm0xbyvsO6ScDWZ/T+APbstYEtxZpMHWRGrvo6EXSKTZffPjliQwcpY6ryRFn3F+R6dlxRy7sE+Vyon2KK5l+6W9CqGKn+5mS2NyQ1NMdNgMR9NdXJ9p/UD3MnzT5iWqoSkendbxXYJMVUEb8VSNxp7jWii8s9q4xQ54TTGk6gmaq5evzquD16IVn2osWCGcG7Laac/1hVgPEJ8GLJfLw3W0ASuh8iHkcA3/19ecBdZmnVbZfCSzPcXOHvhTPZQOT3JsvwTEYtcNI2QbmuaZVI91+6dGy1ukUV+Gb8PZLGj+AeRfpSE=

Steps to reproduce

Reproduction contains an example where a component called ContentNode references itself while rendering a tree of content elements. When enabling SSR, hydration mismatches are reported, but when comparing the generated HTML, everything seems OK.

What is expected?

No hydration mismatches.

What is actually happening?

Hydration mismatches are reported.

System Info

No response

Any additional comments?

No response

edison1105 commented 1 month ago

I'm not sure if this counts as a bug, but the issue is that the vnode of img contains children, which is unreasonable because the img tag is self-closing and cannot have children. Maybe we should ignore the children of void tags when creating vnode.

You shouldn't add child nodes to the img tag, as it is unreasonable. You can modify it like this:

<template>
  <component :is="node.name" v-if="node.type === 'element' && node.name !== 'img'" v-bind="node.attributes">
    <ContentNode v-for="node2 in node.content" :node="node2" />
  </component>
  <template v-else-if="node.type === 'text'">{{ node.text }}</template>
  <img v-else-if="node.name === 'img'" v-bind="node.attributes" />
</template>

Note: this will still report a mismatch in Playground because there is a bug in Playground, but it works correctly when the project is started locally.

Ingramz commented 1 month ago

Thank you for investigating, I can confirm that you are correct that creating a dedicated else-if branch for void elements with no content easily works around this. Playground reporting hydration mismatches despite not using a void element was driving me insane. 🤦

Also now that you explained that the img vnode still contains children, then if I understand correctly, when comparing to the rendered HTML, which does not contain any children, it triggers the mismatch.

As for how to better handle this situation, I have no strong preference, but if the children on a void element vnode do not contribute to rendered HTML / DOM, they should be treated as valid and matching. If something visible was rendered, then that should still continue be treated as hydration mismatch due to the resulting HTML being invalid.