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:

myorg/ ├── packages/ ├── nx.json └── package.json

Create a Package

The packages folder is where we host our monorepo libraries. Create a new is-even folder with the following structure:

packages/ └── is-even/ ├── index.ts └── package.json

Before proceeding, make sure you install TypeScript as we're going to use it to build our package. 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 i typescript -D -W

Now update the content of the files to match the following:

packages/is-even/index.ts
export const isEven = (x: number) => x % 2 === 0;

Update the .gitignore file to make sure you have a dist entry (without a leading slash) to ignore the dist folder inside the is-even package.

Note, the build uses TypeScript to compile the index.ts file into a project-level ./dist folder. To run it use:

npx nx build is-even

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 package.json at the root of this monorepo).

To illustrate how packages can be linked locally, let's create another package is-odd. You can copy the existing is-even package:

packages/ └── is-even/ ├── ... └── is-odd/ ├── index.ts └── package.json

Here's what the content of the files should look like:

packages/is-odd/index.ts
import { isEven } from '@package-based/is-even'; export const isOdd = (x: number) => !isEven(x);

is-odd imports the isEven function from @package-based/is-even. 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 (and it similar for Yarn or PNPM workspaces) to locally-link the two packages, without the need to publish them first to a NPM registry.

At the root of your workspace run

npm install

This allows the NPM workspace to properly link the new is-odd package.

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
{ ... "targetDefaults": { "build": { "dependsOn": ["^build"] } } }

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 npx nx build is-even 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

To build the is-even package run:

npx nx build is-even

Run the same command a second time and you'll see the build cache is being used:

Terminal

~/workspace

npx nx build is-even

> nx run is-even:build > @package-based/is-even@0.0.0 build > tsc index.ts --outDir dist ————————————————————————————————————————————————————————————————— > NX Successfully ran target build for project is-even (1s)

Running Multiple Tasks

To run the build target for all the packages in the workspace, use:

npx nx run-many --target=build

What you would get is the following:

Terminal

~/workspace

npx nx run-many --target=build

✔ nx run is-even:build [existing outputs match the cache, left as is] ✔ nx run is-odd:build (906ms) ————————————————————————————————————————————————————————————————— > NX Successfully ran target build for 2 projects (914ms) Nx read the output from the cache instead of running the command for 1 out of 2 tasks.

Note how on the is-even:build it didn't run the build but rather pulled it out of the cache because the build has ran before. If you re-run the run-many command all of the builds would be cached.

You can also only run tasks on packages that got changed by using

npx nx affected --target=build

Learn More