Skip to content

Monorepo Primer

Modern software dependency graphs are complex and brittle. The following Graph shows a theoretical problem that we may currently face between our own internal and external dependencies.

Using a monorepo is a great software development strategy for managing any number of dependent code modules in a single repository. oneRepo makes this even easier by sharing smart tooling, creating safer linking with source dependencies, and unifying, verifying, & speeding up many aspects of the development process.

JavaScript (and TypeScript) based monorepos come with their own set of problems due to the many ways that source code can be configured, linked, and resolved across boundaries. This is usually referred to as the require resolution algorithm (for CommonJS) and import resolution algorithm for ECMAScript modules. Notice that there are already two possible ways to resolve modules. There are more, but for the following examples, we’ll focus on the CommonJS spec.

Given two applications: one for our logged-out, public facing marketing pages (example.com), and another for the logged in users (app.example.com). We have one shared dependency under our control for our component library (@org/component-library) and another for interfacing with our backend APIs (@org/redux-api):

Loading graph...

In this situation, we have two web applications, pub and auth that are maintained with several shared internal repositories. However, since ensuring that dependencies are up to date is a manual process, developers only update dependencies when they know that the update is needed (eg, for new features). This results in a dependency Graph could have multiple versions of the same dependency.

Notice that the Logged-in website app.example.com now relies on two separate versions of React. Typically this shouldn’t be a problem, as package managers and the Node.js require resolution algorithm will handle loading the correct version per shared module. However, many packages, React included, explicitly do not work if there are multiple versions loaded within the same application.

While we can and should take steps to ensure that we don’t have conflicting major versions of dependencies, it is entirely possible that we could ship code with differing versions that have major differences in the way that they work. For example, if we were to ship an application like above with two versions of React and it slipped through, the worst case scenario is that one of these apps deploys in this state and ends up with a completely broken page response – resulting in potential loss of new or returning customers.

In an ideal scenario, we would have tools that automate enforcement of the same dependency version for all packages across all applications and packages. This would ensure maximum compatibility, fewer bugs, and smaller byte transfers to web visitors.

Loading graph...

Local dependencies in a monorepo

Section titled Local dependencies in a monorepo

When package managers are configured as monorepos, they follow an array of glob patterns to determine where to find other source-level package.json files for determining install dependencies for the repository.

npm is configured using the workspaces field in the root package.json:

package.json
{
"name": "root",
"private": true,
"workspaces": ["apps/*", "modules/*"]
}

Using the workspaces globs configured previously for the package manager each matching location with a package.json will have its dependencies included to install. Taking a look at our three workspaces:

./apps/menu/package.json
{
"name": "menu",
"dependencies": {
"tacos": "workspace:^",
"tortillas": "workspace:^"
}
}
./modules/burritos/package.json
{
"name": "burritos",
"dependencies": {
"lettuce": "^4.4.3",
"tortillas": "^2.0.0"
}
}
./modules/tacos/package.json
{
"name": "tacos",
"dependencies": {
"lettuce": "^4.0.0",
"tortillas": "^1.8.0"
}
}

Notice that each of the Workspaces have dependencies on lettuce and tortillas, but there is a bit of a version mismatching. While both lettuce dependencies allow for major version 4.4.3 and above, tortillas on the other hand require separate major versions (v2 for burritos and v1 for tacos). In order to satisfy this, the package manager should deduplicate lettuce and resolve to only picking 4.4.3, but will need to install tortillas versions separately so there are no conflicts. This will result in a file tree that may look something like this:

  • Directoryapps/
    • Directorymenu/
      • package.json
  • Directorymodules/
    • Directorytacos/
      • package.json
    • Directoryburritos/
      • Directorynode_modules/
        • Directorytortillas/ @2.1.0
          • …
      • package.json
  • Directorynode_modules/
    • burritos → ../modules/burritos
    • tacos → ../modules/tacos
    • Directorylettuce/ @4.5.0
      • …
    • Directorytortillas/ @1.9.4
      • …
  • package.json
Version control system

Version control systems (VCS) are tools to aid with tracking and managing changes to software code. VCS are software tools that help software teams manage changes to source code over time.

Currently, oneRepo is only compatible with git.

Repository

Repositories are collections of files and folders managed by a VCS that track changes to software.

Monorepo

A Repository that includes more than one project, typically with inter-connected and shared dependencies between the projects.

Root

The root folder of a Repository. This term may also be used to refer to the root Workspace of a Monorepo.

Workspace

A Workspace is a singular project within a Monorepo that is either able to be published, deployed, or imported as a dependency of another Workspace.

Graph
The Graph is a representation of all Workspace interdependencies within a Monorepo.