Getting Started with Package-Based Repos

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:

packages/is-even/index.ts
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:

packages/is-odd/index.ts
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.

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 10NX Successfully ran target build for project is-even (33ms) 11 12Nx 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 6NX Successfully ran target build for 2 projects (35ms) 7 8Nx 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 6NX 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 2NX 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 10NX Successfully ran target build for 2 projects (34ms) 11 12Nx 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.

Learn More