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
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')),
...
I will answer this question even though it seems late Below I use the static compilation method
{{ 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:

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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With