How to register global components in Vue 3 dynamically in 2023?
Tags: vue, vue3, frontend, typescript
Published: 2023-07-23 12:16:59

_Header background image above is from [NYStudio107](https://nystudio107.com/img/blog/_1200x675_crop_center-center_82_line/vue-js-3.jpg)._

I created a same article last 2021, [How to register global components in Vue 3 dynamically?](https://dev.to/jirehnimes/how-to-register-global-components-in-vue-3-dynamically-3gl), 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](https://vuejs.org/guide/components/registration.html#global-registration) 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:

```ts
app
  .component('ComponentA', ComponentA)
  .component('ComponentB', ComponentB)
  .component('ComponentC', ComponentC)
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p69kmurlqeyqe0v3j0ze.png)

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

`src/components/common/components.ts`
```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);
  }
};
```

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.

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

If you remember from the previous [article](https://dev.to/jirehnimes/how-to-register-global-components-in-vue-3-dynamically-3gl), 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.

```ts
export const registerComponents = async (app: App<Element>): Promise<void> => {
  // Some code here...
}
```

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.

```ts
for (const fileName of Object.keys(importComponents)) {
  // Some code here...
}
```

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.

```ts
const componentConfig = await importComponents[fileName]();
```

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`
```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;
```

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!**