Hello world,
which means that this is my first article — first of many — as I intend to write a series on creating a monorepo structure for Vue.
What problem are we trying to solve?
Vue has become quite popular over the years, and more mid-size companies — and even some big names like Nintendo, Adobe, or Gitlab to name a few — are adopting it. Sometimes, they even choose it as the primary Framework for their products. That's why growing codebases containing many Vue packages are becoming common.
Juggling with projects and packages, publishing, updating, and dealing with the consequences of postponing the last step longer than we intended, due to fast-paced workflows. Many of us were already there, or are right now. This raises the question:
Is there a good alternative and what are the trade-offs?
In this series, I'll try to deliver some answers, as well as provide one of the solutions which you can see complete in this template. Feel free to use it as a base to kickstart your project or simply try it out.
If you have any questions, feel free to leave a comment or add a suggestion in the sample repo.
Monorepo
There is at least one good option on which we will focus as it's definitely a mature and battle-proven one - monorepo.
In contrary to microservices, which have a bunch of additional complexity this one embraces strengths of a monolith structure. I recommend a very good article on that matter from Maxi Ferreira - May I Interest You In a Modular Monolith?
The simple explanation is that we put everything into one directory. That's it? Well... not exactly. There are many additional possibilities, but let's start with the easy stuff.
One repo, but not much more
In the simplest form, it means that we create one repository with a bunch of sub-directories for our projects and libraries (packages) and that's it. The rest stays as it is: pushing to the origin, publishing with npm or Lerna. Maybe even with semantic-release if you've already made some optimization of the process.
Pros:
- Many projects in one place -> less opened windows
- Better overview of your team's work
Cons:
- More branches -> potential chaos in the git tree
Add workspace
But what about managing external dependencies and consuming your libraries (packages)?
If you don't want to go to every project and update dependencies manually you should consider so-called workspaces. More about them in the next article in the series. In short, they will allow you to update more, maybe even all dependencies at once, and still keep full control over your projects, when some more granular interference will be required.
Sample workspace configuration with pnpm
To enable workspace features in pnpm, you need a pnpm-workspace.yaml
file at the root of your repository.
packages:
- 'apps/*'
- 'libs/*'
This file will tell pnpm to treat all projects in the apps
and libs
directories as workspace packages.
More about it in the pnpm workspace docs.
With pnpm, you can manage workspaces and dependencies across all your projects in monorepo.
A couple of basic commands:
# Add a dependency to all projects in the workspace
pnpm add <package-name> -w
# Add a devDependency to all projects in the workspace
pnpm add <package-name> -Dw
# Add a dependency to a specific project
cd apps/app_1
pnpm add <package-name>
# Add a devDependency to a specific project
cd apps/app_1
pnpm add <package-name> -D
# Update all dependencies in the workspace to their latest versions
pnpm update --latest
Pros:
- Faster update of dependencies for many projects at once
- Less juggling with versions
Cons:
- Requires updating more projects at once if they are tied to the same dependency in the root directory. They can still be split but it's somehow contrary to the goal
Add direct connection of dependencies
The workspaces are allowing us to manage external and internal dependencies way more easily. But what if I told you that you can directly use your other projects without even building them, not even mentioning publishing? With correct typescript configuration, you can even have types and intellisense still working. That is what you can achieve by connecting other of your libraries (previously packages) in your package.json
like this:
{
"name": "@monorepo/app_1",
"version": "1.0.0",
"type": "module",
"scripts": {
// ...
},
"dependencies": {
"@monorepo/commons": "workspace:*",
"@monorepo/ui": "workspace:*"
},
// ...
}
Notice the asterisks instead of version numbers. These mean that we are using whatever version is the newest. In our case, it's always the local one.
Pros:
- Always the current version (also while developing in branches)
- No need to publish libraries as packages and reinstall them
- No local linking required to try out new library versions
Cons:
- After a merge, dependencies might differ, potentially causing problems
This caveat can be mitigated with good test coverage and TypeScript running automatically through the complete codebase on the server before every build (which I highly recommend, regardless; ex. on the dev
branch). This can be easily automated with husky.
If you want to learn more about creation of monorepo with Vue, check out other tutorials in the series. If you prefer starting with a solid foundation - try this template, where I’ve put all of this into practice.
Top comments (0)