What is Monorepo?
Monorepo is an architectural concept and stands for Mono Repository, which is self-explanatory. When a project requires multiple modules, for example, instead of building them using separated repositories you use only one.
As Alexander Noel mentions in this great article,
Imagine that, instead of a small app, you need to maintain a huge platform consisting of a lot of functional areas. If you are thinking about architecture, you will want to do two main things: Separate concerns and avoid code dupes.
So, think of Monorepo as an approach to help you deal with multiple modules/projects independently inside the same repository.
Here are some of the advantages of using Monorepo:
- One place to store all configs and tests;
- Easily refactor global features with atomic commits;
- Simplified package publishing;
- Easier dependency management;
- Re-use code with shared packages while still keeping them isolated;
And some disadvantages:
- No way to restrict access only to some parts of the app;
- Poor Git performance when working on large-scale projects;
- Higher build time;
You can find more details about each one of these topics in
the same article I've mentioned before.
TL;DR
The goal of this article is to help you to build a monorepo boilerplate to allow you to:
- manage multiple applications using Lerna;
- configure test files properly;
- importing shared components within the application;
Building a Monorepo VueJS Project using Lerna
I assume you already have NodeJS + NPM installed in your computer in order to follow the steps below.
1. Lerna
According to its Github, Lerna is "A tool for managing JavaScript projects with multiple packages." I advise you to read a little bit more about it to check its main features and how to use them.
Let's get our hands dirty.
i) In an empty folder, run npm init
to start a new project. Answer each one of the questions ans, after everything is set up, run the following command to install Lerna:
npm install lerna
ii) Create a new folder named modules
in the root directory of your project. This is where all of your VueJS applications will be put.
iii) Create e new file named lerna.json
in the root folder of your project with the following content.
{
"packages": [
"modules/*"
],
"version": "independent"
}
By setting version
property as "independent" you will be able to publish packages independently in NPM, for example.
Obs.: Today, we still won't publish anything, but I might cover this topic in a further article, if you guys find it would be useful. :)
2. Install ESLint dependencies and plugins
In the next step, if you choose to create a new VueJS application using a default preset of Vue CLI, it will install ESLint dependencies within the module level, but, as we are dealing with a monorepo, it's good to install them globally in the root directory.
In a terminal window, go to the main folder of the project and run the following command:
npm install --save-dev eslint babel-eslint eslint-plugin-vue
I won't dive deep into the ESLint settings I usually use in my projects, but I intend to write a short article about it to share with you.
After installing the dependencies, close and reopen the workspace for changes to take effect.
3. Create the VueJS modules
Inside the modules
folder, create two new VueJS projects using Vue CLI. In a terminal command execute:
vue create shared --packageManager npm && vue create admin --packageManager npm
I'm going to use the default preset [VueJS 2] babel, eslint
for both projects and force the CLI to use NPM instead of yarn
, but you may select your favorite settings to proceed.
If your shared
module is not an executable one, you can remove the files App.vue
, main.js
and the public
folder from it.
4. Create jsconfig.json
and workspace files (Skip if you do not use VSCode)
When building multiple applications using monorepo , it is nice to use aliases to import components and functions across modules. So, combining VSCode with Vetur extension, you can create a jsconfig.json
file to tell the editor which alias correspond to each path.
All you need to do is place the file in the root directory with the following content:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"paths": {
"@shared/*": ["./modules/shared/src/*"],
"@admin/*": ["./modules/admin/src/*"]
}
},
"exclude": [
"node_modules"
]
}
Another good practice is to create a workspace file containing global configurations for you editor. When using VSCode, you need to create a file with the .code-workspace
extension in the root folder of the project. Other editors will use their own files.
This is how I've configured my workspace file for this monorepo sample project:
{
"folders": [{
"path": "."
}],
"settings": {
"editor.formatOnSave": false,
"editor.tabSize": 2,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"typescript.format.enable": false,
"javascript.format.insertSpaceAfterCommaDelimiter": false,
"javascript.format.semicolons": "remove",
"eslint.alwaysShowStatus": true,
"eslint.options": {
"extensions": [
".html",
".js",
".vue"
]
},
"eslint.validate": [
"vue",
"html",
"vue-html"
],
"vetur.validation.script": false,
"vetur.validation.template": false
}
}
5. Using shared components
As you might have guessed, the admin
module we've created will import components from the shared
module.
To make this possible, we need to customize some configurations in the vue.config.js
.
If you've used the default preset when creating the admin
module, there won't be any file with this name there, so you need to manually create a vue.config.js
in the admin
folder.
This content, is enough to make the admin
module "see" and import shared
components or functions:
const path = require('path')
const adminNodeModules = path.resolve(__dirname, './node_modules')
const sharedNodeModules = path.resolve(__dirname, '../shared/node_modules')
const adminSrc = path.resolve(__dirname, 'src')
const sharedSrc = path.resolve(__dirname, '../shared/src')
module.exports = {
configureWebpack: {
resolve: {
modules: [adminNodeModules, sharedNodeModules], // this will allow `admin` module to import NPM packages from `shared`
alias: {
'@shared': sharedSrc,
'@admin': adminSrc
}
}
}
}
Another thing that can be done is configure your VueJS project to also be able to import NPM packages from other modules.
This means that you may have dependencies installed in a common module and shared with several applications without the need to duplicate them per module.
6. Global NPM commands
Now that your first VueJS + Lerna monorepo structure is ready, you may want to add new commands to the root package.json
file, so that you are able to install all packages at once when first loading your project in a new machine.
Tests and linting may also be executed altogether.
In the root package.json
file, add the following scripts
{
"scripts": {
"postinstall": "lerna bootstrap",
"admin": "cd ./modules/admin && npm run serve",
"admin:build": "cd ./modules/admin&& npm run build",
"admin:test": "cd ./modules/admin&& npm run test",
"test": "lerna exec -- npm run test",
"lint": "lerna exec -- npm run lint"
}
}
The first command will install all of the dependencies of all projects inside modules
folder after you run npm run install
in the root directory.
All of the other commands are just shortcuts to access specific scripts of the modules or to test and lint all modules with one command.
You can find a fully working example of the code demonstrated here.
I hope you liked it.
Please, share and comment!
See you next week :)
Cover image by @gndclouds
Top comments (1)
lerna bootstrap is outdated now.