Create a monorepo with npm workspaces
npm 7 introduced the concept of workspaces to facilitate the management of monorepos. Here we will explore how to create one, manage local and external dependencies, and more.
There are two things to keep in mind when working with npm workspaces:
- There’s only one
node_modulesfolder located at the root of the repo. - The installation of npm packages should be done at the root of the repo and
using the
--workspace(or-w) argument when needed (we will see more about it down below).
Here’s the folder structure of the monorepo we are going to work with in this blog post:
- package.json
- packages/
- utils/
- package.json
- app1/
- package.json
- app2/
- package.jsonCreate the main package.json
First of all, let’s create a folder for the monorepo and create the main package.json
mkdir monorepo
cd monorepo
npm initCreate a workspace
Then, let’s create our first workspace, utils, which will be shared in both
apps. The --workspace (or -w) argument specifies the path to the workspace.
npm init -w packages/utilsYou can create a scoped package inside a workspace too using the --scope
argument:
npm init -w packages/utils --scope frago12
The command above does a few things:
- Creates the specified folder(s)
./packages/utilsand thepackage.jsonfor that workspace.
- pacakge.json
- packages/
- utils/
- package.json- Adds a reference to the
utilsworkspace in the mainpackage.json.
{
"name": "monorepo",
"workspaces": [
"packages/utils"
],
...
}- Generates a symlink of the new package (workspace) into the
node_modulesfolder at the root of the monorepo (we can verify this by runningnpm ls).
You can also symlink the local packages (workspaces) to the node_modules
folder by executing npm install in the root of the monorepo.
Install external dependencies
Once we have a workspace created, we can install core dependencies (available for all workspaces) or worskpace-specific dependencies (available only for a specific workspace).
As mentioned above, all the dependencies must be installed from the root folder.
Core dependencies
Just npm install them from the root folder. It will add the dependency to the
main package.json file located at the root of the monorepo.
npm install -D prettier{
"name": "monorepo",
"devDependendencies": {
"prettier": "...",
},
...
}Worskpace-specific dependencies
Same as the core dependencies, but you need to specify the workspace (-w)
where you want to install the dependency.
npm install date-fns -w @frago12/utilsThis will add the date-fns dependency to the utils/package.json file.
{
"name": "@frago12/utils",
"dependencies": {
"date-fns": "..."
}
}Reference a local package
Let’s say at this point we have created a couple more workspaces,
@frago12/app1 and @frago12/app2. And, we want to install the package we
previously created, @frago12/utils , in one of them.
A local package can be installed in the same way external packages are.
npm install @frago12/utils -w @frago12/app1The command above will add the dependency in the package.json file of the specified workspace.
{
"name": "@frago12/app1",
"despendencies": {
"@frago12/utils": "1.0.0"
}
}That means we can import our local package just like any other dependency inside the app.
import { something } from "@frago12/utils"
...npm will try to resolve local pacakges first. If the package is not found it will try to resolve them from the npm registry. Some versions of npm could have a buggy behavior related to this. See the found issues.
Execute npm commands
One of the main advantages of having a monorepo is that you can execute npm commands in multiple workspaces simultaneously, for example, upgrading the version of an npm dependency in all workspaces:
npm install react@18 -wsOr only in specific workspaces:
npm install react@18 -w @frago12/app1 -w @frago12/app2The same strategy works for executing npm commands:
npm run build -ws --if-presentNotice the —if-present flag. It will execute the build command in all
workspaces where that script exists.
Found issues
While working with npm workspaces, I found an issue with specific versions of npm where it no longer resolves the local packages first. Fortunately, [email protected] fixes the problem. See BUG ^7.20.3 no longer resolves local package first on install (workspaces) · Issue #3637 · npm/cli · GitHub