question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Feature] API for rendering VNodes to string

See original GitHub issue

What problem does this feature solve?

I’ve created a <head> management system and an awesome feature would be a native way to render VNodes to strings, both in SSR and on client-side

Here’s what I’ve been currently doing in user-land: hello-world.vue

<template>
    <master>
        <template slot="title">Hello World App</template>
        <template slot="description">Meta description here</template>
        <template slot="content">
            Hello World
        </template>
    </master>
</template>
<script>
import master from '@/layouts/master'

export default {
    components: {
        master
    }
}
</script>

master.vue

<template>
    <servue>
        <template slot="head">
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <title>{{ this.$slots.title ? `${ this.$slots.title[0].text } - My App`: `My App` }}</title>
            <meta v-if="this.$slots.description" name="description" :content="this.$slots.description[0].text">
            <slot name="head"/>
        </template>
        <template slot="content">
            <slot name="content"/>
        </template>
    </servue>
</template>

<script>
import servue from './servue'

export default {
    components: {
        servue
    }
}
</script>

You can probably see some issues with how this is done, it only accounts for a single text node, and there may be more. Plus, it seems hacky to directly access slot data inside a template

The head is currently being stringified by a small component: servue.vue

<script>
const unaryTags = [
    "area",
    "base",
    "br",
    "col",
    "embed",
    "hr",
    "img",
    "input",
    "keygen",
    "link",
    "meta",
    "param",
    "source",
    "track",
    "wbr"
]

function renderStartTag(VNode) {
    let html = `<${VNode.tag}`

    if (VNode.data) {
        if (VNode.data.attrs) {
            let attr = VNode.data.attrs
            for (let name in attr) {
                if (attr[name] === "") {
                    html += ` ${name}`
                } else {
                    html += ` ${name}="${attr[name]}"`
                }
            }
        }
    }

    return html + ">";
}

function isUnaryTag(VNode) {
    return unaryTags.indexOf(VNode.tag) > -1
}

function getFullTag(VNode) {
    if (!VNode.tag) return VNode.text

    let html = renderStartTag(VNode)

    if (VNode.children) {
        html += getChildren(VNode)
    }
    if (!isUnaryTag(VNode)) {
        html += `</${VNode.tag}>`
    }
    return html;
}

function getChildren(VNode) {
    let html = ""
    for (let i in VNode.children) {
        let child = VNode.children[i]
        html += getFullTag(child)
    }
    return html
}
export default {
    created() {
        let VNodes = this.$slots.head
        let renderedHead = ""

        for (let i in VNodes) {
            let VNode = VNodes[i];
            renderedHead += getFullTag(VNode)
        }

        if (this.$isServer) {
            this.$ssrContext.head = `<!--VUESERVEHEAD START-->${renderedHead}<!--VUESERVEHEAD END-->`
        }else{
            let head = document.head
            let node
            let foundStart = false
            let startNode

            let children = head.childNodes

            for(let node of children){
                if(node.nodeType === Node.COMMENT_NODE){
                    if(node.nodeValue === "VUESERVEHEAD START"){
                        foundStart = true
                        startNode = node
                        continue
                    }
                }
                if(foundStart){
                    if(node.nodeType === Node.COMMENT_NODE){
                        if(node.nodeValue === "VUESERVEHEAD END"){
                            break
                        }
                    }
                    head.removeChild(node)
                }
            }

            if(startNode){
                let fakeMeta = document.createElement('meta')
                startNode.after(fakeMeta)

                fakeMeta.outerHTML = renderedHead
            }

        }
    },
    render(h){
        return h('div', {
            class: "servueWrapper"
        }, this.$slots.content)
    }
};
</script>

This whole process could be simplified by an API exposed by vue. The API already exists, it just needs to be exposed by Vue

What does the proposed API look like?

Vue.renderVNodesToString([VNode])
$vm.renderVNodesToString([VNode])

import { renderVNodesToString } from 'vue'

A few ideas

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:22
  • Comments:13 (3 by maintainers)

github_iconTop GitHub Comments

5reactions
AlbertMarashicommented, Jun 24, 2019

@posva it’s slightly different, it’s not for SSR

We need a universal API to render VNodes to a string on the server and the client

What @vedmant said

4reactions
bmarkoviccommented, Nov 22, 2019

A canonical example for this could be rendering HTML tooltip for V-Tooltip programatically.

So it’s not that much of a niche use-case.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to render a Vue VNode to a String - Stack Overflow
Uses Vue.extend to construct one SVG component constructor, inside render function of the constructor, it renders slots.default .
Read more >
Render Function APIs | Vue.js
Creates virtual DOM nodes (vnodes). ... Types are simplified for readability. ... The first argument can either be a string (for native elements)...
Read more >
Evan You on Twitter: "@reinink @inertiajs import { render ...
Hey Evan, probably a long shot, but is there any APIs exposed in Vue 3 to help third party libraries render a bunch...
Read more >
Snabbdom Renderer - ServiceNow Developers
Creates a vnode for the virtual DOM from a string of HTML. ... element [HTMLElement] - Element to render the view to; handler...
Read more >
Inferno API
key : (string|number) unique key within this vNodes siblings to identify it during keyed algorithm. ref : (function) callback which is called when...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found