3

I'm trying render a component from string but I didn't succeed. My codes are bellow:

<template>
<div v-html="beautifyNotification(notification)"></div>
</template>

<script>
import { Link } from '@inertiajs/inertia-vue3'
import {compile,h} from "vue"

export default {
    components: {
    },
    props: {
        notifications: Object
    },
    methods: {
        beautifyNotification (ntfction) {
            return h(compile(`<Link :href="`+ntfction.from.username+`"
                    class="h6 notification-friend">`+ntfction.from.name+`
            </Link>, commented on your new
            <Link href="#" class="notification-link">profile status</Link>.`))
        },
    }
}
</script>

I tried render component with h and compile but it returned object object

4
  • I don't see why you need to render your components like that in your example. Just use the <Link /> inside the template. That's exactly how the components are supposed to be used 99% of the time. Commented Feb 2, 2022 at 15:04
  • Check out how <todo-item /> is used in the docs: v3.vuejs.org/guide/introduction.html#composing-with-components Commented Feb 2, 2022 at 15:06
  • Actually i want to learn how to render a string but if I don't find a solution I'll do like what you said Commented Feb 2, 2022 at 15:08
  • if you want to render an HTML string (literally a "<div>This is a string HTML</div>") then you use v-html. Docs: v3.vuejs.org/guide/template-syntax.html#raw-html Commented Feb 2, 2022 at 15:11

3 Answers 3

4

To anyone else hopefully finding this, so you don't have to go down the hellish path of wrong answers that I did (very surprisingly no one's SO answers work properly, even though they specify vue 3). Even within the 'advanced' area of vue discord i was getting weird/wrong answers, which was surprsing.

The answer above by maembe didn't work for me either (it doesnt have access to any other components/scope)

This is what worked perfect for me.

Heres mine CompiledContent component that accepts html + vue components mixed together in a single string from backend.

Notice it doesn't use the compile function at all

<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')), 
  ...
Sign up to request clarification or add additional context in comments.

8 Comments

This doesn't appear to render anything for me, nothing in the DOM, but the component does exist in the Vue component tree with all the correct props/computed, the render code for it is am empty function.... The content of your AppAlert may be important here no?
Just more strings of answers that don't appear work without enough explanation in them to understand what they are trying to do in the first place :(
So, for this to work you need to have runtime compilation enabled, this method of compilation shows no console errors, it just fails silently and renders an empty comment otherwise. stackoverflow.com/a/74305016/3547347
This causes performance problems. We should consider alternatives like using vue's low-level APIs or a simple template compiler.
@TachibanaShin you're welcome to add another answer instead of just saying it's un-ideal. I have spent hours and hours in discord with all top level Vue people who still didn't give working answers. I'm open to seeing a better way, but nothing else actually worked, or people didn't know
|
3

I will answer this question even though it seems late Below I use the static compilation method

Advantage:

  • Fast and less CPU intensive: it uses native compilation instead of bundling the entire heavy vue compiler into your package
  • Saves memory: This method only uses vue's prototype functions instead of including the compiler so it doesn't consume any memory

Defect:

  • Does not support SFC syntax: this is a native compiler so it will not accept regular {{ value }} (although you can add one) but it reluctantly supports event syntax @click=... and compopnent prop support

import { Fragment, h, Component, defineComponent } from "vue"

function buildElement(element: ChildNode, components: Record<string, Component>) {
  if (element.nodeType === 1) {
    const { attributes } = element as HTMLElement

    const name = element.nodeName
    const attrs: Record<string, unknown> = {}
    for (let index = 0; index < attributes.length; index++) {
      const attr = attributes.item(index)
      attrs[attr.name] = attr.value
    }

    const children = compilerChildren(element as HTMLElement, components)

    const component = components[element.nodeName.toLowerCase()]
    console.log(element.nodeName, components)

    return component ? h(component, attrs, {
      default: () => children
    }) : h(name, attrs, children)
    // console.log({ name, attrs, children })
  }

  return element.textContent
}

function compilerChildren(parent: HTMLElement, components: Record<string, Component>) {
  // deflag
  const children = []
  parent.childNodes.forEach(item => {
    switch (item.nodeType) {
      case 1:
      case 3:
        // normal element
        children.push(buildElement(item, components))
        break
      default:
        console.warn("Can't resolve this element: ", item)
    }
  })

  return children
}


export function compiler(html: string, components: Record<string, Component>) {
  const comps = Object.fromEntries(
    Object.entries(components).map(([name, component]) => [name.toLowerCase(), component])
  )
  return () => h(Fragment, null, compilerChildren(
    new DOMParser().parseFromString(html, "text/html").body,
    comps
  ))
}

Here is the common version so people who don't have in-depth knowledge of vue's low-level APIs can understand I would normally use openBlock

Demo:

enter image description here

https://play.vuejs.org/#eNqVVktv4zYQ/itT9VAZ60gJclNsF002QVrkhU3QSxSgskRLSihSIKnYgeH/3uFQkqW4u+ge/OBwHt88OVvvj7oO3hvmRd5Mp6qsDWhmmhp4IvJ57Bkde4tYhCE8FaUGbRJlmALDqponhgHSGl2KHP5uGJzCSMcCHq8uNAlfFCx9A9kYKIypdRSGaPNVB1LlYVKXoV6lR070iESDwlT81yElFmVVS2VgC6ms6pIjih2slKwg9oKQy7xMw+4m9nr2c8mzPZs9WXctQyxSKbRFVHGYwz+xuGacS2DvTH1IwX6B2VKh77G4/QCRVOTsjPSlkkuF0Vlyq2rxlKRFuUxEAo9FKWZkhQQ7BRS7A+lcMSZQ/BtLDdChFY1QMivfMdofnCHjusxMEcHJ8XG9OYOClXlh+uMySd9yJRuRHZHiyKlCvbMQlTgcQ22fuGIBcF2CaSEyziomDJQCrNAYcV2KNxR5wB9H633tbGEUu7g+FnKNce1y4ts4TzF9pHE3sTIuwQ5iV1IEaEbCISkeXOCRvMC/S5l9wNbyHgZgXZSGncViRzZIwJtiKSOuVZkHr1oKrHcStuFw+O5rUyLu2IucWnuXYD2s/yKaUQ2bdvTUlvN/0F/1xtJi70ExzdQ7Fkd/h52TM+OuLx/v2Ab/95eVzBqO3D+4/Ma05I3F6NjO0WEq9Z6P0P5JZY8d+aQvN4YJ3TllgVrOHfHHHnbBxQ9c38M9DU5JDuOJURw3WmA0RrLvzCuV5LZ6plBMwWrHNrKnjK1KQeaIsG9c14oKM4ufVSNSCwSWTcmzS1eIfluQEc4QpN7JjE2ppkiTjgC7R6oM60Kh0wOriwk6g1oByhV0WgKB8k8fNYP5fA4nPQugSluzW0gMKlo2hmlEOe/bIdFw/XR704LqEO8FaT707GTmDkljJqv7EHEj3oRciwWKb3edwEoq8DmzfZixDV4dn7V/ZwOIAWciN0V79eXLwJ+hTRQfyGBvVD4JTPbMBO3ZfgfWlZdWJHhP7Ihr2Qjd2CUcfDxTTAwanfKEpC7mn2I3zB4B+KSwLxKn0TE+fw5sYOSNXKOxRDN/8jLUITkLsEpHKbci3zWs8H1RYmD5dyj8/jR1scHJtY8W1nPScKxJfwLzRR+EPk4TiFCFDWQv3fH0McdXcYh2SyV0wG4HJQkMYt/i7fwzOC0upMBep7rsGft2OkhMneA3ov9OVv5PTyF6DAJPcnc8KIbnNiXOVEA3tnd1gJV9ie+lb8vQBq8Pq16XJi2ALvo+HVc0JhtOok+E0wGBgAmpqoR38RledgCDutGFPxoz1uqoQgbNgW+MYsnbYf5HuttkrhMl/Ni7SMRvBlOFtHfmXtd+kuHMBmuvN9G2vcv1KMnD0uozaz9sQzP3IMn0zEb42LvU/VxW9y2oMYf3y1fcTAI7qi8FCjHtd4DbKxQm8iBsQZXUvv/sirmnv1CfEHXcuSMep739aQPgOqzw90+LaDjuEQdF3UETbA1f728fEoUvsD8JsAI1u0IfHsnvdg3BBw7bJrSH2JsEdpuY7ocI+t9iITD23es2R7soj1eVJUTdamV3U9xAMJ/e4vz+5muECzGXBheZWbh03ON1Bgntxkz7tsENmzbnGMezfTIflKz1jB7ldrdx2UNMCx9XKKuwXaK83b/bkA9X

4 Comments

The DOMParser() works well when navigating from another screen to the one where things are rendered using a compiler. However, it doesn't seem to work as expected when refreshing the page or directly accessing the page through the URL.
@AdityaPrasadDhal What are you having problems with?
got the issue @TachibanaShin. I am using it in nuxt and DOMParser doesn't work in server side. So, I am currently using it only in the client side. Utterly grateful for your help.
@AdityaPrasadDhal don't use domparser it's an unforgiving API use cheerio or htmlparser2. If you cannot solve the problem. Please create a new question on stackoverflow
2

You're using the h function wrong here. h() returns virtual node that is meant to be used inside a render() function instead of a template. You don't need a <template> or <v-html> at all in this case:

//no template element
<script>
import { Link } from '@inertiajs/inertia-vue3'
import {compile,h} from "vue"

export default {
    props: {
        notifications: Object
    },
    render() {
        return h(
           // abbreviated template string for brevity
           compile('<Link href="#" class="notification-link">profile status</Link>')
        ) 
    }
}
</script>

5 Comments

Ah thank you. I wonder about can I use a specific function that I call later under render()
what do you mean by that? render() is just a function, so you can call other functions from within it
my compile always return undefined. is it correct?
try wrapping the whole thing in a div
Ive tried importing it but i still get Failed to resolve component

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.