core
core copied to clipboard
Incorrect vite warning regarding nesting tr directly in table element
Vue version
3.5.10
Link to minimal reproduction
https://play.vuejs.org/#eNp9kDELwjAQhf9KuVnqoJMUQcVBBxV1zFLrWatpEpKLFqT/3SSlrYN0CS/vexfe5QMLpeKXRZhBQlgqnhLOmYiihNILb6S/6Hkydkcg4xY51Y/ACMhkUtyKPH4YKdyLHx9nkMlSFRz1XlEhhWEwiwLxLOVcvrfBI21x1PrZHbPnH/9hKu8xOGg0qF/IoGOU6hypwevTDiunO1jKq+UuPQCPaCS3vmMTW1pxdbV/cqHtplRSUyHys1lXhMK0S/miPlmHPAP3rauB1fu6k3ga5pioof4CfnKCSA==
Steps to reproduce
Create a SFC with this content.
<template>
<table>
<tr></tr>
</table>
</template>
Vite will issue a warning:
warning: <tr> cannot be child of <table>, according to HTML specifications. This can cause hydration errors or potentially disrupt future functionality.
This warning is factually incorrect. The relevant spec says that a <table> may contain:
In this order: optionally a caption element, followed by zero or more colgroup elements, followed optionally by a thead element, followed by either zero or more tbody elements or one or more tr elements, followed optionally by a tfoot element, optionally intermixed with one or more script-supporting elements.
This is because htmlNesting.ts does not list tr as a valid child of table, despite the spec allowing it.
What is expected?
I expect no warning to be produced.
What is actually happening?
A warning is produced from vite:
warning: <tr> cannot be child of <table>, according to HTML specifications. This can cause hydration errors or potentially disrupt future functionality.
System Info
+-- @vitejs/[email protected]
+-- @vitejs/[email protected]
+-- @vue/[email protected]
+-- [email protected]
+-- [email protected]
`-- [email protected]
const table = document.createElement('table');
table.innerHTML = '<tr></tr>';
console.log(table.innerHTML);
https://html.spec.whatwg.org/multipage/tables.html#the-tr-element Maybe we should modify the warning message.
I’d suggest making the check more flexible, as it’s not very comprehensive at the moment.
const table = document.createElement('table'); table.innerHTML = '<tr></tr>'; console.log(table.innerHTML);
I don't think this is showing what you think it is showing.
This is a serialized DOM. In HTML, <tbody> is explicitly optional, by specification, as I've cited above.
I don't think this is showing what you think it is showing.我不认为这显示了你认为它所显示的内容。
This is a serialized DOM. In HTML,
<tbody>is explicitly optional, by specification, as I've cited above.这是一个序列化的 DOM。在 HTML 中,<tbody>根据规范是明确可选的,正如我上面引用的那样。
The directly generated <table><tr></tr></table> during SSR becomes <table><tbody><tr></tr></tbody></table> during CSR, as mentioned in the warning, resulting in hydration mismatch. This is what I want it to showing.
At a minimum the warning shouldn't claim that this is due to a spec. It seems this is constraint introduced by the implementation details of hydration. It could say something like this.
Despite being valid HTML, Vue SSR does not support direct nesting of
<tr>within a<table>. Doing this can cause discrepancies during hydration.
Personally, I'm not using SSR at all, so then I would know it's perfectly safe to ignore this warning.
Yes, the warning should be corrected. Perhaps we can try adding some transformations to similar behaviors so that <tbody> can be automatically generated.
I see the same thing. Fix the warning or provide a work around?
I'm seeing my stdout absolutely brim-filled with this warning and its accompanying template print out in both dev and build environments, to a point that it swallows and obscures any legitimate warnings. I do not use SSR so I'm not impacted by the discrepancy between the hydration feature implementation and the html specification.
Could this warning be predicated by SSR actually being used? Or could we disable this warning in any way? (except writing a custom vite logger to filter it out) If not I think this warning should be removed. Can't really expect everyone to change all table based components to an optional html syntax or lose functionality of the console.
I see that this issue is labeled as an edge-case, my vote is that it is not.
Can there be some additional smarts going on, such that this warning only displays if there is a mismatch detected between SSR and CSR?
Alternatively, Vue should not be adding
nodes because the browser is fine with no such node being present. Perhaps that's the real problem, that Vue should not be doing this at all?
Update... I just spent the time to modify source to always use either thead or tbody or both. I had maybe a dozen cases in which I was not using the optional node. So now I guess I'm on team tbody. Moooving on.
Can't really expect everyone to change all table based components to an optional html syntax or lose functionality of the console.
i agree with @aelgn - i dont think this is an edge case and, since this is optional html syntax, it makes it harder to debug actual errors
thead and/or tbody did fix the error bug which showed up when running: npm run dev. I just did an update and its still there: ── [email protected] ├── [email protected] └── [email protected]
Technically, that same spec says this too:
For historical reasons, certain elements have extra restrictions beyond even the restrictions given by their content model.
A table element must not contain tr elements, even though these elements are technically allowed inside table elements according to the content models described in this specification. (If a tr element is put inside a table in the markup, it will in fact imply a tbody start tag before it.)
https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
Also, even if someone is not using SSR, they shouldn't be writing invalid HTML. This particular case of tr inside table doesn't affect non-SSR users, but other cases should keep showing warnings.
Svelte also throws similar error:
Technically, that same spec says this too:
For historical reasons, certain elements have extra restrictions beyond even the restrictions given by their content model. A table element must not contain tr elements, even though these elements are technically allowed inside table elements according to the content models described in this specification. (If a tr element is put inside a table in the markup, it will in fact imply a tbody start tag before it.)
https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
That quote is regarding the document object, not the markup. The parenthesized part explicitly explains the relationship between the markup and the document object.
If you don't believe me, you can use the WHATWG's own validator service, which shows no errors or warnings for direct nesting of <tr> in <table>.
Also, even if someone is not using SSR, they shouldn't be writing invalid HTML.
Agreed. But this isn't invalid. I've quoted the relevant spec.
Svelte also throws similar error:
True. But at least the error message doesn't claim that it's because it's invalid, just that it breaks Svelte's assumptions. My preference would be that the warning was removable by default or configuration. But I'd settle for a message worded like svelte's, which makes it clear that the constraint is based on vue's implementation details.
For a Vue newbie, this was very confusing. Seeing something that is obviously valid HTML get flagged as an error/warning does not help me advance in my Vue knowledge. It is good that I stumbled across this thread. Thanks for fixing this in the future.
I was confused by this, but after reading over the links in this thread, I was able to work around the issue by wrapping my <tr> tags in a <tbody> tag and I no longer receive the vite warnings.
I was sad to see that the PR #12704 was not included in v3.5.17 released last week.
Until PR is merged / this issue otherwise fixed we are using a custom vite logger to filter out the offending message deluge to gain use of log output, if anyone is interested:
vite.config.ts
import { createLogger, defineConfig } from 'vite';
function customLogger() {
const logger = createLogger('warn');
const warn = logger.warn;
logger.warn = (message, options) => {
if (typeof message === 'string' && message.includes('<tr> cannot be child of <table>')) {
return;
}
warn(message, options);
};
return logger;
}
export default defineConfig({
customLogger: customLogger(),
...
});