Using `v-html` directive on custom component is not server-rendering properly
Vue version
3.2.37
Link to minimal reproduction
https://github.com/HeavyMedl/vue-3-ssr-vhtml-custom-component-bug
Steps to reproduce
git clone https://github.com/HeavyMedl/vue-3-ssr-vhtml-custom-component-bug.git
cd vue-3-ssr-vhtml-custom-component-bug/
npm ci
In development mode
npm run dev
Or, in production mode
npm run build
npm run serve
Visit http://localhost:6173
What is expected?
The server-rendered HTML to reflect the same structure which is generated by hydrating client-side without server-rendering, and for the DOM to reflect this.
<div>
<section>This is a section without using v-html</section>
<div class="custom-component">
<div>I replace whats inside custom component</div>
</div>
<article class="custom-dynamic-component">
<div>I replace whats inside custom component</div>
</article>
<div class="custom-dynamic-component">
<!--[-->Dynamic component without v-html<!--]-->
</div>
<div><div>Normal div using v-html</div></div>
</div>
What is actually happening?
In either case, development or production, observe the resultant HTML generated by renderToString
. The HTML from v-html
is missing:
<div>
<!-- This works -->
<section>This is a section without using v-html</section>
<!-- This fails: v-html -->
<div class="custom-component"><!--[--><!--]--></div>
<!-- This fails: v-html -->
<article class="custom-dynamic-component"><!--[--><!--]--></article>
<!-- This works -->
<div class="custom-dynamic-component">
<!--[-->Dynamic component without v-html<!--]-->
</div>
<!-- This works -->
<div><div>Normal div using v-html</div></div>
</div>
We can see the difference in the compiled bundles
Client bundle (expected)
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_custom_component = resolveComponent("custom-component");
const _component_custom_dynamic_component = resolveComponent("custom-dynamic-component");
return openBlock(), createElementBlock("div", null, [
_hoisted_1,
createVNode(_component_custom_component, { innerHTML: $data.customComponentHTML }, null, 8, ["innerHTML"]),
createVNode(_component_custom_dynamic_component, {
tag: "article",
innerHTML: $data.customComponentHTML
}, null, 8, ["innerHTML"]),
createVNode(_component_custom_dynamic_component, { tag: "div" }, {
default: withCtx(() => [
_hoisted_2
]),
_: 1
}),
createBaseVNode("div", { innerHTML: $data.normalDiv }, null, 8, _hoisted_3)
]);
}
Server bundle
function _sfc_ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
const _component_custom_component = resolveComponent("custom-component");
const _component_custom_dynamic_component = resolveComponent("custom-dynamic-component");
_push(`<div${ssrRenderAttrs(_attrs)}><section>This is a section without using v-html</section>`);
_push(ssrRenderComponent(_component_custom_component, null, null, _parent));
_push(ssrRenderComponent(_component_custom_dynamic_component, { tag: "article" }, null, _parent));
_push(ssrRenderComponent(_component_custom_dynamic_component, { tag: "div" }, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(`Dynamic component without v-html`);
} else {
return [
createTextVNode("Dynamic component without v-html")
];
}
}),
_: 1
}, _parent));
_push(`<div>${$data.normalDiv}</div></div>`);
}
System Info
System:
OS: macOS 12.5.1
CPU: (10) arm64 Apple M1 Max
Memory: 385.73 MB / 32.00 GB
Shell: 5.8.1 - /bin/zsh
Binaries:
Node: 16.13.0 - ~/.nvm/versions/node/v16.13.0/bin/node
npm: 8.1.0 - ~/.nvm/versions/node/v16.13.0/bin/npm
Browsers:
Chrome: 104.0.5112.101
Firefox: 104.0
Safari: 15.6.1
npmPackages:
vue: ^3.2.37 => 3.2.37
Any additional comments?
My use case is that I have a private Vue 3 component library using vite
. Consumers need to inherit the CSS delcarations of components from the library, but should be able to inject escaped HTML using the v-html
directive.
import { CompA } from '@company/lib'
<template>
<div>
<comp-a v-html='htmlFromCMS'>
</div>
</template>
<script>
export default {
name: 'App',
components: {
CompA,
}
props: {
htmlFromCMS: {
type: String,
default: '<div>My HTML</div>',
},
},
}
</script>
Ugh, you should never use v-html
on a component. A component is supposed to manage its own DOM tree. Using v-html
completely defeats the purpose of using a component at all, why not just use it on a plain <div>
?
We should probably just make this a compile time error.
Ugh, you should never use
v-html
on a component. A component is supposed to manage its own DOM tree. Usingv-html
completely defeats the purpose of using a component at all, why not just use it on a plain<div>
?We should probably just make this a compile time error.
It is quite practical for reusing styles, event handlers, etc. for static (<slot />
) and dynamic (from backend/CMS) content.
Let's say I have a <Text></Text>
component which has special styles for text (e.g. setting h1-h6 font size & weight).
I can use this component as usual by putting inside some HTML.
And in other places, where content is dynamic, I can just use the same component but provide its innerHTML
with v-html
.