You want to dynamically import components based on some dataset or api call and perhaps wait for those components to fully download before rendering the page, here is an example:
- Dont forget if you are using
async setup()
in Vue 3 (not Nuxt) you will have to wrap you components in<Suspense>
or you will see nothing render.
Options API
<template>
<div>
<component
v-once
v-for="(component, index) in components"
:is="component.component"
v-bind="component.data"
:key="index"
/>
</div>
</template>
<script>
import { defineAsyncComponent } from 'vue';
export default {
data:()=> ({
page: {},
components: []
}),
async created() {
// dataset
const page_req = await fetch('/data/page-data.json')
const {components, ...page) = await page_req.json()
this.page = page
// import components
if (components) {
const dynamic_components = components.map((component)=> {
const {type, ...data} = component;
// split the data and the component instance so you can v-bind the data easier in the template
return {
data: data,
component: defineAsyncComponent(() => import(`../components/Module/${type}.vue`))
}
})
// download all components over the network before rendering the page
// otherwise the page jumps around as components load in
await Promise.all(dynamic_components.map((component)=> {
return component.component.__asyncLoader()
}))
// we do not want components to be reactive data, this can cause performance issues, so we freeze the object
// vue understands frozen objects should not be reactive.
this.components = Object.freeze(dynamic_components)
}
}
}
</script>
Composition API
<template>
<div>
<component
v-once
v-for="(component, index) in components"
:is="component.component"
v-bind="component.data"
:key="index"
/>
</div>
</template>
// composition api
<script setup>
import { defineAsyncComponent } from 'vue';
// dataset (local data, or you can fetch from an api)
const page = await import('~/static/data/some-page-of-components.json')
let components = []
// import components
components = page.components.map((component)=> {
const {type, ...data} = component;
// split the data and the component instance so you can v-bind the data/props easier in the template
return {
data: data,
// A note is that if you use path aliases for dynamic imports like @ or ~ you might experience issues.
component: defineAsyncComponent(() => import(`../components/Module/${type}.vue`))
}
})
</script>
A note is that if you use path aliases for dynamic imports like @
or ~
you might experience issues.
Top comments (4)
Thanks a lot for the very informative post and for sharing. I have tested your approach and I am getting the components to load. I do get some 404 console log errors when loading the components though when running the page in the browser - saying the component is not found. It seems to be using the wrong path? Also it does not work when I separate the component name and file extension in the import statement. I have to combine the component name and the file extension. A bit hard to debug as the components do load. And is it possible to ask you to include the json structure you use for mocking your API? I am using Nuxt 3, Vite and VueJS with composition API. Thanks again!
Hi! 👋🏻
Do you know why such aliases would be an issue? Seems strange that this may cause some disturbance. 🤔
It was said that the use of aliases was fixed in newer versions of Vite... I am yet to see this working in action.
I am also amending the code above to have options api example and thats difference because we have to set the components to non reactive data.
I have updated the code above to be a bit more friendly if you want to grab the updated version.
Oh, my bad I guess. Wasn't aware that it was an issue before.
Looks better! 💪🏻