Getting Started with Package-Based Repos
Example repository/nrwl/nx-recipes/tree/main/package-based
Create a New Workspace
Start by creating a new workspace. We can use the following command that will help us set it up.
❯
npx create-nx-workspace@latest package-based --preset=npm
The file structure should look like this:
1package-based/
2├── packages/
3├── nx.json
4└── package.json
5
Create a Package
The packages
folder is where we host our monorepo libraries. Create a new is-even
folder with the following structure:
1package-based/
2├── packages/
3│ └── is-even/
4│ ├── index.ts
5│ └── package.json
6├── nx.json
7└── package.json
8
Update the content of the files to match the following:
1export const isEven = (x: number) => x % 2 === 0;
2
Next install TypeScript (notice we're using tsc
for the build
script in package.json
above). While we could install TypeScript at the package-level, it is more convenient to have it globally for the entire monorepo. Run the following command at the root of your workspace.
❯
npm add -D typescript
Next run your build
script with:
❯
npx nx build is-even
Your built package now exists in the packages/is-even/dist
directory as expected.
Local Linking of Packages
Linking packages locally in a package-based monorepo style is done with NPM/Yarn/PNPM workspaces. In this specific setup we use NPM workspaces (see the workspaces
property of the package.json
file at the root of your workspace).
To illustrate how packages can be linked locally, let's create another package called: is-odd
. You can copy the existing is-even
package:
1package-based/
2├── packages/
3│ ├── is-even/
4│ │ ├── index.ts
5│ │ └── package.json
6│ └── is-odd/
7│ ├── index.ts
8│ └── package.json
9├── nx.json
10└── package.json
11
Here's what the content of the files should look like:
1import { isEven } from 'is-even';
2
3export const isOdd = (x: number) => !isEven(x);
4
is-odd
imports the isEven
function from your is-even
package. Therefore its package.json
file should list the is-even
package in its package.json
file as a dependency.
The workspaces
property in the root-level package.json
tells NPM to create links for all packages found in the packages
directory. This removes the need to publish them first to a NPM registry. (Similar functionality exists for Yarn and PNPM workspaces as well.)
At the root of your workspace run:
❯
npm install
NPM will create a Symbolic Link in your file system at: node_modules/is-even
and node_modules/is-odd
, so they reflect changes to your packages/is-even
and packages/is-odd
directories as they happen.
Task Dependencies
Most monorepos have dependencies not only among different packages, but also among their tasks.
For example, whenever we build is-odd
we need to ensure that is-even
is built beforehand. Nx can define such task dependencies by adding a targetDefaults
property to nx.json
.
1{
2 ...
3 "targetDefaults": {
4 "build": {
5 "dependsOn": ["^build"]
6 }
7 }
8}
9
This tells Nx to run the build
target of all the dependent projects first, before the build
target of the package itself is being run.
Remove any existing dist
folder and run:
❯
npx nx build is-odd
It will automatically first run build
for the is-even
package, and then the build
for is-odd
. Note that if is-even
has been built before, it would just be restored out of the cache.
Cache Build Results
Run the command:
~/workspace❯
npx nx build is-even
1> nx run is-even:build [existing outputs match the cache, left as is]
2
3
4> is-even@0.0.0 build
5> tsc index.ts --outDir dist
6
7
8 ——————————————————————————————————————————————————————————————————————————————————————————
9
10 NX Successfully ran target build for project is-even (33ms)
11
12 Nx read the output from the cache instead of running the command for 1 out of 1 tasks.
13
14
Note that the cache for the build
script was already populated when we ran it previously in this tutorial.
Running Multiple Tasks
To run the build
target for all the packages in the workspace, use:
~/workspace❯
npx nx run-many -t build
1 ✔ nx run is-even:build [existing outputs match the cache, left as is]
2 ✔ nx run is-odd:build [existing outputs match the cache, left as is]
3
4 ————————————————————————————————————————————————————————————————————————————————————————
5
6 NX Successfully ran target build for 2 projects (35ms)
7
8 Nx read the output from the cache instead of running the command for 2 out of 2 tasks.
9
Notice that both builds are replayed from cache. We can skip the cache by adding the --skip-nx-cache
option:
~/workspace❯
npx nx run-many -t build --skip-nx-cache
1 ✔ nx run is-even:build (1s)
2 ✔ nx run is-odd:build (1s)
3
4 ———————————————————————————————————————————————————————————————————————
5
6 NX Successfully ran target build for 2 projects (2s)
7
Notice that using this method, the is-even
build ran before the is-odd
build, and that the is-even
build only happened once. This demonstrates how run-many
is informed by the targetDefaults
we set earlier.
You can also only run tasks on packages that got changed by using the command:
~/workspace❯
npx nx affected -t build
1
2 NX Affected criteria defaulted to --base=main --head=HEAD
3
4
5 ✔ nx run is-even:build [existing outputs match the cache, left as is]
6 ✔ nx run is-odd:build [existing outputs match the cache, left as is]
7
8 ——————————————————————————————————————————————————————————————————————————————————————————————
9
10 NX Successfully ran target build for 2 projects (34ms)
11
12 Nx read the output from the cache instead of running the command for 2 out of 2 tasks.
13
Notice that the base
and head
options were populated with their default values. You could provide your own options here as needed. Notice too that the cache is also used with the affected
command.