dx(runtime-core): warn on render child nodes in void element
fix #12136
Size Report
Bundles
| File | Size | Gzip | Brotli |
|---|---|---|---|
| runtime-dom.global.prod.js | 100 kB | 38 kB | 34.2 kB |
| vue.global.prod.js | 159 kB | 57.9 kB | 51.4 kB |
Usages
| Name | Size | Gzip | Brotli |
|---|---|---|---|
| createApp (CAPI only) | 46.9 kB | 18.3 kB | 16.7 kB |
| createApp | 55 kB | 21.3 kB | 19.4 kB |
| createSSRApp | 59 kB | 23 kB | 20.9 kB |
| defineCustomElement | 59.8 kB | 22.8 kB | 20.8 kB |
| overall | 68.7 kB | 26.3 kB | 24 kB |
@vue/compiler-core
pnpm add https://pkg.pr.new/@vue/compiler-core@12137
@vue/compiler-dom
pnpm add https://pkg.pr.new/@vue/compiler-dom@12137
@vue/compiler-ssr
pnpm add https://pkg.pr.new/@vue/compiler-ssr@12137
@vue/reactivity
pnpm add https://pkg.pr.new/@vue/reactivity@12137
@vue/compiler-sfc
pnpm add https://pkg.pr.new/@vue/compiler-sfc@12137
@vue/runtime-core
pnpm add https://pkg.pr.new/@vue/runtime-core@12137
@vue/runtime-dom
pnpm add https://pkg.pr.new/@vue/runtime-dom@12137
@vue/server-renderer
pnpm add https://pkg.pr.new/@vue/server-renderer@12137
@vue/shared
pnpm add https://pkg.pr.new/@vue/shared@12137
@vue/compat
pnpm add https://pkg.pr.new/@vue/compat@12137
vue
pnpm add https://pkg.pr.new/vue@12137
commit: 7b68bf7
I don't think this is a correct fix.
- Firstly, rendering elements inside an
<img>tag is unreasonable. - Secondly,
<img><span>1</span></img>, the internal<span>will be rendered. But<component is="img"><span>1</span></component>, the internal<span>will not be rendered. see Playground
If we really want to solve this issue
- the internal
<span>should be rendered. When mounting an element, if it is a void tag, its children should be rendered as sibling nodes. Not sure if this is worth it. - or, gives a warning if the user tries to render nodes in the void tag
you're right, hydrate checks shouldn't be skipped no matter what.
@yangxiuxiu1115
The solution I recommend is to give warnings in the DEV. Both ssr and client, when render the child node to a void tag, give the user a warning.
Note, this only happens in <component is="VOID_TAG">child nodes</component>
Would you like to give it a try?
sure, I'd love to.
I'm not sure about this.
Consider this case:
<script setup>
import { ref } from 'vue'
const tag = ref('input')
</script>
<template>
<component :is="tag">
<template v-if="tag !== 'input'">
Content
</template>
</component>
</template>
This will show the warning and fail with SSR because the input has children, but it only has children because the v-if is rendering a comment node. I feel that using v-if like this (or even an empty v-for, like the original reproduction) should be treated as not having children for this purpose. The comment nodes are an implementation detail.
I also find it interesting that introducing an explicit v-slot attribute changes how it behaves:
<script setup>
import { ref } from 'vue'
const tag = ref('input')
</script>
<template>
<component :is="tag">
<template v-slot v-if="tag !== 'input'">
Content
</template>
</component>
</template>
The extra v-slot changes how it's compiled, so there's no longer a comment node. SSR hydration works fine in that case.
My current feeling is that we should:
- Only show the warning if there are 'real' children, i.e. not Vue-generated comment/fragment nodes.
- Discard the children.
Secondly,
<img><span>1</span></img>, the internal<span>will be rendered.
I think that example is misleading.
From the perspective of an HTML parser (or the template parser in this case), the <span> isn't considered to be internal in the first place. The <span> isn't moved outside the <img>, it was never inside it to begin with.
This is different from an example like <table><div></div></table>. In that scenario, the <div> is inside the <table> and is then moved outside because it isn't a valid child.
But that isn't what's happening with the <img>/<span> example. In that case the <img> is closed immediately, so when the parser encounters the <span> it just adds it as the next node, after the <img>.