DEV Community

Cover image for How to register global components in Vue 3 dynamically in 2023?
Jireh June Nimes
Jireh June Nimes

Posted on

How to register global components in Vue 3 dynamically in 2023?

Header background image above is from NYStudio107.

I created a same article last 2021, How to register global components in Vue 3 dynamically?, then when I recently tried to use it with the latest version of Vue 3, it's not working properly anymore and with type issues.

Vue 3's documentation still not includes a way to dynamically register global or common components throughout your application by scanning a directory. The documentation only mentions to register each global or common component during app creation like this:

app
  .component('ComponentA', ComponentA)
  .component('ComponentB', ComponentB)
  .component('ComponentC', ComponentC)
Enter fullscreen mode Exit fullscreen mode

For this article, I'll be showing a modified version that works with the latest version of Vue 3 + Vite + Vue Router + Typescript. I also tried this with implementation of SSR and the global components still work. I'll also explain what are the changes and why it is needed. Let's proceed!

Here is the sample directory structure:

Image description

We will register all components inside the common directory as global or common components with a .vue file extension.

src/components/common/components.ts

import type { App } from 'vue';

const importComponents = import.meta.glob('./**/*.vue');

export const registerComponents = async (app: App<Element>): Promise<void> => {
  for (const fileName of Object.keys(importComponents)) {
    const componentConfig = await importComponents[fileName]();
    const componentName = fileName
      .split('/')
      .pop()
      ?.replace(/\.\w+$/, '') as string;

    app.component(componentName, (componentConfig as any)?.default);
  }
};
Enter fullscreen mode Exit fullscreen mode

For this instance, I prefer not to have a prefix of Base and I prefer to call them global or common components.

Let's start on getting the global or common component Vue files.

const importComponents = import.meta.glob('./**/*.vue');
Enter fullscreen mode Exit fullscreen mode

If you remember from the previous article, we used require.context to get the global or common components that matches our desired filename pattern with a prefix of Base. require.context doesn't work with Vite properly and if you search for solutions, the recommended way is to use import.meta.glob.

My issue with import.meta.glob is that the component configurations are asynchronous unlike the previous implementation. The problem with that is during initial render of the application or when you refresh in the browser, the global or common components will not be detected especially with Vue Router.

export const registerComponents = async (app: App<Element>): Promise<void> => {
  // Some code here...
}
Enter fullscreen mode Exit fullscreen mode

That's why you can see that the registerComponents function is asynchronous with a return type of Promise<void>. I'll show later that we will call the said function with await before registering Vue Router with our Vue application.

The app argument is the Vue application instance that we will be using to register our global or common components dynamically.

for (const fileName of Object.keys(importComponents)) {
  // Some code here...
}
Enter fullscreen mode Exit fullscreen mode

Looping through the key values of the importComponents is somehow similar to the previous implementation its just that we prefer to use for loop instead to handle await before proceeding on next executions.

const componentConfig = await importComponents[fileName]();
Enter fullscreen mode Exit fullscreen mode

As I mentioned awhile ago, the component configuration from using import.meta.glob is asynchronous or in Promise that's why we will need the use of await before executing the remaining commands and registering the following components. When one component fails to register, then the remaining components will not be registered and will throw an error.

The rest of the execution is similar from the previous implementation. Let's proceed on executing the registerComponents function in my main file where creating the instance of our Vue application takes place.

Originally, the name of the file is main.ts but in my case because I was doing it with SSR implementation, the name is app.ts which I call it on both entry-client.ts and entry-server.ts. I'll not discuss full SSR implementation here in detail but as I mentioned, the registration of global or common components works also with SSR.

src/app.ts or src/main.ts

import { createApp as createNonSSRApp, createSSRApp } from 'vue';

import App from './App.vue';
import { registerComponents } from './components/common/components';
import { router } from './router';

const createApp = async () => {
  const isSSR = typeof window === 'undefined';
  const app = (isSSR === true ? createSSRApp : createNonSSRApp)(App);

  await registerComponents(app);

  app.use(router(isSSR));

  return app;
};

export default createApp;
Enter fullscreen mode Exit fullscreen mode

As you can see above that the createApp function is already asynchronous because of our registerComponents. I executed it first before register our Vue Router instance.

So that's it! I'll be updating this article further when needed and feel free to comment down below for further assistance or corrections. I hope this article will help you. Cheers!

PS: I'm openly looking for new opportunities as a full-stack developer. Please message me if interested. Thanks!

Top comments (0)