Vous voyez des erreurs dans cet article ? reportez-les à contact@izio.fr
Comment bien gérer un monorepo en Node.js
Gérer un repo en Node.js n'est pas facile. Il y a beaucoup d'outils existants qui permettent de résoudre ce problème. Mais quelle est la manière la plus propre pour gérer un monorepo ?
What a monorepo is?
If you still didn't hear about mono repository despite all the articles on the subject, then you live in a cave. A mono repository is a repository that contains every project or sub-project of a structure.
The other way to maintain projects under a version control system (VCS) is to split projects into different repositories.
Do you see this forest picture? it's my mono repository and every tree is a subfolder containing a project. In comparison, having multiple forests that contain only one tree, is a multi-repository architecture. I am not going to compare the two ways of managing projects for two reasons:
- It depends on what you want to achieve
- Internet has plenty of articles speaking of this subject (⚠️ a lot of these articles are biased)
The Example
Considering this project tree:
nodejs-monorepo-example
┣ packages
┃ ┣ api
┃ ┃ ┣ index.js
┃ ┃ ┗ package.json
┃ ┣ maths
┃ ┃ ┣ index.js
┃ ┃ ┗ package.json
┃ ┗ website
┃ ┃ ┣ index.js
┃ ┃ ┗ package.json
┗ package.json
Note: To make it simple, we'll be using pure Javascript here. But the same is doable in Typescript, in fact, this website has been built using this architecture in Typescript.
We have one packages
folder containing every sub-project. maths
package will contain cross-package sharable code.
In order to generate package.json
file, you can use npm init
. Replace npm
with your favourite package manager (yarn, pnpm, etc.).
The differents ways
There are multiple ways to achieve a monorepo architecture in Node.js, from what I know there are three simple methods :
I tried using the first two solutions, but they most likely will not fit all needs.
You are free to use one of these three methods. However, I'll show you how to set up the workspace only on pnpm
.
The proper way
pnpm is a package manager made in Typescript that focuses on performance, efficiency and (guess what?) monorepo! 🎉
pnpm
offers many installation methods. I assume you already have a package manager: npm install -g pnpm
.
Let's create a workspace definition file pnpm-workspace.yaml
:
1# pnpm-workspace.yaml 2 3packages: 4 - "packages/**"
In our example, this file isn't needed because by default pnpm
assume all packages of all subdirectories are included.
The next step is to name our packages
. Edit all package.json
files and make an alias name so you remember it's an internal package:
1{ 2 "name": "@example/maths", 3 "version": "1.0.0", 4 "description": "An internal maths package", 5 "main": "index.js", 6 "scripts": { 7 "start": "node index.js" 8 }, 9 "author": "IzioDev", 10 "license": "ISC" 11}
Now, we can share the code located in one package with another package. Try to run pnpm add @example/maths
while you're in /packages/website
.
🤔 It's weird, it's not saying that the package wasn't found on the npm
registry. Yes, correct, by default pnpm
look all over your packages before looking for an npm
package.
You can check this assertion by running pnpm add @example/test
and how it's looking for an npm
package.
Let's check the website package.json
file:
1// packages/website/package.json 2 3"dependencies": { 4 "@example/maths": "workspace:^1.0.0" 5}
It created a new dependency with a pnpm
syntax. But don't worry, if you need these packages to be published on the npm
registry, at publish time, this dependency would become :
"@example/maths": "^1.0.0"
Note: Since now, we've only used Common JS, However, if you want to use ESM, just add "type": "module"
to all package.json so that node
can know it's an ES module.
We can now create an exported add
function into /maths/index.js
:
1// packages/maths/index.js
2
3// Common JS
4const add = (a, b) => a + b;
5
6module.exports = {
7 add,
8};
9
10// Or, ESM
11export const add = (a, b) => a + b;
Edit /website/index.js
to import our freshly created add
function:
1// packages/website/index.js
2
3// Common JS syntaxe
4const add = require("@example/maths").add;
5
6// Or, ESM syntaxe
7import { add } from "../maths";
8
9// Use the imported function
10const result = add(1, 2);
11
12console.log(`The result is: ${result}`);
Since we're lazy developers, we don't want to change directory to /packages/website
every time in order to run the entry file, right? So let's tweak our package.json
at the project root directory:
1// package.json 2 3"scripts": { 4 "start:website": "pnpm -C packages/website start" 5}
The -C
option is to tell pnpm
we want to run the command on another path, so here: on packages/website
, and we're running the start
command, as you would probably do using npm
.
Ok, let's try this all together pnpm start:website
at the project root directory will output:
The result is: 3
No really? 🤔 Thanks node for being so smart.
Conclusion
Well, you have everything you need to set up a Node.Js monorepo and manage your internal packages properly.
In this article, we've:
- Installed pnpm
- Seen the different ways (npm, yarn, pnpm workspaces)
- Created a pnpm workspace with multiple packages
- Made a package dependent on another
- Created an utility script at the project root to make our life easier
What's next?
In order to go further, you can try publishing your packages on a registry using the pnpm publish command.