VueJS - Building a basic Monorepo project
Published: 2021-05-19 19:28:05

### 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.

![Repositories Types - Monorepo, Single-repo monolith and multiple repos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a5rf6lrgm1hosa72vq1r.png)

As Alexander Noel mentions in [this great article](https://www.toptal.com/front-end/guide-to-monorepos), 

>  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](https://nodejs.org/) installed in your computer in order to follow the steps below._

#### 1. Lerna

According to its [Github](https://github.com/lerna/lerna), 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:

```shell
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.

```json
{
  "packages": [
    "modules/*"
  ],
  "version": "independent"
}
```

By setting `version` property as "independent" you will be able to publish packages independently in [NPM](https://npmjs.com), 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](https://cli.vuejs.org/). In a terminal command execute:

```shell
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](https://marketplace.visualstudio.com/items?itemName=octref.vetur), 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:

```json
{
  "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:

```json
{
    "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:

```javascript
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

```json
{
  "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](https://github.com/vcpablo/vuejs-lerna-monorepo).

I hope you liked it.
Please, share and comment!

See you next week :)

_Cover image by [@gndclouds](https://unsplash.com/@gndclouds)_