The monorepo structure is an increasingly popular way to organize JavaScript code. In a JavaScript monorepo, you typically have a package.json
file at the root of your project that points to the package.json
files of the packages in your monorepo. These child package.json
files are typically stored at packages/project-name/package.json
.
Motivation for moving to a monorepo
At my current job, I'm leading the push to reorganize our frontend into a monorepo structure. We currently have one main React app. I'm working towards moving our marketing pages to a static site generator and another team at my company is building a new design system that will contain our basic React components. Housing these three packages in a monorepo makes sense as they will share code and have many identical dependencies.
Benefits of a monorepo
By placing them in a monorepo, we get some neat benefits. One benefit is that we can importing from one package into another directly without publishing to a package registry. Additionally, Yarn will automatically de-duplicate dependency code stored in our node_modules
folders when multiple packages depend on the same version of a dependency.
The gotcha, of course, is that your packages have to have the exact same version dependencies. This can be a tough problem to solve. You can try setting all your packages to have the same dependency strings, but that's easy to break. It just takes one person to upgrade a dependency in one package but not in another to cause problems.
Enforcing identical dependencies in a monorepo
The way around the weakness above is to only allow a single package in your monorepo to import a dependency. Then export that dependency and make the other packages import it from their sibling. Here's what that looks like:
In your project root:
// package.json
{
"workspaces": [
"packages/*"
],
...
}
In your package that imports shared dependencies:
// packages/shared-dependencies/package.json
{
"dependencies" : {
"react": "16.x",
...
},
...
}
// packages/shared-dependencies/react.js
export * from "react"; // to re-export named exports
export { default } from "react"; // to re-export the default export
In each of your packages that needs the shared dependency:
// packages/web-app/package.json
{
"dependencies" : {
"shared-dependencies": "*",
...
},
...
}
// packages/web-app/src/App.js
import React, { useState } from "shared-dependencies/react"
Sources
Photo by Elaine Casap