How can I create a library that exposes a single Vue component that can be used by distributed .mjs files?
<p>I want to create a Vue component bundled into a single .mjs file. Another Vue project can get this .mjs file over HTTP and use the component. Installing a pluggable component via npm is not possible because other applications will try to get it based on the configuration at runtime.</p>
<p>Things to consider with pluggable components</p>
<ul>
<li>May be using a subcomponent from another UI framework/library</li>
<li>May use custom CSS</li>
<li>May depend on other files, such as images</li>
</ul>
<hr />
<p><strong>Copy library</strong></p>
<p>I created a new Vuetify project via <code>npm create vuetify</code></p>
<p>I deleted everything in the src folder except vite-env.d.ts and created a component Renderer.vue</p>
<pre class="brush:php;toolbar:false;"><script setup lang="ts">
import { VContainer } from "vuetify/components"
defineProps<{ value: unknown }>()
</script>
<template>
<v-container>
<span class="red-text">Value is: {{ JSON.stringify(value, null, 2) }}</span>
</v-container>
</template>
<style>
.red-text { color: red; }
</style></pre>
<p>and an index.ts file</p>
<pre class="brush:php;toolbar:false;">import Renderer from "./Renderer.vue";
export { Renderer };</pre>
<p>I added library mode in vite.config.ts</p>
<pre class="brush:php;toolbar:false;">build: {
lib: {
entry: resolve(__dirname, "./src/index.ts"),
name: "Renderer",
fileName: "renderer",
},
rollupOptions: {
external: ["vue"],
output: {
globals: {
vue: "Vue",
},
},
},
},</pre>
<p>and extended the package.json file</p>
<pre class="brush:php;toolbar:false;">"files": ["dist"],
"main": "./dist/renderer.umd.cjs",
"module": "./dist/renderer.js",
"exports": {
".": {
"import": "./dist/renderer.js",
"require": "./dist/renderer.umd.cjs"
}
},</pre>
<p>Since I use custom CSS, Vite generates a styles.css file, but I have to inject the styles into the .mjs file. Based on this question, I'm using the plugin vite-plugin-css-injected-by-js.</p>
<p>When building, I get the required .mjs file containing the custom CSS</p>
<hr />
<p><strong>Using components</strong></p>
<p>I created a new Vue project via <code>npm create vue</code></p>
<p>For testing purposes, I copied the generated .mjs file directly into the new project's src directory and changed the App.vue file to </p>
<pre class="brush:php;toolbar:false;"><script setup lang="ts">
import { onMounted, type Ref, ref } from "vue";
const ComponentToConsume: Ref = ref(null);
onMounted(async () => {
try {
const { Renderer } = await import("./renderer.mjs"); // fetch the component during runtime
ComponentToConsume.value = Renderer;
} catch (e) {
console.log(e);
} finally {
console.log("done...");
}
});
</script>
<template>
<div>Imported component below:</div>
<div v-if="ComponentToConsume === null">"still loading..."</div>
<component-to-consume v-else :value="123" />
</template></pre>
<p>Unfortunately, I get the following warning and error</p>
<blockquote>
<p>[Vue warn]: Vue received a component that has become a reactive object. This can cause unnecessary performance overhead and should be avoided by marking components with <code>markRaw</code> or using <code>shallowRef</code> instead of <code>ref</code>. </p>
</blockquote>
<blockquote>
<p>[Vue warn]: Injection 'Symbol(vuetify:defaults)' not found. </p>
</blockquote>
<blockquote>
<p>[Vue warn]: An unhandled error occurred during execution of the setup function</p>
</blockquote>
<blockquote>
<p>[Vue warn]: An unhandled error occurred during execution of a scheduler refresh. </p>
</blockquote>
<blockquote>
<p>Uncaught (in promise) Error: [Vuetify] Default instance not found</p>
</blockquote>
<p>Does anyone know what I'm missing or how to fix it? </p>