A Comprehensive Guide to npm Workspaces and Monorepos

Leticia Mirelly ϟ
4 min readDec 30, 2023

--

A little about current workspace managers

Yarn Classic x Yarn Berry Workspaces

Dependency Resolution: Yarn classic workspaces use a “pluggable” resolution algorithm, providing more flexibility in how dependencies are resolved. Like npm, Yarn uses hoisting to share common dependencies at the root level, unlike Yarn Berry, which has a new architecture with a “plug’n’play” resolution strategy, aiming for a zero-installation approach. It takes advantage of the concept of “PnP” files to represent dependencies without the need for a traditional node_modules directory. This is a complex choice if you want to migrate managers later because Yarn Berry eliminates the need for a central node_modules leading to faster and more efficient installations however brings significant changes in how dependencies are resolved.

Npm Workspaces

Dependency Resolution: npm workspaces utilize a “fixed” strategy for dependency resolution. Each workspace has its own node_modules, and dependencies are installed per workspace. However, common dependencies are hoisted to the root node_modules to optimize disk space.

Established Ecosystem Integration: Being the default package manager for Node.js, npm seamlessly integrates with the broader Node.js ecosystem.

Share Dependencies: npm Workspaces enable the sharing of dependencies between packages. If multiple packages require the same dependency, it can be installed once at the workspace level, optimizing disk space and simplifying dependency management.

Hands-on with npm workspace

Setup Project

mkdir your-project && cd your-project && npm init -y

Parent Workspace Configuration (root package.json)

{
"name": "my-monorepo",
"workspaces": [
"packages/*"
],
// Example of shared scripts in workspaces
{
"scripts": {
"build": "npm run build:package-a && npm run build:package-b",
"build:package-a": "cd packages/package-a && npm run build",
"build:package-b": "cd packages/package-b && npm run build"
}
}
}

Directory Structure

/my-monorepo
|-- /packages
| |-- /package-a
| |-- /package-b
|-- package.json

Package A

// /packages/package-a/package.json
{
"name": "package-a",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
}
}
// /packages/package-a/index.js
module.exports = () => {
console.log('Hello from Package A!');
};

Package B

// /packages/package-b/package.json
{
"name": "package-b",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"package-a": "1.0.0"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1"
}
}
// /packages/package-b/index.js
const packageA = require('package-a');

module.exports = () => {
packageA();
console.log('Hello from Package B!');
};

npm link in Workspaces:

Local Development and Testing:

npm link

is a powerful tool in the npm ecosystem, allowing packages to be symlinked for local development. In the context of npm workspaces, this tool can be used to connect packages within the workspace for seamless local development and testing.

Mechanism:

npm ensures that when using npm link, the linked package is correctly hoisted to the top-level node_modules directory, maintaining the integrity of the workspace structure. After running the above command, package-a will be symlinked and hoisted to the top-level node_modules, allowing seamless development.

# Example npm link in workspaces
cd packages/package-a
npm link
cd ../package-b
npm link package-a

After setting up the structure, running npm install in the root directory installs all dependencies for all packages, linking them where necessary.

Helpful Commands for Reference

Installing Workspace Dependencies

Install dependencies for all packages defined in the workspace configuration.

npm install

Scopes and Private Packages in Workspaces

  • Scopes (@scope/package) can also be used in workspaces, and the logic of private scopes still applies.
# Example of installation in workspaces with private scope
npm install @scope/package-a --workspace

Listing Dependencies Across Workspaces

List dependencies across all workspaces.

npm ls

Hoisting in npm Workspaces

In npm workspaces, hoisting plays a crucial role in optimizing the installation and management of dependencies across multiple packages within the same workspace.

Common Dependencies

  • When multiple packages within a workspace depend on the same package and version, npm hoists that dependency to the top-level node_modules directory. This means that instead of having duplicate copies of the same dependency in each package's node_modules, there's a single shared instance at the root.
// Example workspace package.json
{
"workspaces": ["packages/*"],
"dependencies": {
"lodash": "^4.17.21"
}
}

If two packages within the workspace use lodash, npm ensures that only one copy of lodash is present at the top-level node_modules directory.

Scoped Packages

  • For scoped packages, hoisting also occurs within the scoped directory, further optimizing the organization of dependencies.
// Example workspace package.json with scoped package
{
"workspaces": ["packages/*"],
"dependencies": {
"@my-monorepo/package-a": "^1.0.0"
}
}

The scoped package, in this case, will be hoisted to the top-level node_modules/@my-scope/package-a directory.

Benefits of Hoisting in npm Workspaces:

Disk Space Efficiency

By hoisting common dependencies, npm workspaces optimize disk space usage, as there’s only one copy of a dependency shared among multiple packages.

Faster Installations

Hoisting reduces the time it takes to install dependencies, as npm can avoid redundant installations and copy operations.

Consistent Dependency Versions:

Hoisting ensures that all packages within the workspace use the same version of a dependency, avoiding version conflicts.

Conclusion

While it is feasible to manage tasks at the individual workspace level, centralizing shared responsibilities at the root level fosters a more coherent and straightforward approach. Embracing npm Workspaces can significantly improve monorepo management, fostering enhanced collaboration, modularity, and overall development efficiency. However, the choice between npm Workspaces, Yarn Workspaces, and Yarn Berry hinges on considerations such as ecosystem familiarity, project requirements, and personal preferences. Each of these tools possesses unique strengths, and the decision should be carefully aligned with the specific needs and objectives of the project.

--

--

Leticia Mirelly ϟ
Leticia Mirelly ϟ

Written by Leticia Mirelly ϟ

Software engineer. Tempting writer. I like AI engineering stuff.

Responses (1)