Skip to content

HeavyMedl/vue-3-ssr-vhtml-custom-component-bug

Repository files navigation

vue-3-ssr-vhtml-custom-component-bug #6553

Using v-html directive on custom component is not server-rendering properly.

This repo is a stripped down version of https://github.com/vitejs/vite/tree/main/playground/ssr-vue

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>

About

Using `v-html` directive on custom component not server-rendering

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published