DEV Community

GreggHume
GreggHume

Posted on • Updated on

 

Vue 3, Nuxt 3 and Vite: Dynamic import components off data

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

A note is that if you use path aliases for dynamic imports like @ or ~ you might experience issues.

Top comments (4)

Collapse
 
henninghov profile image
henninghov • Edited

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!

Collapse
 
kissu profile image
Konstantin BIFERT

Hi! 👋🏻
Do you know why such aliases would be an issue? Seems strange that this may cause some disturbance. 🤔

Collapse
 
greggcbs profile image
GreggHume • Edited

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.

Collapse
 
kissu profile image
Konstantin BIFERT

Oh, my bad I guess. Wasn't aware that it was an issue before.

Looks better! 💪🏻