Vue3 + Vite Global Component Registration

Vue3 + Vite Global Component Registration

I'm working on a project in my new job and as part of the project I'm trying to register some components globally. As a company we're going to build out a component library and we want to be able to use some components without having to manually import them in each component. In Vue2 this was an easy thing to do but in Vue3 it's a bit more complex.

Initial Attempt

I found another blog post that describes how to do this in Vue3 but the issue I ran into was an error indicating that "require is not defined". I don't know if this is because of JS modules, or the fact that I'm using TypeScript, or that we're using Vite for the project, but it doesn't work.

Svelte Experience to the Rescue

As I've noted in a previous blog post we can import "local" files into a Vite module using a glob import. So what I'll end up doing is grabbing all files that match a given format, convert the file names to the format I want, and globally register all components.

Grab Components

Let's assume that the file structure of the project includes a main.ts file at the root of the project, as well as a /src/lib/components folder, which is where I'll store all of the components to be globally registered. This I also stole from Svelte, although we can't just use import * from '$lib/components/... like we can in Svelte. Regardless, this felt like a natural place to store these components.

We'll be able to simply grab all components using const components = import.meta.globEager('/src/lib/components/<Name Pattern>.vue')

Create a plugin

Let's also create a plugin to grab and register all of these components, and set it up at /src/plugins/global-components.ts. It is here we'll set up the script to grab and register all components as

import { App } from 'vue'

export const register = (app: App<Element>): void => {
  // Grab all components in `/src/lib/components/` that start with "Base"
  const components = import.meta.globEager('../lib/components/Base*.vue')
  Object.entries(components).forEach(([path, component]) => {
    // Just get the file name itself, remove the .vue extension, and remove the "Base" at the front of the file name
    const pathSplit = path.split('/')
    const fileName = pathSplit[pathSplit.length - 1].split('.vue')[0].split('Base')[1]

    // Convert to kebab-case and register with a "jvp-" prefix
    const kebab = fileName.replace(/([a-z0–9])([A-Z])/g, '$1-$2').toLowerCase()
    app.component(`jvp-${kebab}`, component.default.render())
  })
}

The comments should be fairly self-explanatory, but just in case they're not this is what is happening

  • Fetch all components that live in /src/lib/components that begin with Base in the file name
  • For each of these components
    • Grab the file name by splitting the path on the / character, and getting the last entry in the resulting array.
    • Remove the .vue extension as well as the Base prefix
    • Convert the PascalCase file name to kebab-case using your favorite method (my Googling led me to medium.com/@mattkenefick/snippets-in-javasc..)
    • Register the component with the prefix you want (I'm using "jvp" for these)

There are likely better ways to handle the file name search and string manipulation but this was a simple way for me to do it since I'm prescribing the naming convention myself.

Use plugin in main.ts

Now that this is set up we need to use it in our main.ts file. This is a fairly simple process. First we need to import our newly created register function and execute it with the app instance that we create in main.ts. It should look something like this

import App from './App.vue'
import { createApp } from 'vue'
import { register } from './plugins/global-components'

const app = createApp(App)
register(app)

and we should now have access to any and all components created in /src/lib/components/Base<NameRemainder>.vue throughout our app as <jvp-name-remainder /> without having to import it directly. Again, there may be a simpler/easier way to handle this functionality, but this is the first way I tried that actually worked so I'm just going to leave this here.