core icon indicating copy to clipboard operation
core copied to clipboard

dx(runtime-core): warn on render child nodes in void element

Open yangxiuxiu1115 opened this issue 1 year ago • 7 comments

fix #12136

yangxiuxiu1115 avatar Oct 10 '24 07:10 yangxiuxiu1115

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

github-actions[bot] avatar Oct 10 '24 07:10 github-actions[bot]

Open in Stackblitz

@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

pkg-pr-new[bot] avatar Oct 10 '24 07:10 pkg-pr-new[bot]

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

edison1105 avatar Oct 10 '24 09:10 edison1105

you're right, hydrate checks shouldn't be skipped no matter what.

yangxiuxiu1115 avatar Oct 10 '24 10:10 yangxiuxiu1115

@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?

edison1105 avatar Oct 10 '24 10:10 edison1105

sure, I'd love to.

yangxiuxiu1115 avatar Oct 10 '24 11:10 yangxiuxiu1115

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:

  1. Only show the warning if there are 'real' children, i.e. not Vue-generated comment/fragment nodes.
  2. 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>.

skirtles-code avatar May 24 '25 19:05 skirtles-code