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 withBase
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 theBase
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)
- Grab the file name by splitting the path on the
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.