Adding Nx to your Existing Project

Nx can be added to any type of project, not just monorepos. The main benefit is to get caching abilities for the package scripts. Each project usually has a set of scripts in the package.json:

package.json
1{ 2 ... 3 "scripts": { 4 "build": "tsc -p tsconfig.json", 5 "test": "jest", 6 "lint": "eslint . --ext .ts", 7 } 8} 9

You can make these scripts faster by leveraging Nx's caching capabilities. For example:

  • You change some spec files: in that case the build task can be cached and doesn't have to re-run.
  • You update your docs, changing a couple of markdown files: then there's no need to re-run builds, tests, linting on your CI. All you might want to do is trigger the Docusaurus build.

Installing Nx on a Non-Monorepo Project

Run the following command:

npx nx@latest init

This will

  • collect all the NPM scripts in the corresponding package.json files of your workspace packages
  • ask you which of those scripts are cacheable (e.g. build, test, lint)
  • ask you which of those scripts might need to be run in a certain order (e.g. if you run the build script you might want to first build all the dependent projects)
  • ask you for custom output folders that should be captured as part of the caching

This process adds nx to your package.json at the root of your workspace:

package.json
1{ 2 "name": "my-workspace", 3 ... 4 "devDependencies": { 5 ... 6 "nx": "15.3.0" 7 } 8} 9

In addition it generates a nx.json based on your answers during the setup process. This includes cacheable operations as well as some initial definition of the task pipeline. Here is an example:

nx.json
1{ 2 "tasksRunnerOptions": { 3 "default": { 4 "runner": "nx/tasks-runners/default", 5 "options": { 6 "cacheableOperations": ["build", "lint"] 7 } 8 } 9 } 10} 11

Wrapping Cacheable Scripts

Nx also automatically wraps your cacheable scripts with the nx exec command. The main advantage here is that you can still keep using npm start or npm run build (or other package manager's alternatives) as you're accustomed to. But still get the benefits of making those operations cacheble.

Here's an example of a build and lint script being wrapped by Nx:

package.json
1{ 2 ... 3 "scripts": { 4 "build": "nx exec -- vite build", 5 "lint": "nx exec -- eslint \"src/**/*.ts*\"", 6 ... 7 "dev": "vite", 8 "start": "vite --open", 9 }, 10 "devDependencies": { 11 ... 12 "nx": "15.3.0" 13 } 14} 15
Use Nx commands directly

Alternatively you could obviously also just switch to using nx for invoking the commands. Like nx build rather than npm run build.

Fine-tuning caching with Nx Inputs

To get the best caching results, you can customize which inputs should be accounted for when it comes to caching certain commands. This can be done in your nx.json.

For example, excluding markdown files from the lint task cache:

nx.json
1{ 2 ... 3 "targetDefaults": { 4 "lint": { 5 "inputs": ["{projectRoot}/**/*.ts","!**/*.md"] 6 } 7 } 8} 9

This includes all TypeScript files, but excludes markdown files. As a result, changing your README won't invalidate your "lint cache".

Learn more about Nx Inputs.

Learn More