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-based monorepos
Section titled JavaScript-based monoreposJavaScript (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.
Resolving dependencies
Section titled Resolving dependenciesGiven 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 monorepoWhen 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
:
Yarn, like npm, uses the workspaces
field in the root package.json
, but also enables additional features, like the workspace:
protocol, focused installs, and parallel execution. It is recommended to set Yarn to use install modules using the node-modules linker to avoid compatibility issues
.
Unlike npm and Yarn, pnpm uses a special pnpm-workspace.yaml
file.
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:
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
Glossary
Section titled Glossary- 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.