We've got a Vue app and would like to allow third parties to create plugins. We'd like the plugins to be built in the form of a Vue single-file component.
At runtime, the end user would select a plugin to add to the app. The app would fetch the plain-text .vue file, compile it on the fly, and display it in the app.
Vue supports dynamic and async components, but these to be compiled into the app ahead of time. We'd like to do the same thing, except load the code on the fly.
How can I make this work?
Here's what I've got so far:
<template>
<div>
The component goes here:
<component :is="pluginComponent"></component>
</div>
</template>
<script>
import { parseComponent } from "vue-template-compiler";
export default {
data() {
return {
pluginComponent: null
};
},
mounted() {
// fetch code from some external source here
let code = "<template><div>hello</div></template>";
let comp = parseComponent(code);
this.pluginComponent = comp;
}
};
</script>
(I modified the build so the vue-template-compiler is present.)
The code above generates this error:
[Vue warn]: invalid template option:[object Object]
found in
---> <Anonymous>
<Pages/plugin/PluginId.vue> at pages/plugin/_plugin_id.vue
<Nuxt>
<Layouts/default.vue> at layouts/default.vue
<Root> instrument.js:110
instrumentConsole instrument.js:110
VueJS 15
TypeError: "vnode is null"
VueJS 14
instrument.js:110
I'm guessing that whatever parseComponent() produces isn't what <component>
is looking for.
Add a script tag inside your Vue component template. Just add the script into the component template. But don't forget to add, type="application/javascript" to the script tag otherwise it will show an error during the build. However, if you have added document.
You might know Vue updates reactively: when you change a value, the DOM is automatically updated to reflect the latest value. Vue does these updates asynchronously. In contrast, a test runner like Jest runs synchronously. This can cause some surprising results in tests.
Usually, if we want to dynamically load a Vue.js component, we use a dynamic import. This works great as long as MyComponent is part of the code base (either directly or as a npm dependency) of the Vue.js client application.
At runtime, the end user would select a plugin to add to the app. The app would fetch the plain-text .vue file, compile it on the fly, and display it in the app. Vue supports dynamic and async components, but these to be compiled into the app ahead of time.
We'd like the plugins to be built in the form of a Vue single-file component. At runtime, the end user would select a plugin to add to the app. The app would fetch the plain-text .vue file, compile it on the fly, and display it in the app. Vue supports dynamic and async components, but these to be compiled into the app ahead of time.
This works great as long as MyComponent is part of the code base (either directly or as a npm dependency) of the Vue.js client application.
I'm guessing that whatever
parseComponent()
produces isn't what<component>
is looking for
I'd say yes, as it doesn't seem to compile to any render
functions.
As stated in the docs, vue-template-compiler
is for runtime-compilation. And in most cases you should be using it with vue-loader
.
How can I make this work?
You might want to use Vue.compile
as it allows you to compile template string into a render
function; which you can then bind to an object for an async or dynamic component.
Do note, however, that this is only available in the full build, which is roughly 30% heavier-weight than the runtime-only build counterparts. Read more on Runtime + Compiler vs. Runtime-only.
With that in mind, since you didn't mention in the question, which bundler you are using, I'm going to assume Webpack with Vue-CLI, and here's how you would configure the vue
alias (as a reference point when importing).
In your console (from the root of the project), run:
vue inspect resolve.alias.vue$
If that results in something along "vue/dist/vue.runtime.esm.js" (it should be, by default), then it's clear that we need to change this part.
Now, since the internal webpack config is maintained using webpack-chain
, we would configure/reset the alias like so:
module.exports = {
chainWebpack: config => {
config.resolve.alias
.set('vue$', 'vue/dist/vue.esm.js')
}
}
Check out the explanation of different builds.
And at this point, all you'd need to do is pass the "dynamic" template to the compile
function, excluding the <template>
tag though.
import Vue from 'vue';
export default {
mounted() {
// fetch code from some external source here
let code = '<div>hello</div>';
let comp = Vue.compile(code);
this.pluginComponent = comp;
}
}
<template>
<div>
The component goes here:
<component :is="pluginComponent"></component>
</div>
</template>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With