Crafting a reusable UI Component library with Vuetify, Vue 3 and GitLab

Matt Coleman
5 min readAug 11, 2023
Photo by Alvaro Reyes on Unsplash

For as long as I can remember I’ve always wanted to create a UI component library that can be imported like many of the other greats. So thanks to Vue 2 reaching its end of life on December 31st 2023, I’ve convinced my team that now is the perfect time to finally create one.

Currently, our front end platform is built with Vue CLI and is on the latest version of Vue 2 (2.7). It has all the usual goodies like Storybook JS for building components in isolation, Jest for all our unit tests, Cypress for E2E testing and Buefy for rapidly developing components.

Having a monolith repo for the entire platform has served us well, but now that Buefy has announced that it will never support Vue 3, I feel like it’s time to move on to a more modular approach. This will ensure our main platform and all other front end’s across the business aren’t duplicating functionality, and are consistent in their look and feel.

Today’s plan is simple; Create a UI library that can be used across all front end projects and import it into a fake project to prove it works.

So let’s get started.

Firstly, we need to create a repo for all that UI goodness:

npm create vite@latest

After selecting Vue and adding anything else you want use, let’s amend out package.json file:

{
"name": "my-ui-library",
"version": "1.0.0",
"type": "module",
"files": [
"dist"
],
"main": "./dist/ui-library.umd.cjs",
"exports": {
".": {
"import": "./dist/ui-library.js",
"require": "./dist/ui-library.umd.cjs"
},
"./style.css": "./dist/style.css"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vite-plugin-vuetify": "^1.0.2",
"vue": "^3.3.4",
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"vite": "^4.4.6"
}
}

We need to change the vite.config.js file as well:

import { fileURLToPath, URL } from "node:url";
import { resolve } from "path";

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vuetify, { transformAssetUrls } from "vite-plugin-vuetify";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
template: { transformAssetUrls },
}),
vuetify({
autoImport: true,
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
optimizeDeps: {
include: ["vuetify"],
},
// The important bit...
build: {
lib: {
// src/main.ts is where we have exported our component(s)
entry: resolve(__dirname, "src/main.js"),
name: "MyComponentLibrary",
// the name of the output files when the build is run
fileName: "ui-library",
},
rollupOptions: {
// Make sure to exclude Vue from the bundle
external: ['vue'],
}
},
});

Then let’s turn our attention to the star of the show and install Vuetify:

npm i vuetify

We don’t want to import the whole library as we want to keep the overall bundle size as small as possible. So, as and when we need a Vuetify component we will simply import them.

Inside the main.js file let’s import Vuetify:

import { createApp } from "vue";
import { createVuetify } from "vuetify";

const vuetify = createVuetify();

import App from "./App.vue";

createApp(App).use(vuetify).mount("#app");

We won’t necessarily need an App.vue but for now we’ll use it to confirm that the library is installed and working as expected.

In the components/Button folder, create a Button.vue component:

<template>
<v-btn v-bind="$props">
{{ label }}
</v-btn>
</template>

<script setup>
defineProps({
label: {
type: String,
default: "Button",
},
});
</script>

Create an index.js file in the components folder to export the component:

export { default as MyButton } from "@/components/Button/Button.vue";

Then export the component in our main.js :

// ...

import { MyButton } from "@/components";

const MyComponentLibrary = {
install: (app) => {
app.component("MyButton", GuruButton);
},
};

export { vuetify, GuruComponentLibrary, MyButton };

You’ll notice that I’ve also exported Vuetify in this file. This is to export the settings for Vuetify in the UI library without repeating them in other projects using the library. It also eliminates the need to install Vuetify in those projects.

Additionally, I’ve exported the library as a whole and the individual component to enhance flexibility when importing the library in other projects.

To build the library, run:

npm run build

This should produce a dist folder with 2 JS files and a CSS file.

> vite build

vite v4.4.9 building for production...
✓ 129 modules transformed.
dist/style.css 29.36 kB │ gzip: 4.75 kB
dist/ui-library.js 208.61 kB │ gzip: 63.25 kB
dist/ui-library.umd.cjs 143.45 kB │ gzip: 52.54 kB
✓ built in 4.31s

So what now?

Distributing your newly created UI library can be done in a few ways and probably demands it’s own article, a common choice is NPM. However, no one outside of my team will ever need to use this library so I’ll be using our GitLab registry instead.

For that we need to modify the package.json file. Firstly change the name to a scoped name and specify a publishConfig using two bit’s of information from your own GitLab setup.

{company_name} The root name of your GitLab
{project_id} The Project ID can be found in the repo underneath it’s name.

// ...
"name": "@{company_name}/ui-library"
"publishConfig": {
"{company_name}:registry": "https://gitlab.com/api/v4/projects/{project_id}/packages/npm/"
},
// ...

You’ll also need to create a .npmrc file to point to the GitLab registry:

@{company_name}:registry=https://gitlab.com/api/v4/projects/{project_id}/packages/npm/
//gitlab.com/api/v4/packages/npm/:_authToken={access_token}
//gitlab.com/api/v4/projects/{project_id}/packages/npm/:_authToken={access_token}

Create an access token in GitLab and then run:

npm publish

Then inside the repo under select Deploy >> Package Registry and you should see your component libraries first release.

Now the fun bit!

If you create a new repo and add an .npmrc file to let it know that you are using a custom registry and where to look for it:

@{company_name}:registry=https://gitlab.com/api/v4/projects/{project_id}/packages/npm/
//gitlab.com/api/v4/packages/npm/:_authToken={access_token}

Then you can install your newly created library with NPM:

npm i @companyName/ui-library -D

Import the library and the CSS in your main.js file:

import "./assets/main.css";

// Import the library CSS
import "@companyName/ui-library/style.css";
// Import Vuetify from the library
import { vuetify } from "@companyName/ui-library";

import { createApp } from "vue";
import App from "./App.vue";

// Add Vuetofy to the App
createApp(App).use(vuetify).mount("#app");

Inside your Vue component, you can then use the Button component like so:

<script setup>
import { MyButton } from "@companyName/ui-library";
</script>

<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
You’ve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
</h3>
<MyButton label="My Button" />
</div>
</template>

Thank you for reading!

If you’ve made it this far and have any questions, please don’t hesitate to let me know.

Bye for now! 👋

--

--

Matt Coleman

A front end developer with a passion for Vue JS, modular and portable code, well tested code and all things in between.