1

I'm trying to import a vue component based on a path entered into url parameters. So for instance, if <host>/<path> is entered into the browser, I want to import a vue component located at <path>.vue.

In my routes.js file, I have a route that will get nested paths:

  { path: 'object/:catchAll(.*)*', component: BaseObject }

And send it to BaseObject:

<template>
  <div>
    <component :is="componentFile" />
  </div>
</template>
<script>
import { mapState, mapActions } from 'vuex'

export default {
  name: 'BaseObject',
  data () {
    return {
      componentPath: '',
      address: ''
    }
  },
  methods: {
    importComponent (path) {
      return () => import(`./${path}.vue`)
    }
  },
  computed: {
    componentFile () {
      return this.importComponent(this.componentPath)
    }
  },
  created () {
    const params = this.$route.params.catchAll
    console.log(params)
    this.address = params.pop()
    this.componentPath = params.join('/')
  }
}
</script>

When I navigate to http://localhost:8080/#/object/action, I'm expecting the component located at ./action.vue to be loaded. But this doesn't happen - instead I get the following errors:

runtime-core.esm-bundler.js?9e79:38 [Vue warn]: Invalid VNode type: undefined (undefined) 
  at <Anonymous> 
  at <BaseObject onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< null > > 
  at <RouterView> 
  at <QPageContainer> 
  at <QLayout view="lHh Lpr lFf" > 
  at <MainLayout onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< Proxy {$i18n: {…}, $t: ƒ, …} > > 
  at <RouterView> 
  at <App>

and

Uncaught (in promise) Error: Cannot find module './.vue'
        at app.js:416

Does anyone know how this can be accomplished?

3
  • Why just don't import components in lazy way and then set them to <component :is="" /> through componentFile? Commented Jul 26, 2021 at 7:23
  • @AriShojaei. how's that different from what I'm doing? Maybe I don't understand you Commented Jul 26, 2021 at 7:53
  • You try to do it in a hard way! Commented Jul 26, 2021 at 9:30

3 Answers 3

2
+100

There are at least 2 problems with your code...

  1. Your "catch all" route is defined as { path: 'object/:catchAll(.*)*', component: BaseObject } so if you navigate to URL http://localhost:8080/#/object/action the "object" part is matched and catchAll param will be an array containing single item "action". So the created hook will pop this single item, params array remains empty and componentPath will be empty too (this is reason for Cannot find module './.vue' error)

  2. In Vue 3, the old async component syntax (() => import(``./${path}.vue``)) is deprecated. You should always use defineAsyncComponent helper when creating async components (and this is reason for Invalid VNode type: undefined Vue warning)

So you BaseObject should look like this:

<template>
  <div>
    <component :is="componentFile" />
  </div>
</template>
<script>
import { defineComponent, defineAsyncComponent } from "vue";

export default defineComponent({
  name: "BaseObject",
  data() {
    return {
      componentPath: "",
      address: "",
    };
  },
  methods: {},
  computed: {
    componentFile() {
      return defineAsyncComponent(() =>
        import(`../components/${this.componentPath}.vue`)
      );
    },
  },
  created() {
    const params = this.$route.params.catchAll;
    console.log(this.$route.params);
    // this.address = params.pop();
    this.componentPath = params.join("/");
  },
});
</script>

Working demo

Also note that defining "catch all" route like this is dangerous as it will match all routes as - /object/action, /object/action/dd, /object/action/dd/bb etc. and those components will not exist. So maybe it would be better to allow only one level of nesting...

Sign up to request clarification or add additional context in comments.

Comments

0

I guess that you are using Webpack to bundle your application and resolve JS modules. As you can see in the docs https://webpack.js.org/api/module-methods/#import-1, import() returns a promise, it works asynchronous. You have to resolve that promise first, before using the component.

methods: {
  async getComponentFile() {
    const component = await this.importComponent(this.componentPath);
    return component;
  }
},

3 Comments

I don't think computed properties can be async - my linter doesn't allow this to compile. "error Unexpected async function declaration in "componentFile" computed property vue/no-async-in-computed-properties"
Valid point. Then maybe change it from computed to a method?
I tried it your way and I still get this error: Component is missing template or render function.
0

Don't hard easy solutions!

<component :is="componentFile" />
export default {
 name: 'BaseObject',

 components: {
  firstComponent: () => import('...'),
  secondComponent: () => import('...'),
  thirdComponent: () => import('...')
 }

 computed: {
  componentFile () {
   return this.detectComponentBasedPath()
  }
 },

 methods: {
  detectComponentBasedPath () {
   ...
  }
 }
}
</script>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.