Search code examples
vue.jsvuejs3vite

(Vue3 / Vite) not rendering nested external vue component


I am building module vue.js app that is composed from different Vite/Vue projects.

The workflow - to have Theme plugin / **Menu **plugin which can be dynamically applied within vue app.

The components renders at the root level of the app, but it fails to render when nested. (Theme component renders Menu component), however it appears in DOM as just component name....

I have tried to create separate vite/vue apps, build them, and then manually load them within index.html.

ThemePlugin project

  • theme/vite.config.ts
import { fileURLToPath, URL } from "node:url";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), vueJsx()],
  build: {
    rollupOptions: {
      output: {
        dir: "./dist",
        entryFileNames: "plugin.js",
        assetFileNames: "plugin.css",
        chunkFileNames: "chunk.js",
        manualChunks: undefined,
      },
    },
  },
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
});

  • theme/ThemeWrapper.vue
<template>
  <div class="vite-theme-plugin-build">
    <MenuLauncher></MenuLauncher>
    <div style="color:red" v-if="test1">Is Rendered from Theme Wrapper</div>
  </div>
</template>

<script lang="ts">
import type { App, Component } from 'vue';

type Framework = {
  app: App;
  externalComponents: {
    ThemeWrapper: Component;
    MenuLauncher: Component;
  };
};
const w = window as unknown as Framework;

console.log(w);

export default {
  name: "ThemeWrapper",
  components: {
    MenuLauncher: w.externalComponents.MenuLauncher
  },
  setup() {
    return {
      test1: true,
    };
  },
};
</script>
  • theme/main.ts
import type { App, Component } from "vue";
import ThemeWrapperVue from "./ThemeWrapper.vue";

type Framework = {
  app: App;
  externalComponents: {
    ThemeWrapper: Component;
    MenuLauncher: Component;
  };
};

const w = window as unknown as Framework;
w.externalComponents = w.externalComponents || {};
w.externalComponents.ThemeWrapper = ThemeWrapperVue;

MenuPlugin project

  • menu/vite.config.ts
Identical to the ThemePlugin project configuration
  • menu/MenuLauncher.vue
<template>
  <div class="vite-menu-plugin-build">
    <div>
      This is dynamic menu launcher!
    </div>
    <div style="color:blue" v-if="test1">Is Rendered from MenuLauncher</div>
  </div>
</template>

<script lang="ts">
export default {
  name: "MenuLauncher",
  setup() {
    return {
      test1: true,
    };
  },
};
</script>

  • menu/main.ts
import type { App, Component } from "vue";

import MenuLauncherVue from "./MenuLauncher.vue";

type Framework = {
  app: App;
  externalComponents: {
    ThemeWrapper: Component;
    MenuLauncher: Component;
  };
};

const w = window as unknown as Framework;

w.externalComponents = w.externalComponents || {};
w.externalComponents.MenuLauncher = MenuLauncherVue;



index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Test</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <script  src="./menu/dist/plugin.js"></script>
    <script  src="./theme/dist/plugin.js"></script>
  </head>
  <body>
    <div id="app"></div>

    <script>
      document.addEventListener("DOMContentLoaded", (event) => {
        window.app = Vue.createApp({
          setup() {
            return {};
          },
          template: `
        <div>
            <div style="border: 1px solid gainsboro; margin:1rem;">
              <div>ThemeWrapper component doesn't load MenuLauncher component....</div>
              <ThemeWrapper></ThemeWrapper>
            </div>
            <div style="border: 1px solid gainsboro; margin:1rem;">
              <div>Root component is able to render MenuLauncher... But above "ThemeWrapper" component cannot render it Why???</div>
              <MenuLauncher></MenuLauncher>    
            </div>
        </div>`,
        });
        window.app.component('MenuLauncher', window.externalComponents.MenuLauncher)
        window.app.component('ThemeWrapper', window.externalComponents.ThemeWrapper)
        window.app.mount("#app");
      });
    </script>
  </body>
</html>

Preview of end result: enter image description here

Desired result: MenuLauncherVue component should be able to render within ThemeWrapper component.


Solution

  • After lots of reading and trying I have found solution that worked for me.

    Posting here in case anyone would be facing similar problem.

    Vite configiguration for Theme / Menu plugins:

    import { defineConfig } from "vite";
    import vue from "@vitejs/plugin-vue";
    
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [vue()],
      build: {
        rollupOptions: {
          external: ["vue"],
          output: {
            dir: "./dist",
            // entryFileNames: "plugin.js",
            // assetFileNames: "plugin.css",
            // chunkFileNames: "chunk.js",
            // manualChunks: undefined,
            globals: {
              vue: "Vue",
            },
          },
        },
        lib: {
          name: 'menu',
          formats: ["umd", "iife"],
          entry: "src/main.ts",
        },
      },
    });
    

    Notice 2 important things:

    1. rollupOptions.outout.globals indicates that Vue will be available globally.
    2. lib setting was set to build dist in formats of ["umd", "iife"].(If we would set format to be es then we run into the error when the component is not being rendered...)

    It might still be some kind of bug, I would prefer to use es bundle, however I have no idea why it doesn't work.

    Full example if anyone needs: https://github.com/ninode97/vue-multi-example