Skip to content

[Feature Request] Rendering outside of the Vue-App #410

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
douglasg14b opened this issue May 18, 2025 · 1 comment
Open

[Feature Request] Rendering outside of the Vue-App #410

douglasg14b opened this issue May 18, 2025 · 1 comment

Comments

@douglasg14b
Copy link

douglasg14b commented May 18, 2025

I see the TODO in the docs about this.

Is this possible now? if so, how? if I try this right now (rendering a PortalTarget somewhere else) the portal that points at it never seems to render it's content.

My use case:

A userscript that adds components/UI to an existing page. Often I will need to react to certain DOM elements appearing and add additional components to them, which means rendering Vue components outside of the scope of the original app.

This is possible today the ahrd way, but I run ito a ton of caveats such as losing access to plugin data and what not on the original app instance.

export function injectVueComponent<TComponent extends Parameters<typeof h>[0]>(
    domSelector: string,
    component: TComponent,
    app: App,
    propsGetter: () => Record<string, unknown>
) {
    let vueComponent: VNode | null = null;
    let mountTarget: Element | null = null;

    watchForElement(domSelector, {
        onAppear: (element) => {
            mountTarget = element;
            const props = propsGetter();

            vueComponent = h(component, props);
            vueComponent.appContext = app._context; // Set the app context to the component

            render(vueComponent, element);
        },
        onVanish: () => {
            if (mountTarget) {
                render(null, mountTarget);
            }
            vueComponent = null;
            mountTarget = null;
        },
    });
}
@douglasg14b
Copy link
Author

douglasg14b commented May 18, 2025

I did try and do a bit of a kludge, though as you can probably tell me knowledge is lacking here. This did not work unfortunately, the portal target exists in the DOM, but the portal itself doens't

main.ts

    const _app = createApp(App as Component)
        .use(PortalVue)
        .mount(container);

    const portalTarget = resolveComponent('PortalTarget');

    injectVueComponent2(THREAD_HEADER_CONTAINER, portalTarget, app, 'thread-header', () => parseOpenThreadInfo());

App.vue

// THis doesn't appear to render it;s contents
        <portal to="thread-header" v-slot="{ channelId, threadTs }">
            <ThreadSummaryHeader :channelId="channelId" :threadTs="threadTs" />
        </portal>
export function injectVueComponent2<TComponent extends Parameters<typeof h>[0]>(
    domSelector: string,
    component: TComponent,
    portalName: string,
    app: App,
    propsGetter: () => Record<string, unknown>
) {
    let vueComponent: VNode | null = null;
    let mountTarget: Element | null = null;

    watchForElement(domSelector, {
        onAppear: (element) => {
            mountTarget = element;
            const props = propsGetter();

            vueComponent = h(component, {
                name: portalName,
                props: { ...props },
            });

            vueComponent.appContext = app._context; // Set the app context to the component

            render(vueComponent, element);
        },
        onVanish: () => {
            if (mountTarget) {
                render(null, mountTarget);
            }
            vueComponent = null;
            mountTarget = null;
        },
    });
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant