Adding Nx to Lerna/Yarn/PNPM/NPM Workspace

In our teams we see a shift away from Lerna and a strong preference to use Nx for managing JavaScript-based monorepos. Thoughtworks Technology Radar 2021

If you have a monorepo that is powered by Lerna, Yarn, PNPM, or NPM, you can make it a lot faster by running the following:

npx add-nx-to-monorepo

Watch this 3-min video to see how the command works and what next steps are:

npx add-nx-to-monorepo does the following:

  1. Add Nx to your package.json.
  2. Create nx.json, containing all the necessary configuration for Nx.
  3. Set up Nx Cloud (if you chose "yes").

If you are familiar with Turborepo, check out this guide. At this point, Nx can do anything Turbo can, and much more.

What You Get Right Away

Run Any Npm Scripts

After you run the command above, you can run any npm script using Nx. For instance, if myproj has a build script, you can invoke it using nx build myproj. If you pass any flags, they are forwarded to the underlying script.

Parallelization and Task Invariants

Nx knows how your projects relate to each other. For instance, if Project A depends on Project B, Nx will build Project B first before building Project A.

When you run nx build myproj, Nx doesn't just build myproj, it first makes sure the results of building all myproj's dependencies are in the right place. If the right files are in the right place, Nx will do nothing. If not, Nx will check if the right files are in its computation cache. If yes, Nx will restore them. If not, Nx will build the dependencies. In other words, Nx will use the faster way to get the context for building myproj ready. Nx also knows which tasks can run in parallel and which tasks cannot be. Nx will parallelize the tasks without breaking any invariants.

Finally, Nx is also much better at minimising your CPU's idle time, so running the same command via Nx will often be a lot faster than using Lerna.

Computation Caching

Nx supports computation caching. If it has seen the computation you are trying to perform, it's going to extract the result from its cache instead of running it. To see it in action, run the same command twice: nx build myproj and then again nx build myproj. This cache can be shared with your teammates and your CI. Your whole organisation will never build or test the same thing twice when using Nx.

In addition to restoring all the files, Nx replays the terminal output as well, so you don't lose any information when running a cached command. Other tools performing computation caching (e.g., Turborepo) change the terminal output of the commands you run. They don't preserve animations and colors. We instrument Node.js to be able to capture terminal output as is. When running, say, an npm script via Nx, the output will not be modified. The same is true when Nx restores the output from cache.

Learn about computation caching.

Distributed Task Execution

Nx is the only build system used by the JavaScript community that supports this feature, and this is the most powerful feature of Nx.

Imagine you are running nx affected --build. Normally this command runs the build target for the affected projects in parallel on the same machine. However, if you enable distributed task execution, the command will send the task graph to Nx Cloud. Nx Cloud agents will then pick up the tasks to execute them.

This happens transparently. If an agent needs the output of lib1 to build app1, and some agent built lib1, the first agent is going to fetch the needed output before running the build task.

As agents complete tasks, the main job where you invoked nx affected --build will start receiving created files and terminal outputs. After nx affected --build completes, the machine will have the build files and all the terminal outputs as if it ran it locally.

Using Distributed Task Execution you can keep your CI fast, with practically no effort, regardless of the size of your workspace.

Learn more distributed task execution.

Affected Commands

Nx automatically analyzes your workspace to know what projects are affected by your commit. Simply run: nx affected --target=test to see it in action. Often, Nx is able to do a better job detecting affected than other tools because it looks not just at the changed files but also at the nature of the changes.

Learn more "affected".

Workspace Visualization

Run nx graph to see a visualization of your workspace. nx affected:graph shows what is affected by your commit. nx graph --watch watches your workspace for changes and updates the visualization.

GitHub integration

If you said "yes" to Nx Cloud, you can enable Nx Cloud - GitHub integration to get a much better overview of what happens in your PRs.

Nx Console screenshot

VS Code Plugin

Nx Console screenshot

Lerna and Nx Command Comparison

Lerna:

1{
2  "private": true,
3  "scripts": {
4    "build:all": "lerna run build",
5    "build:app1": "lerna run build --scope=app1",
6    "build:since": "lerna run build --since=main",
7    "test:app": "lerna run test",
8    "test:app1": "lerna run test --scope=app1",
9    "test:since": "lerna run test --since=main",
10    "dev": "lerna run dev --stream --parallel",
11    "dev:app1": "lerna run dev --stream --scope=app1"
12  },
13  "devDependencies": {
14    "lerna": "*"
15  }
16}

Nx + Lerna:

1{
2  "private": true,
3  "scripts": {
4    "build:all": "nx run-many --target=build --all",
5    "build:app1": "nx build app1",
6    "build:since": "nx affected --target=build",
7    "test:all": "nx run-many --target=test --all",
8    "test:app1": "nx test app1",
9    "test:since": "nx affected --target=test",
10    "dev": "nx run-many --target=dev --all",
11    "dev:app1": "nx dev app1"
12  },
13  "devDependencies": {
14    "lerna": "*",
15    "nx": "*"
16  }
17}

Learn more about Nx CLI.

Next Steps

All this works without your having to change your repo in any way. Whatever setup you have still works the same way but faster and with better dev ergonomics. But Nx enables much more than that.

Nx is like a VS Code of build tools. It has a very powerful core, but it's really the plugins and extra capabilities that really transform how you develop.

Nx has first class support for React, Next.js, React Native, Angular, Node, NestJS, Jest, Cypress, Storybook and many more. All the plugins are designed to work together and create a cohesive and pleasant to use dev environment.

In addition, Nx makes a lot of things much easier, like building large apps incrementally, distributing CI (no point in doing caching unless you can do that), enforcing best practices, building design systems.

Excluding Sources

The add-nx-to-monorepo command does its best to figure out what projects you have in the repo. Similar to other tools, it looks at the workspaces property in the root package.json and tries to find all package.json files matching the globs. You can change those globs to exclude some projects. You can also exclude files by creating an .nxignore file, like this:

third_party # nx will ignore everything in the third-party dir

Enabling JS Analysis

The add-nx-to-monorepo command adds the following to the generated nx.json. This disables JS analysis, such that Nx only analyzes package.json files like Lerna or Turborepo.

1{
2  "pluginsConfig": {
3    "@nrwl/js": {
4      "analyzeSourceFiles": false
5    }
6  }
7}

We do this because most existing Lerna monorepos have implicit dependencies between projects Lerna knows nothing about. By adding "analyzeSourceFiles": false we are trying to make sure that Nx sees the same project graph Lerna does, even though the graph is often incorrect.

You can remove the section in the config, which will enable the JS/TS analysis. In this case Nx will consider all import and require statements in your JS/TS files when creating its project graph. If you do that, you can also add the paths property to the root tsconfig.base.json (if you don't have this file, create it), which will tell Nx how to resolve imports.

1{
2  "compilerOptions": {
3    "paths": {
4      "one": ["packages/one/index"],
5      "one/*": ["packages/one/*"],
6      "two": ["packages/two/index"],
7      "two/*": ["packages/two/*"]
8    }
9  }
10}

Real world examples of using add-nx-to-monorepo

Speeding Up Facebook React Monorepo with Nx

Speeding Up Remotion Monorepo with Nx

Speeding Up Storybook Monorepo with Nx