Search code examples
javascriptvue.jsvue-componentvuejs3

Vue 3 h() with custom component


I've read about 100 threads about this and still can't quite get it.

I'm trying to parse a string template from a backend API and render it, using custom components.

I have aliased esm build in vite.config.js:

  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
      vue: 'vue/dist/vue.esm-bundler.js',
    },
  },

Here is my component:

<script>
import { defineAsyncComponent, h } from 'vue';

export default {
  components: {
    PageCalculator: defineAsyncComponent(() => import('@/components/PageCalculator.vue')), /* eslint-disable-line */
  },
  props: {
    content: {
      type: String,
      default: '',
    },
  },
  render() {
    const render = {
      template: `<div class="content">${this.content}</div>`,
    };
    return h(render);
  },
};
</script>

This gives the error Failed to resolve component.

how can I go about dynamically loading the component? I have a large number of components that the backend can send back, so I wanted to lazily load them.

Here is how it's used from the outer component:

<app-page :title="page.title" container-class="w-full">
  <compiled-content :content="page.content_html" />
</app-page>

The content of the HTML might look like this (the content prop):

<p>this is a nice sentence</p>
<page-calculator />
<p>this is a <router-link :to="{name: 'home'}">link</router-link></p>

Solution

  • Wow, this was such a painful journey, yet the answer is so simple.

    here is how you render vue components from the server dynamically, even if they're mixed within html

    I'm extremely surprised the answer is not in more places... people had me registering global components, using methods that didnt't work. I actually eventually just stumbled across it on my own while trying to piece together tons of different answers.

    Here's the final version that:

    1. compiles vue code in realtime from the server
    2. does so with components and methods IN that component, without requiring global components
    <script>
    import { h } from 'vue';
    import AppAlert from '@/components/AppAlert.vue';
    
    export default {
      props: {
        content: {
          type: String,
          default: '',
        },
      },
      render() {
        const r = {
          components: {
            AppAlert,
          },
          template: `<div class="content">${this.content || ''}</div>`,
          methods: {
            hello() {
              // method "hello" is also available here
            },
          },
        };
        return h(r);
      },
    };
    </script>
    

    If you have tons of components in your content you can also make them all async components:

    components: {
      AppAlert: defineAsyncComponent(() => import('@/components/AppAlert.vue')), 
      ...