Sharing Code in a monorepo

Monorepos let you share code across applications without friction. To do that, you'll be building packages to share code between your apps.

What is a package?

The word 'package' has a double meaning when it comes to monorepos. It can refer to either of these:

  1. A set of files you download from a registry into your node_modules, via a package manager like npm.
  2. A workspace containing code that can be shared between applications - by convention usually inside /packages.

This dual meaning can be very confusing to folks new to the monorepo scene. You're likely very familiar with package installation, but not so familiar with workspaces.

The fact is that they're very similar. A package is just a piece of shared code. Except that installed packages live in your node_modules, and local packages lives in a workspace - likely in your /packages folder.

Anatomy of a package

Each package contains a package.json. You're likely familiar with using these to manage dependencies and scripts in your applications.

However, you may not have noticed the main and name fields before:

packages/my-lib/package.json
{
  // The name of your package
  "name": "my-lib",
 
  // When this package is used, this file is actually
  // the thing that gets imported
  "main": "./index.js"
}

Both of these fields are important for deciding how this package behaves when it's imported. For instance, if index.js has some exports:

packages/my-lib/index.js
export const myFunc = () => {
  console.log("Hello!");
};

And we import this file into one of our apps:

apps/web/pages/index.jsx
import { myFunc } from "my-lib";
 
myFunc(); // Hello!

Then we'll be able to use the code inside the my-lib folder inside our applications.

To summarize, each package must have a name and a main declared inside its package.json.

Package resolution in package.json is a very complicated topic, and we can't do justice to it here. Other fields in your package.json may take precedence over main depending on how the package is being imported.

Check the npm docs for a guide.

For our purposes, using main will be good enough.

Next steps

We're going to introduce two styles of packages - internal packages and external packages:

Internal packages are intended to only be used inside the monorepo where they're housed. They are relatively simple to set up, and if your project is closed source they will be the most useful to you.

External packages are bundled and sent to a package registry. This is useful for design systems, shared utility libraries or any open source work. However, they introduce more complexity around bundling, versioning and publishing.