Search code examples
javascriptvuejs3vue-sfc

Vue3 How to parse SFC string to a component?


I want to compile sfc string as a component like this:

<script setup>
import { parseToComponent } from './';

const sfcString = `
    <script setup>
    import { ref } from 'vue';
    const count = ref(0);
    </script>

    <template>
        <div class="label">{{ count }}</div>
        <button @click="count++">increase</button>
    </template>

    <style scoped>
    .label {
        color: red;
    }
    </style>
`;

const parsedComponent = parseToComponent(sfcString);
</script>

<template>
    <div>
        <parsedComponent />
    </div>
</template>

<style scoped>
</style>

How can I do this?

I tried to use vue/compiler-sfc to parse and compile sfcString, but I don't know how to convert it to component:

import { parse, compileTemplate, compileScript, compileStyle } from 'vue/compiler-sfc';

export function parseToComponent(sfcString) {
    const { descriptor } = parse(sfcString);

    const scriptOptions = ...;
    const templateOptions = ...;
    const styleOptions = ...;

    // succeed, but how to use them to take a component?
    const script = compileScript(descriptor, scriptOptions);
    const template = compileTemplate(templateOptions);
    const style = compileStyle(styleOptions);
}

Solution

  • I used vue3-sfc-loader to implemented it:

    import * as Vue from 'vue';
    import { loadModule } from 'vue3-sfc-loader';
    
    export function parseToComponent(sfcString) {
        const id = genRandomId(id);
        const options = {
            moduleCache: { vue: Vue },
            async getFile(url) {
                return Promise.resolve(sfcString);
            },
            addStyle(styleString) {
                let style = document.getElementById(id);
                if (!style) {
                    style = document.createElement('style');
                    style.setAttribute('id', id);
                    const ref = document.head.getElementsByTagName('style')[0] || null;
                    document.head.insertBefore(style, ref);
                }
                style.textContent = styleString;
            }
        };
        const component = loadModule(`${id}.vue`, options);
        return Vue.defineAsyncComponent(() => component);
    }
    

    Use in code(need markRaw()):

    <script setup>
    import { markRaw } from 'vue';
    import { parseToComponent } from './';
    
    const sfcString = `...`;
    const parsedComponent = markRaw(parseToComponent(sfcString));
    </script>
    
    <template>
        <div>
            <parsedComponent />
        </div>
    </template>