Building a Vue.js Monorepo in 2023

Learn how Bit can help you overcome all the Monorepo problems for your new Vue.js project!

Fernando Doglio
Bits and Pieces
Published in
15 min readJul 5, 2023

--

Let’s face it — as developers, we often find ourselves grappling with challenges like the need to easily share ad reuse code between projects, ensure standardization across our codebase, and maintain complex dependencies. These hurdles can slow us down and hinder collaboration within our teams.

But monorepos can come to our rescue.

In this article, I’m going to explore the benefits of using a monorepo approach, and on top of it, I’ll give you a quick intro to Bit, the tool we’ll use to manage everything.

The Pros and Cons of Monorepos

While using a monorepo for Vue projects brings numerous benefits, we have to agree that it can introduce a set of challenges that limit scalability.

It’s a tradeoff.

Let’s try to quickly understand the good and bad parts of tackling a Vue project with a monorepo:

👍 The pros:

  • Full deployments are easier, there are fewer moving parts to orchestrate.
  • Simpler dependency management. Every component will have the same dependencies (same versions).
  • Whenever sharing code makes sense, doing it is a lot easier.
  • Standardization becomes smoother.

👎 The cons:

  • Publishing a single component or feature gets tricky. Imagine having dozens of components as part of your project, and you only made a change in one of them.
  • Build times increase significantly because of the same reason.
  • A monorepo makes it a lot easier for developers to introduce inter-component coupling.
  • Working with a big team inside a single monorepo can be chaotic. Especially if the team isn’t using the right tools, or they don’t understand them.
  • Onboarding takes forever. Joining the team means having to learn and understand the entire codebase, not just the code of a single component.
  • Less dependency flexibility, given how multiple versions of the same library can’t be part of the same codebase.

Having your cake and eating it too: using component-driven development on your monorepo

Perhaps the biggest challenge with monorepos is that they have a tendency to couple components and different parts of the app together, eventually treating components as a single, monolithic entity.

So a Component-Driven development workflow sounds like could help here.

CDD is a way of working in which you structure your software into independent components, where each component addresses a specific concern and exposes a well-defined interface for other components to interact with.

I’m sure you can see how for a Vue project, this should be the de facto way of working.

But what happens when you already have a monorepo and want to start doing CDD? That’s where Bit comes into play.

Bit allows us to overcome the interdependence and coupling issues by providing a powerful mechanism for isolating and managing individual components within the monorepo.

In other words, you’ll be working using CDD INSIDE the monorepo.

Crazy? Maybe, but it works!

With Bit, you package each component as an independent entity, making it easier to modify, test, and reuse them across projects without impacting other components. This level of isolation empowers developers to work on specific components with confidence, knowing that changes made to one of them won’t unintentionally affect others.

But wait, isn’t that like working on a multi-repo configuration?!

EXACTLY!

With Bit, you’ll FEEL like you’re using multiple individual repos WHILE actually using a monorepo. This translates into keeping the “pros” we discussed above and getting rid of the “cons”.

Don’t believe me? Let’s go one by one:

  • Component coupling: With Bit, when you create a component it’s automatically isolated from all others. It also routes local dependencies through the node_modules folder, making it even harder to couple components.
  • Publishing a single component gets tricky. You literally have a single command to push and publish new versions of components by using Bit. Not only that, but Bit also lets you independently version each component, making it even easier for other applications to choose exactly what to use.
  • Build times increase significantly because of the same reason. This is no longer the case, you’re now able to deploy, install and update components individually.
  • Working with a big team inside a single monorepo can be chaotic. Bit provides collaboration tools out-of-the-box, making it super easy for multiple developers to work on the same project (or component) without stepping on each other’s toes.
  • Onboarding takes forever. This is no longer the case, since you can have developers (and teams) working on single components, even when they’re all working on the same monorepo.
  • Less dependency flexibility. Also no longer valid, Bit handles environments on a per-component basis, which means you can have different versions of the same dependency working on the same project (i.e. having some components using Vue 3 and others Vue 2).

In conclusion, while monorepos may introduce challenges, Bit serves as the bridge between both worlds. Bit’s capabilities can effectively address monorepo problems and unlock the true potential of Component-Driven Development for Vue projects.

Before continuing, take a brief look at how Bit can help:

But don’t take my word for it! Keep reading and I’ll show you a very practical example.

Demo Time — Building a Blog

I’m a huge believer that developers learn more by doing than by reading, this is why I want to present a practical use case here.

Imagine we are building a blog, a multi-page application with various components shared across different pages.

In our blog project, we have several pages, such as the homepage, blog post pages, and an about page. Each of these pages requires different components like navigation bars, footer sections, and social media icons. Traditionally, managing these components individually across separate repositories could be cumbersome and time-consuming.

By utilizing Bit, we can seamlessly build and share components between the various pages of our blog.

So let’s start!

Setting up the Vue.js Monorepo with Bit

Setting up a Vue.js monorepo using Bit requires a clear understanding of the project requirements and goals. Otherwise, you’re moving forward without a clear direction, and that’s never a good thing.

Here are some key considerations to keep in mind:

  1. Code Sharing: Determine the need for code sharing across multiple projects or components within the monorepo. Identify which components or libraries should be reusable and shared among different Vue applications. In our case, those will be the NavBar, Footer and the Media Icons.
  2. Modularity: Assess the level of modularity required for your project. Consider whether you need to develop and version components independently to allow for flexibility and granular updates. An alternative could be going for microservices instead (here is a nice article describing how to build component-driven microservices).
  3. Collaboration: Evaluate the collaboration requirements within your team. Determine how developers will work together on shared components, manage dependencies, and ensure consistent coding practices. Bit will give you the tools, you just need to set the standards.

Creating a Vue.js Monorepo with Bit

Now, let’s walk through the process of creating a Vue.js monorepo using Bit. Follow these steps:

  1. Install Bit: Start by installing the Bit CLI globally on your machine. You can find detailed installation instructions in the Bit documentation: Bit Installation.
  2. Initialize a Monorepo: Create a new directory for your monorepo and navigate to it in your terminal. Run the following command to initialize the monorepo structure:
bit new vue workspace --env teambit.vue/vue --default-scope [my-org].[my-scope]

This command sets up the necessary configuration files for the monorepo inside a folder called workspace . You can specify whatever name you want there.

Also notice that the parts inside brackets need to be modified. You’ll use those, mostly, to find your shared components once you export them to a remote scope on Bit Cloud for hosting and collaboration. So start by going there and creating an account and a scope (essentially a group of components).

2.1. Creating the scope. Scopes are “groups” for your components. In our case, we’ll go to Bit Cloud, click on the “New” button in the top right corner and then on the “Scope” option.

If you don’t have an account yet, you can go ahead and sign up. You can also do that later when you’re ready to export your components.

This will take you to the “new scope” page:

Here I’ve set mine as a personal public scope with the name “vue-blog”. You can choose whatever you want. Finally, click on “Create”.

You’re now ready to create your workspace with the above command 🥳 (properly replacing your username and the scope’s name).

3. Create Vue Components: Begin creating your Vue components within the monorepo. Each component will have its own directory and contain its code, styles, and tests. Don’t worry though, you won’t have to keep track of that, Bit will do it for you. You simply use the following command after going into your workspace folder (remember this has to be executed inside the workspace):

bit create vue components/nav-bar
bit create vue components/my-footer
bit create vue components/media-icons

After each of those commands, you should be seeing an output like this:

Notice the dash (“-”) on all their names, that’s mandatory because by default the linter will ask you to have names with, at least, two words separated by the dash character.

Once done with all 3, your folder structure should look like this:

Let’s take the current state of this project as our initial Monorepo. We have 3 different projects right here, 3 different components that as of right now, only share Vue as their common dependency.

We can build each one independently and we will.

Here is a simple code for each component:

//navbar.vue
<template>
<nav class="navbar">
<ul class="nav-list">
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Services</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</template>

<script>
export default {
// Component logic here (if needed)
}
</script>

<style scoped>
.navbar {
background-color: #333;
color: #fff;
padding: 10px;
}

.nav-list {
list-style-type: none;
display: flex;
justify-content: space-between;
align-items: center;
}

.nav-list li {
margin-right: 10px;
}

.nav-list li a {
color: #fff;
text-decoration: none;
}
</style>
//footer.vue
<template>
<footer class="footer">
<div class="footer-content">
<p>&copy; 2023 My Website. All rights reserved.</p>
</div>
</footer>
</template>

<script>
export default {
// Component logic here (if needed)
}
</script>

<style scoped>
.footer {
background-color: #f0f0f0;
padding: 20px;
text-align: center;
}
</style>
//mediaicons.vue
<template>
<div class="media-icons">
<a href="#"><i class="fab fa-facebook-f"></i></a>
<a href="#"><i class="fab fa-twitter"></i></a>
<a href="#"><i class="fab fa-instagram"></i></a>
<a href="#"><i class="fab fa-linkedin-in"></i></a>
</div>
</template>

<script>
export default {
// Component logic here (if needed)
}
</script>

<style scoped>
.media-icons {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}

.media-icons a {
color: #333;
margin: 0 5px;
}

.media-icons a i {
font-size: 20px;
}
</style>

You could also add tests inside the test-ready templates Bit created for you (those are the .spec.ts files located inside each component’s folder), as well as review the automated documentation created by Bit for each component. However, we’ll skip that part for now, since we care more about the actual monorepo and how to manage it.

So let’s keep going.

Working on our new Monorepo

As of right now, you can submit your code into a single repo, and you have yourself a monorepo with Vue components. But now we can see some of the problems we already mentioned:

  • How do we version each component individually?
  • How do we distribute them?
  • How do we use them?

For that, we have to keep moving forward, into “tagging”.

Tag Components: Once you have added components, tag them with a specific version. This begins your ability to track changes and manage versions effectively, and most importantly, individually. Use the following command:

bit install
bit tag

The first command will make sure all the dependencies needed and added with the previous bit create commands are present.

Then the bit tag command will automatically tag all your components. All of them will be given a version number by default (which is 0.0.1) and they’ll all be ready to export.

You should see something like this when the tag command is done:

Yes, “export” is key, because right now the “version” information is on your local workspace. In other words, you have to use Bit to “export” your components, which will translate into you pushing them to their remote scope (on Bit Cloud).

You can also self-host your remote scope — learn more here.

Share Components: To share the components within the monorepo so they can be used in other projects you’ll want to use the following command:

bit export

That command will effectively export ALL components at once, in fact, you can see here the end result:

Notice the “Vue” icon on all my 3 components, and how 2 of them actually show a proper preview. The MediaIcons component is not showing any preview because if you look at the code, it’s just placeholder code.

Now my components are live, and anyone can use them, I’ve shared them with the world, after versioning them, all with a single toolchain for component-driven development.

However, let’s keep going, because we’re still missing the solution to our collaboration problems.

Using and collaborating on these components: First things first, there are multiple ways in which you can install Bit components, and they all have different goals. You can:

  • Install it with Bit or a package manager (pnpm, Yarn or npm) like any normal dependency. This is meant to be used when you just want to “use” the component as is.
  • Import it with Bit. Notice how I’m no longer saying “installing”. By “importing” the component, you’ll be downloading the files into your own workspace where you’ll be able to edit the source code and make it your own. Any changes you make can then be pushed back and shared as a new version. This is the method you’ll use if you want your teammates to collaborate with you on the development of your components.
  • Fork it with Bit. You can also “fork” these components, which works a lot like the “import” but the history of the component is not downloaded. This effectively means you’ll be creating a new component if you push any changes to make to it.

Let’s say we have our team working on the MediaIcons component so it’s not placeholder anymore. After making the desired changes, they need to update the version, and export it. The entire process should only affect that component, not the rest.

Here is the new code they’ve come up with:

<template>
<div class="media-icons">
<a href="#">
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 24 24" id="facebook"><path d="M20.9,2H3.1A1.1,1.1,0,0,0,2,3.1V20.9A1.1,1.1,0,0,0,3.1,22h9.58V14.25h-2.6v-3h2.6V9a3.64,3.64,0,0,1,3.88-4,20.26,20.26,0,0,1,2.33.12v2.7H17.3c-1.26,0-1.5.6-1.5,1.47v1.93h3l-.39,3H15.8V22h5.1A1.1,1.1,0,0,0,22,20.9V3.1A1.1,1.1,0,0,0,20.9,2Z"></path></svg>
</a>
<a href="#">
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 24 24" id="twitter"><path d="M22,5.8a8.49,8.49,0,0,1-2.36.64,4.13,4.13,0,0,0,1.81-2.27,8.21,8.21,0,0,1-2.61,1,4.1,4.1,0,0,0-7,3.74A11.64,11.64,0,0,1,3.39,4.62a4.16,4.16,0,0,0-.55,2.07A4.09,4.09,0,0,0,4.66,10.1,4.05,4.05,0,0,1,2.8,9.59v.05a4.1,4.1,0,0,0,3.3,4A3.93,3.93,0,0,1,5,13.81a4.9,4.9,0,0,1-.77-.07,4.11,4.11,0,0,0,3.83,2.84A8.22,8.22,0,0,1,3,18.34a7.93,7.93,0,0,1-1-.06,11.57,11.57,0,0,0,6.29,1.85A11.59,11.59,0,0,0,20,8.45c0-.17,0-.35,0-.53A8.43,8.43,0,0,0,22,5.8Z"></path></svg>
</a>
<a href="#">
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 24 24" id="instagram"><path d="M12,9.52A2.48,2.48,0,1,0,14.48,12,2.48,2.48,0,0,0,12,9.52Zm9.93-2.45a6.53,6.53,0,0,0-.42-2.26,4,4,0,0,0-2.32-2.32,6.53,6.53,0,0,0-2.26-.42C15.64,2,15.26,2,12,2s-3.64,0-4.93.07a6.53,6.53,0,0,0-2.26.42A4,4,0,0,0,2.49,4.81a6.53,6.53,0,0,0-.42,2.26C2,8.36,2,8.74,2,12s0,3.64.07,4.93a6.86,6.86,0,0,0,.42,2.27,3.94,3.94,0,0,0,.91,1.4,3.89,3.89,0,0,0,1.41.91,6.53,6.53,0,0,0,2.26.42C8.36,22,8.74,22,12,22s3.64,0,4.93-.07a6.53,6.53,0,0,0,2.26-.42,3.89,3.89,0,0,0,1.41-.91,3.94,3.94,0,0,0,.91-1.4,6.6,6.6,0,0,0,.42-2.27C22,15.64,22,15.26,22,12S22,8.36,21.93,7.07Zm-2.54,8A5.73,5.73,0,0,1,19,16.87,3.86,3.86,0,0,1,16.87,19a5.73,5.73,0,0,1-1.81.35c-.79,0-1,0-3.06,0s-2.27,0-3.06,0A5.73,5.73,0,0,1,7.13,19a3.51,3.51,0,0,1-1.31-.86A3.51,3.51,0,0,1,5,16.87a5.49,5.49,0,0,1-.34-1.81c0-.79,0-1,0-3.06s0-2.27,0-3.06A5.49,5.49,0,0,1,5,7.13a3.51,3.51,0,0,1,.86-1.31A3.59,3.59,0,0,1,7.13,5a5.73,5.73,0,0,1,1.81-.35h0c.79,0,1,0,3.06,0s2.27,0,3.06,0A5.73,5.73,0,0,1,16.87,5a3.51,3.51,0,0,1,1.31.86A3.51,3.51,0,0,1,19,7.13a5.73,5.73,0,0,1,.35,1.81c0,.79,0,1,0,3.06S19.42,14.27,19.39,15.06Zm-1.6-7.44a2.38,2.38,0,0,0-1.41-1.41A4,4,0,0,0,15,6c-.78,0-1,0-3,0s-2.22,0-3,0a4,4,0,0,0-1.38.26A2.38,2.38,0,0,0,6.21,7.62,4.27,4.27,0,0,0,6,9c0,.78,0,1,0,3s0,2.22,0,3a4.27,4.27,0,0,0,.26,1.38,2.38,2.38,0,0,0,1.41,1.41A4.27,4.27,0,0,0,9,18.05H9c.78,0,1,0,3,0s2.22,0,3,0a4,4,0,0,0,1.38-.26,2.38,2.38,0,0,0,1.41-1.41A4,4,0,0,0,18.05,15c0-.78,0-1,0-3s0-2.22,0-3A3.78,3.78,0,0,0,17.79,7.62ZM12,15.82A3.81,3.81,0,0,1,8.19,12h0A3.82,3.82,0,1,1,12,15.82Zm4-6.89a.9.9,0,0,1,0-1.79h0a.9.9,0,0,1,0,1.79Z"></path></svg>
</a>
<a href="#">
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 24 24" id="linkedin"><path d="M20.47,2H3.53A1.45,1.45,0,0,0,2.06,3.43V20.57A1.45,1.45,0,0,0,3.53,22H20.47a1.45,1.45,0,0,0,1.47-1.43V3.43A1.45,1.45,0,0,0,20.47,2ZM8.09,18.74h-3v-9h3ZM6.59,8.48h0a1.56,1.56,0,1,1,0-3.12,1.57,1.57,0,1,1,0,3.12ZM18.91,18.74h-3V13.91c0-1.21-.43-2-1.52-2A1.65,1.65,0,0,0,12.85,13a2,2,0,0,0-.1.73v5h-3s0-8.18,0-9h3V11A3,3,0,0,1,15.46,9.5c2,0,3.45,1.29,3.45,4.06Z"></path></svg>
</a>
</div>
</template>

<script>
export default {
// Component logic here (if needed)
}
</script>

<style scoped>
.media-icons {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
margin-top: 20px;
}

.media-icons a {
color: #333;
margin: 0 5px;
}

.media-icons a svg {
width: 24px;
}
</style>

However, we know the code is part of our monorepo, so let’s use Bit to accomplish this task. First we need to worry about the version, so let’s update it:

bit tag deleteman.vue-blog/components/media-icons

Notice how I referenced the component by specifying its ID using the following URI: [username].[scope]/folder/component-name

This process will also take care of running the tests and it’ll validate that everything works properly. Once the process finishes, we need to publish it. Again, we only need to publish this particular component, and we do that with:

bit export deleteman.vue-blog/components/media-icons

And, just like that, we’ve published version 0.0.2 of our component which is actually part of a bigger monorepo. And lo and behold, now we have a proper preview of our component:

Let’s now explore the next step: scaling and using our components somewhere else.

Did you like what you read? Consider subscribing to my FREE newsletter where I share my 2 decades’ worth of wisdom in the IT industry with everyone. Join “The Rambling of an old developer”!

Scaling our Vue Monorepo

So far, we’ve managed to set up a simple monorepo, and with it, we’ve explored how to solve the problems that monorepos pose (like versioning and deploying components without affecting others).

But once projects start growing, we have to keep in mind certain aspects:

  • How can other teams collaborate with you on your components?
  • How can others reuse your components?
  • What about documentation? Is there a solution for it?

Bit has already proven that it successfully tackles collaboration. Through the use of scopes you can organize the components and split your teams accordingly.

As for reusing them, as I already mentioned, it’s just a matter of deciding how to install them. If the plan is to reuse and improve, the best option is to “import” them.

For example, if you were to be interested in using my NavBar component and expanding on it, you’d use the following line from within your own workspace (yes, you need to have a workspace configured):

bit import deleteman.vue-blog/components/nav-bar

When you do that, the following things will happen:

  • A folder within your workspace will be created and the code of my component will be copied there.
  • A symlink inside the node_modules folder will be created pointing to the code of the component. Why? Because that way you can install the component as a normal one, and still have the ability to make changes to the code.

And from within your code, you’d import the NavBar like this:

import {NavBar} from '@deleteman/vue-blog.components.nav-bar'

The way in which you can import the components imported with Bit is definitely one of my favorite aspects of the tool.

You can easily access the code of the component, but you still get to use it like a normal dependency, without having to worry about local paths.

You can learn more about how Bit does dependency management with this incredible video tutorial:

What about the monorepo documentation?

Let’s face it, the bigger the project, the bigger the docs gotta be unless you’re hoping for the project to die a horrible death.

Bit solves that problem by providing out-of-the-box support for MarkDown documentation files. If you can’t find them inside your component’s folder, simply create one that ends on .docs.md and write the documentation there.

You also have something called “compositions”, which are files that let you set up and use your component in different ways. These compositions then get inserted into the documentation section by default.

You can review how the documentation is going to look before exporting your component by using the following line:

bit start

This simple command will start a dev server and it’ll display a website like this:

Notice how when I click on the “media-icons” link, I get to see a basic doc (the title of “Media Icons” and the line underneath it). That text is part of my media-icons.docs.md file.

And the composition is simply the component rendered normally. Since this is a simple component, with a single composition we have everything we need, but you could have multiple ones exported from the same file. My composition file looks like this:

import BasicMediaIcons from "./media-icons-basic.fixture.vue";

export { BasicMediaIcons };

If you want to know more about writing documentation for Bit components, check out this page from their docs.

In conclusion, not very often do we get to say that we can have our cake and eat it too, but with a component-driven approach, we can safely say that we do.

CDD gives developers the ability to work with a monorepo as if it was a multi-repo. Getting the benefits of monorepos, and solving the problems they come with.

And on top of that, Bit gives you CDD superpowers. Using it, you can ignore your monorepo entirely and treat your code as a sum of individual components.

What are your thoughts on the CDD approach? Have you found any other tool that solves every problem like Bit does? Or are you having to use multiple ones to function?

--

--

I write about technology, freelancing and more. Check out my FREE newsletter if you’re into Software Development: https://fernandodoglio.substack.com/