docs(core): project linking concept and recipe (#29652)

Adds two pages:
- Concept page describing two options for [TypeScript project
linking](https://nx-dev-git-docs-workspaces-project-linking-nrwl.vercel.app/concepts/typescript-project-linking)
(workspaces and path aliases).
- Also describes performance benefits of TS Project References that are
possible with workspaces
- Recipe describing how to manually [switch from path aliases to
workspaces project
linking](https://nx-dev-git-docs-workspaces-project-linking-nrwl.vercel.app/recipes/tips-n-tricks/switch-to-workspaces-project-references)
This commit is contained in:
Isaac Mann 2025-01-22 22:11:06 -05:00 committed by GitHub
parent 7524356180
commit 8357a2270a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 686 additions and 320 deletions

View File

@ -630,6 +630,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "TypeScript Project Linking",
"path": "/concepts/typescript-project-linking",
"id": "typescript-project-linking",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Nx and Turborepo",
"path": "/concepts/turbo-and-nx",
@ -843,6 +851,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "TypeScript Project Linking",
"path": "/concepts/typescript-project-linking",
"id": "typescript-project-linking",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Nx and Turborepo",
"path": "/concepts/turbo-and-nx",
@ -1954,6 +1970,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Switch to Workspaces and TS Project References",
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references",
"id": "switch-to-workspaces-project-references",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Enable Typescript Batch Mode",
"path": "/recipes/tips-n-tricks/enable-tsc-batch-mode",
@ -3680,6 +3704,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Switch to Workspaces and TS Project References",
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references",
"id": "switch-to-workspaces-project-references",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Enable Typescript Batch Mode",
"path": "/recipes/tips-n-tricks/enable-tsc-batch-mode",
@ -3811,6 +3843,14 @@
"children": [],
"disableCollapsible": false
},
{
"name": "Switch to Workspaces and TS Project References",
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references",
"id": "switch-to-workspaces-project-references",
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Enable Typescript Batch Mode",
"path": "/recipes/tips-n-tricks/enable-tsc-batch-mode",
@ -8235,14 +8275,6 @@
"isExternal": false,
"children": [],
"disableCollapsible": false
},
{
"name": "Configure TypeScript Project References in an Nx Workspace",
"path": "/nx-api/js/documents/typescript-project-references",
"id": "typescript-project-references",
"isExternal": false,
"children": [],
"disableCollapsible": false
}
],
"isExternal": false,

View File

@ -1168,17 +1168,6 @@
"path": "/nx-api/js/documents/overview",
"tags": [],
"originalFilePath": "shared/packages/js/js-plugin"
},
"/nx-api/js/documents/typescript-project-references": {
"id": "typescript-project-references",
"name": "Configure TypeScript Project References in an Nx Workspace",
"description": "The JS plugin for Nx contains executors and generators that provide the best experience for developing JavaScript and TypeScript projects. ",
"file": "generated/packages/js/documents/typescript-project-references",
"itemList": [],
"isExternal": false,
"path": "/nx-api/js/documents/typescript-project-references",
"tags": [],
"originalFilePath": "shared/packages/js/typescript-project-references"
}
},
"root": "/packages/js",

View File

@ -864,6 +864,17 @@
"path": "/concepts/sync-generators",
"tags": ["sync"]
},
{
"id": "typescript-project-linking",
"name": "TypeScript Project Linking",
"description": "",
"mediaImage": "",
"file": "shared/concepts/typescript-project-linking",
"itemList": [],
"isExternal": false,
"path": "/concepts/typescript-project-linking",
"tags": ["project-linking"]
},
{
"id": "turbo-and-nx",
"name": "Nx and Turborepo",
@ -1157,6 +1168,17 @@
"path": "/concepts/sync-generators",
"tags": ["sync"]
},
"/concepts/typescript-project-linking": {
"id": "typescript-project-linking",
"name": "TypeScript Project Linking",
"description": "",
"mediaImage": "",
"file": "shared/concepts/typescript-project-linking",
"itemList": [],
"isExternal": false,
"path": "/concepts/typescript-project-linking",
"tags": ["project-linking"]
},
"/concepts/turbo-and-nx": {
"id": "turbo-and-nx",
"name": "Nx and Turborepo",
@ -2675,6 +2697,17 @@
"path": "/recipes/tips-n-tricks/eslint",
"tags": []
},
{
"id": "switch-to-workspaces-project-references",
"name": "Switch to Workspaces and TS Project References",
"description": "",
"mediaImage": "",
"file": "shared/recipes/tips-n-tricks/switch-to-workspaces-project-references",
"itemList": [],
"isExternal": false,
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references",
"tags": ["project-linking"]
},
{
"id": "enable-tsc-batch-mode",
"name": "Enable Typescript Batch Mode",
@ -5042,6 +5075,17 @@
"path": "/recipes/tips-n-tricks/eslint",
"tags": []
},
{
"id": "switch-to-workspaces-project-references",
"name": "Switch to Workspaces and TS Project References",
"description": "",
"mediaImage": "",
"file": "shared/recipes/tips-n-tricks/switch-to-workspaces-project-references",
"itemList": [],
"isExternal": false,
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references",
"tags": ["project-linking"]
},
{
"id": "enable-tsc-batch-mode",
"name": "Enable Typescript Batch Mode",
@ -5223,6 +5267,17 @@
"path": "/recipes/tips-n-tricks/eslint",
"tags": []
},
"/recipes/tips-n-tricks/switch-to-workspaces-project-references": {
"id": "switch-to-workspaces-project-references",
"name": "Switch to Workspaces and TS Project References",
"description": "",
"mediaImage": "",
"file": "shared/recipes/tips-n-tricks/switch-to-workspaces-project-references",
"itemList": [],
"isExternal": false,
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references",
"tags": ["project-linking"]
},
"/recipes/tips-n-tricks/enable-tsc-batch-mode": {
"id": "enable-tsc-batch-mode",
"name": "Enable Typescript Batch Mode",

View File

@ -769,6 +769,22 @@
"path": "/nx-api/nx/documents/sync-check"
}
],
"project-linking": [
{
"description": "",
"file": "shared/concepts/typescript-project-linking",
"id": "typescript-project-linking",
"name": "TypeScript Project Linking",
"path": "/concepts/typescript-project-linking"
},
{
"description": "",
"file": "shared/recipes/tips-n-tricks/switch-to-workspaces-project-references",
"id": "switch-to-workspaces-project-references",
"name": "Switch to Workspaces and TS Project References",
"path": "/recipes/tips-n-tricks/switch-to-workspaces-project-references"
}
],
"module-federation": [
{
"description": "",

View File

@ -1152,17 +1152,6 @@
"path": "js/documents/overview",
"tags": [],
"originalFilePath": "shared/packages/js/js-plugin"
},
{
"id": "typescript-project-references",
"name": "Configure TypeScript Project References in an Nx Workspace",
"description": "The JS plugin for Nx contains executors and generators that provide the best experience for developing JavaScript and TypeScript projects. ",
"file": "generated/packages/js/documents/typescript-project-references",
"itemList": [],
"isExternal": false,
"path": "js/documents/typescript-project-references",
"tags": [],
"originalFilePath": "shared/packages/js/typescript-project-references"
}
],
"executors": [

View File

@ -1,141 +0,0 @@
# Configure TypeScript Project References in an Nx Workspace
In Nx 20, the `@nx/js` plugin provides the ability to incrementally build projects in a monorepo using [TypeScript Project References](https://www.typescriptlang.org/docs/handbook/project-references.html). Nx also provides a `ts` preset for `create-nx-workspace` that configures project references and uses `workspaces` to link projects instead of [TypeScript compilerOptions Paths](https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths).
The TypeScript team recommends using project references when working in a monorepo, but until now the configuration settings were difficult to maintain. Each project is required to list its own project dependencies in the `references` property of the `tsconfig.json` file so that TypeScript can incrementally compile projects in the correct order. In a large monorepo, maintaining those settings manually is cost prohibitive. To solve this problem, the `@nx/js` plugin registers a [sync generator](/concepts/sync-generators) to automatically update the references based on Nx's project graph before any TypeScript `build` task is executed.
## Create a New Nx Workspace Using Project References
We anticipate that this style of compiling projects will eventually become the default, but currently it will only be enabled for repositories configured in a specific way. Existing workspaces will continue to function as usual and there is no migration path yet. You can generate a new repository with these settings by using the `--preset=ts` flag of the `create-nx-workspace` command.
```shell
npx create-nx-workspace --preset=ts
```
{% callout type="note" title="Empty Workspace with Paths" %}
To generate an empty Nx workspace that links projects with the `compilerOptions.paths` property and does not use project references, use `create-nx-workspace --preset=apps`
{% /callout %}
This will generate an empty repository that is configured to use TypeScript project references. To see the new functionality in action, create some TypeScript projects and make sure to use the `tsc` bundler option.
```shell
nx g @nx/js:lib packages/cart --bundler=tsc
nx g @nx/js:lib packages/utils --bundler=tsc
```
These generators will detect that your repository is configured to use project references and update the configuration accordingly. If these generators were executed in an Nx repository that used `compilerOptions.paths`, they would update that setting instead.
To make `cart` depend on `utils`, update `packages/cart/package.json` like this:
```jsonc {% fileName="packages/cart/package.json" %}
{
"dependencies": {
"utils": "*"
}
}
```
Now if you run `nx build cart` or directly run `nx sync`, the `packages/cart/tsconfig.json` file will have its references updated for you.
## Project Reference Configuration Files
Nx expects the following configuration settings to be in place in order to use TypeScript project references to build projects. Most of this configuration is set up and maintained for you automatically by Nx.
Identify projects in the `workspaces` property in the root `package.json` file.
```json {% fileName="package.json" %}
{
"workspaces": ["packages/*"]
}
```
The root `tsconfig.base.json` should contain a `compilerOptions` property and no other properties. `compilerOptions.composite` and `compilerOptions.declaration` should be set to `true`. `compilerOptions.paths` should not be set.
```jsonc {% fileName="tsconfig.base.json" %}
{
"compilerOptions": {
// Required compiler options
"composite": true,
"declaration": true
// Other options...
}
}
```
The root `tsconfig.json` file should extend `tsconfig.base.json` and not include any files. It needs to have `references` for every project in the repository so that editor tooling works correctly.
```jsonc {% fileName="tsconfig.json" %}
{
"extends": "./tsconfig.base.json",
"files": [], // intentionally empty
"references": [
// UPDATED BY PROJECT GENERATORS
// All projects in the repository
]
}
```
Each project's `tsconfig.json` file should extend the `tsconfig.base.json` file and list `references` to the project's dependencies.
```jsonc {% fileName="packages/cart/tsconfig.json" %}
{
"extends": "../../tsconfig.base.json",
"files": [], // intentionally empty
"references": [
// UPDATED BY NX SYNC
// All project dependencies
{
"path": "../utils"
},
// This project's other tsconfig.*.json files
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
```
Each project's `tsconfig.lib.json` file extends the project's `tsconfig.json` file and adds `references` to the `tsconfig.lib.json` files of project dependencies.
```jsonc {% fileName="packages/cart/tsconfig.lib.json" %}
{
"extends": "./tsconfig.json",
"compilerOptions": {
// Any overrides
},
"include": ["src/**/*.ts"],
"exclude": [
// exclude config and test files
],
"references": [
// UPDATED BY NX SYNC
// tsconfig.lib.json files for project dependencies
{
"path": "../utils/tsconfig.lib.json"
}
]
}
```
The project's `tsconfig.spec.json` does not need to reference project dependencies.
```jsonc {% fileName="packages/cart/tsconfig.spec.json" %}
{
"extends": "./tsconfig.json",
"compilerOptions": {
// Any overrides
},
"include": [
// test files
],
"references": [
// tsconfig.lib.json for this project
{
"path": "./tsconfig.lib.json"
}
]
}
```

View File

@ -254,6 +254,12 @@
"tags": ["sync"],
"file": "shared/concepts/sync-generators"
},
{
"name": "TypeScript Project Linking",
"id": "typescript-project-linking",
"tags": ["project-linking"],
"file": "shared/concepts/typescript-project-linking"
},
{
"name": "Nx and Turborepo",
"id": "turbo-and-nx",
@ -1012,6 +1018,12 @@
"id": "eslint",
"file": "shared/eslint"
},
{
"name": "Switch to Workspaces and TS Project References",
"id": "switch-to-workspaces-project-references",
"tags": ["project-linking"],
"file": "shared/recipes/tips-n-tricks/switch-to-workspaces-project-references"
},
{
"name": "Enable Typescript Batch Mode",
"id": "enable-tsc-batch-mode",
@ -2303,12 +2315,6 @@
"id": "overview",
"path": "/nx-api/js",
"file": "shared/packages/js/js-plugin"
},
{
"name": "Configure TypeScript Project References in an Nx Workspace",
"id": "typescript-project-references",
"path": "/nx-api/js/typescript-project-references",
"file": "shared/packages/js/typescript-project-references"
}
]
},

View File

@ -0,0 +1,302 @@
# Typescript Project Linking
The naive way to reference code in a separate project is to use a relative path in the `import` statement.
```ts
import { someFunction } from '../../teamA/otherProject';
const result = someFunction();
```
The problem with this approach is that all your import statements become tied to your folder structure. Developers need to know the full path to any project from which they want to import code. Also, if `otherProject` ever moves locations, there will be superfluous code changes across the entire repository.
A more ergonomic solution is to reference your local projects as if they were external npm packages and then use a project linking mechanism to automatically resolve the project file path behind the scenes.
```ts
import { someFunction } from '@myorg/otherProject';
const result = someFunction();
```
There are two different methods that Nx supports for linking TypeScript projects: package manager workspaces and TypeScript path aliases. Project linking with TS path aliases was available with Nx before package managers offered a workspaces project linking approach. The Nx Team has since added full support for workspaces because (1) it has become more common across the TypeScript ecosystem and (2) packages will be resolved using native node module resolution instead of relying on TypeScript. Nx provides a cohesive experience for repositories using TypeScript path aliases without project references or repositories using package manager workspaces with TypeScript project references enabled.
## Project Linking with Workspaces
To create a new Nx workspace that links projects with package manager workspaces, use the `--workspaces` flag.
```shell
npx create-nx-workspace --workspaces
```
### Set Up Package Manager Workspaces
The configuration for package manager workspaces varies based on which package manager you're using.
{% tabs %}
{% tab label="npm" %}
```json {% fileName="package.json" %}
{
"workspaces": ["apps/**", "packages/**"]
}
```
Defining the `workspaces` property in the root `package.json` file lets npm know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `npm install` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
If you want to reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `*` specified as the library's version. `*` tells npm to use whatever version of the project is available.
```json {% fileName="/apps/my-app/package.json" %}
{
"devDependencies": {
"@my-org/some-project": "*"
}
}
```
{% /tab %}
{% tab label="yarn" %}
```json {% fileName="package.json" %}
{
"workspaces": ["apps/**", "packages/**"]
}
```
Defining the `workspaces` property in the root `package.json` file lets yarn know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `yarn` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
If you want to reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells yarn that the project is in the same repository](https://yarnpkg.com/features/workspaces) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
```json {% fileName="/apps/my-app/package.json" %}
{
"devDependencies": {
"@my-org/some-project": "*"
}
}
```
{% /tab %}
{% tab label="bun" %}
```json {% fileName="package.json" %}
{
"workspaces": ["apps/**", "packages/**"]
}
```
Defining the `workspaces` property in the root `package.json` file lets bun know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `bun install` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
If you want to reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells bun that the project is in the same repository](https://bun.sh/docs/install/workspaces) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
```json {% fileName="/apps/my-app/package.json" %}
{
"devDependencies": {
"@my-org/some-project": "workspace:*"
}
}
```
{% /tab %}
{% tab label="pnpm" %}
```yaml {% fileName="pnpm-workspace.yaml" %}
packages:
- 'apps/**'
- 'packages/**'
```
Defining the `packages` property in the root `pnpm-workspaces.yaml` file lets pnpm know to look for project `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `pnpm install` is run in the root folder.
If you want to reference a local library project from an application, you need to include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells pnpm that the project is in the same repository](https://pnpm.io/workspaces#workspace-protocol-workspace) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
```json {% fileName="/apps/my-app/package.json" %}
{
"devDependencies": {
"@my-org/some-project": "workspace:*"
}
}
```
{% /tab %}
{% /tabs %}
### Set Up TypeScript Project References
With workspaces enabled, you can also configure TypeScript project references to speed up your build and typecheck tasks.
The root `tsconfig.base.json` should contain a `compilerOptions` property and no other properties. `compilerOptions.composite` and `compilerOptions.declaration` should be set to `true`. `compilerOptions.paths` should not be set.
```jsonc {% fileName="tsconfig.base.json" %}
{
"compilerOptions": {
// Required compiler options
"composite": true,
"declaration": true,
"declarationMaps": true
// Other options...
}
}
```
The root `tsconfig.json` file should extend `tsconfig.base.json` and not include any files. It needs to have `references` for every project in the repository so that editor tooling works correctly.
```jsonc {% fileName="tsconfig.json" %}
{
"extends": "./tsconfig.base.json",
"files": [], // intentionally empty
"references": [
// UPDATED BY PROJECT GENERATORS
// All projects in the repository
]
}
```
#### Individual Project TypeScript Configuration
Each project's `tsconfig.json` file should extend the `tsconfig.base.json` file and list `references` to the project's dependencies.
```jsonc {% fileName="packages/cart/tsconfig.json" %}
{
"extends": "../../tsconfig.base.json",
"files": [], // intentionally empty
"references": [
// UPDATED BY NX SYNC
// All project dependencies
{
"path": "../utils"
},
// This project's other tsconfig.*.json files
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
```
Each project's `tsconfig.lib.json` file extends the project's `tsconfig.json` file and adds `references` to the `tsconfig.lib.json` files of project dependencies.
```jsonc {% fileName="packages/cart/tsconfig.lib.json" %}
{
"extends": "./tsconfig.json",
"compilerOptions": {
// Any overrides
},
"include": ["src/**/*.ts"],
"exclude": [
// exclude config and test files
],
"references": [
// UPDATED BY NX SYNC
// tsconfig.lib.json files for project dependencies
{
"path": "../utils/tsconfig.lib.json"
}
]
}
```
The project's `tsconfig.spec.json` does not need to reference project dependencies.
```jsonc {% fileName="packages/cart/tsconfig.spec.json" %}
{
"extends": "./tsconfig.json",
"compilerOptions": {
// Any overrides
},
"include": [
// test files
],
"references": [
// tsconfig.lib.json for this project
{
"path": "./tsconfig.lib.json"
}
]
}
```
### TypeScript Project References Performance Benefits
Using TypeScript project references improves both the speed and memory usage of build and typecheck tasks. The repository below contains benchmarks showing the difference between running typecheck with and without using TypeScript project references.
{% github-repository title="TypeScript Project References Benchmark" url="https://github.com/jaysoo/typecheck-timings" /%}
Here are the baseline typecheck task performance results.
```text
Typecheck without using project references: 186 seconds, max memory 6.14 GB
```
Using project references allows the TypeScript compiler to individually check the types for each project and store the results of that calculation in a `.tsbuildinfo` file for later use. Because of this, the TypeScript compiler does not need to load the entire codebase into memory at the same time, which you can see from the decreased memory usage on the first run with project references enabled.
```text
Typecheck with project references first run: 175 seconds, max memory 945 MB
```
Once the `.tsbuildinfo` files have been created, subsequent runs will be much faster.
```text
Typecheck with all `.tsbuildinfo` files created: 25 seconds, max memory 429 MB
```
Even if some projects have been updated and individual projects need to be type checked again, the TypeScript compiler can still use the cached `.tsbuildinfo` files for any projects that were not affected. This is very similar to the way Nx's caching and affected features work.
```text
Typecheck (1 pkg updated): 36.33 seconds, max memory 655.14 MB
Typecheck (5 pkg updated): 48.21 seconds, max memory 702.96 MB
Typecheck (25 pkg updated): 65.25 seconds, max memory 666.78 MB
Typecheck (100 pkg updated): 80.69 seconds, max memory 664.58 MB
Typecheck (1 nested leaf pkg updated): 26.66 seconds, max memory 407.54 MB
Typecheck (2 nested leaf pkg updated): 31.17 seconds, max memory 889.86 MB
Typecheck (1 nested root pkg updated): 26.67 seconds, max memory 393.78 MB
```
These performance benefits will be more noticeable for larger repositories, but even small code bases will see some benefits.
### Local TypeScript Path Aliases
If you define TS path aliases in an individual project's tsconfig files, you should not define them also in the root `tsconfig.base.json` file because TypeScript does not merge the paths. The paths defined in the root file would be completely overwritten by the ones defined in the project tsconfig. For instance, you could define paths like this in an application's tsconfig file.
```jsonc {% fileName="/apps/my-remix-app/tsconfig.app.json" %}
{
"compilerOptions": {
"paths": {
"#app/*": ["./app/*"],
"#tests/*": ["./tests/*"],
"@/icon-name": [
"./app/components/ui/icons/name.d.ts",
"./types/icon-name.d.ts"
]
}
}
}
```
## Project Linking with TypeScript Path Aliases
{% callout type="warning" title="Path Aliases Overwrite Extended Configuration Files" %}
If you define path aliases in a project's specific `tsconfig.*.json` file, those path aliases will overwrite the path aliases defined in the root `tsconfig.base.json`. You can't use both project-level path aliases and root path aliases.
{% /callout %}
Linking projects with TypeScript path aliases is configured entirely in the tsconfig files. You can still use package manager workspaces to enable you to define separate third-party dependencies for individual projects, but the local project linking is done by TypeScript instead of the package manager.
The paths for each library are defined in the root `tsconfig.base.json` and each project's `tsconfig.json` should extend that file. Note that application projects do not need to have a path defined because no projects will import code from a top-level application.
```jsonc {% fileName="/tsconfig.base.json" %}
{
"compilerOptions": {
// common compiler option defaults for all projects
// ...
// These compiler options must be false or undefined
"composite": false,
"declaration": false,
"paths": {
// These paths are automatically added by Nx library generators
"@myorg/shared-ui": ["packages/shared-ui/src/index.ts"]
// ...
}
}
}
```

View File

@ -1,141 +0,0 @@
# Configure TypeScript Project References in an Nx Workspace
In Nx 20, the `@nx/js` plugin provides the ability to incrementally build projects in a monorepo using [TypeScript Project References](https://www.typescriptlang.org/docs/handbook/project-references.html). Nx also provides a `ts` preset for `create-nx-workspace` that configures project references and uses `workspaces` to link projects instead of [TypeScript compilerOptions Paths](https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths).
The TypeScript team recommends using project references when working in a monorepo, but until now the configuration settings were difficult to maintain. Each project is required to list its own project dependencies in the `references` property of the `tsconfig.json` file so that TypeScript can incrementally compile projects in the correct order. In a large monorepo, maintaining those settings manually is cost prohibitive. To solve this problem, the `@nx/js` plugin registers a [sync generator](/concepts/sync-generators) to automatically update the references based on Nx's project graph before any TypeScript `build` task is executed.
## Create a New Nx Workspace Using Project References
We anticipate that this style of compiling projects will eventually become the default, but currently it will only be enabled for repositories configured in a specific way. Existing workspaces will continue to function as usual and there is no migration path yet. You can generate a new repository with these settings by using the `--preset=ts` flag of the `create-nx-workspace` command.
```shell
npx create-nx-workspace --preset=ts
```
{% callout type="note" title="Empty Workspace with Paths" %}
To generate an empty Nx workspace that links projects with the `compilerOptions.paths` property and does not use project references, use `create-nx-workspace --preset=apps`
{% /callout %}
This will generate an empty repository that is configured to use TypeScript project references. To see the new functionality in action, create some TypeScript projects and make sure to use the `tsc` bundler option.
```shell
nx g @nx/js:lib packages/cart --bundler=tsc
nx g @nx/js:lib packages/utils --bundler=tsc
```
These generators will detect that your repository is configured to use project references and update the configuration accordingly. If these generators were executed in an Nx repository that used `compilerOptions.paths`, they would update that setting instead.
To make `cart` depend on `utils`, update `packages/cart/package.json` like this:
```jsonc {% fileName="packages/cart/package.json" %}
{
"dependencies": {
"utils": "*"
}
}
```
Now if you run `nx build cart` or directly run `nx sync`, the `packages/cart/tsconfig.json` file will have its references updated for you.
## Project Reference Configuration Files
Nx expects the following configuration settings to be in place in order to use TypeScript project references to build projects. Most of this configuration is set up and maintained for you automatically by Nx.
Identify projects in the `workspaces` property in the root `package.json` file.
```json {% fileName="package.json" %}
{
"workspaces": ["packages/*"]
}
```
The root `tsconfig.base.json` should contain a `compilerOptions` property and no other properties. `compilerOptions.composite` and `compilerOptions.declaration` should be set to `true`. `compilerOptions.paths` should not be set.
```jsonc {% fileName="tsconfig.base.json" %}
{
"compilerOptions": {
// Required compiler options
"composite": true,
"declaration": true
// Other options...
}
}
```
The root `tsconfig.json` file should extend `tsconfig.base.json` and not include any files. It needs to have `references` for every project in the repository so that editor tooling works correctly.
```jsonc {% fileName="tsconfig.json" %}
{
"extends": "./tsconfig.base.json",
"files": [], // intentionally empty
"references": [
// UPDATED BY PROJECT GENERATORS
// All projects in the repository
]
}
```
Each project's `tsconfig.json` file should extend the `tsconfig.base.json` file and list `references` to the project's dependencies.
```jsonc {% fileName="packages/cart/tsconfig.json" %}
{
"extends": "../../tsconfig.base.json",
"files": [], // intentionally empty
"references": [
// UPDATED BY NX SYNC
// All project dependencies
{
"path": "../utils"
},
// This project's other tsconfig.*.json files
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
```
Each project's `tsconfig.lib.json` file extends the project's `tsconfig.json` file and adds `references` to the `tsconfig.lib.json` files of project dependencies.
```jsonc {% fileName="packages/cart/tsconfig.lib.json" %}
{
"extends": "./tsconfig.json",
"compilerOptions": {
// Any overrides
},
"include": ["src/**/*.ts"],
"exclude": [
// exclude config and test files
],
"references": [
// UPDATED BY NX SYNC
// tsconfig.lib.json files for project dependencies
{
"path": "../utils/tsconfig.lib.json"
}
]
}
```
The project's `tsconfig.spec.json` does not need to reference project dependencies.
```jsonc {% fileName="packages/cart/tsconfig.spec.json" %}
{
"extends": "./tsconfig.json",
"compilerOptions": {
// Any overrides
},
"include": [
// test files
],
"references": [
// tsconfig.lib.json for this project
{
"path": "./tsconfig.lib.json"
}
]
}
```

View File

@ -0,0 +1,257 @@
# Switch to Workspaces and Project References
If you want to take advantage of the [performance benefits](/concepts/typescript-project-linking#typescript-project-references-performance-benefits) of TypeScript project references, it is recommended to use package manager workspaces for local [project linking](/concepts/typescript-project-linking). If you are currently using TypeScript path aliases for project linking, follow the steps in this guide to switch to workspaces project linking and enable TypeScript project references.
## Enable Package Manager Workspaces
Follow the specific instructions for your package manager to enable workspaces project linking.
{% tabs %}
{% tab label="npm" %}
```json {% fileName="package.json" %}
{
"workspaces": ["apps/**", "libs/**"]
}
```
Defining the `workspaces` property in the root `package.json` file lets npm know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `npm install` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
If you reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `*` specified as the library's version. `*` tells npm to use whatever version of the project is available.
```json {% fileName="/apps/my-app/package.json" %}
{
"devDependencies": {
"@my-org/some-project": "*"
}
}
```
{% /tab %}
{% tab label="yarn" %}
```json {% fileName="package.json" %}
{
"workspaces": ["apps/**", "libs/**"]
}
```
Defining the `workspaces` property in the root `package.json` file lets yarn know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `yarn` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
If you reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells yarn that the project is in the same repository](https://yarnpkg.com/features/workspaces) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
```json {% fileName="/apps/my-app/package.json" %}
{
"devDependencies": {
"@my-org/some-project": "*"
}
}
```
{% /tab %}
{% tab label="bun" %}
```json {% fileName="package.json" %}
{
"workspaces": ["apps/**", "libs/**"]
}
```
Defining the `workspaces` property in the root `package.json` file lets bun know to look for other `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `bun install` is run in the root folder. Also, the projects themselves will be linked in the root `node_modules` folder to be accessed as if they were npm packages.
If you reference a local library project with its own `build` task, you should include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells bun that the project is in the same repository](https://bun.sh/docs/install/workspaces) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
```json {% fileName="/apps/my-app/package.json" %}
{
"devDependencies": {
"@my-org/some-project": "workspace:*"
}
}
```
{% /tab %}
{% tab label="pnpm" %}
```yaml {% fileName="pnpm-workspace.yaml" %}
packages:
- 'apps/**'
- 'libs/**'
```
Defining the `packages` property in the root `pnpm-workspaces.yaml` file lets pnpm know to look for project `package.json` files in the specified folders. With this configuration in place, all the dependencies for the individual projects will be installed in the root `node_modules` folder when `pnpm install` is run in the root folder.
If you reference a local library project from an application, you need to include the library in the `devDependencies` of the application's `package.json` with `workspace:*` specified as the library's version. [`workspace:*` tells pnpm that the project is in the same repository](https://pnpm.io/workspaces#workspace-protocol-workspace) and not an npm package. You want to specify local projects as `devDependencies` instead of `dependencies` so that the library is not included twice in the production bundle of the application.
```json {% fileName="/apps/my-app/package.json" %}
{
"devDependencies": {
"@my-org/some-project": "workspace:*"
}
}
```
{% /tab %}
{% /tabs %}
## Update Root TypeScript Configuration
The root `tsconfig.base.json` should contain a `compilerOptions` property and no other properties. `compilerOptions.composite` and `compilerOptions.declaration` should be set to `true`. `compilerOptions.paths` and `compilerOptions.rootDir` should not be set.
Note: Before you delete the `paths` property, copy the project paths for use as `references` in the `tsconfig.json` file.
{% tabs %}
{% tab label="Before" %}
```jsonc {% fileName="tsconfig.base.json" %}
{
"compilerOptions": {
"allowJs": false,
"allowSyntheticDefaultImports": true,
// ...
"paths": {
"@myorg/utils": ["libs/utils/src/index.ts"],
"@myorg/ui": ["libs/ui/src/index.ts"]
}
}
}
```
{% /tab %}
{% tab label="After" %}
```jsonc {% fileName="tsconfig.base.json" %}
{
"compilerOptions": {
// Required compiler options
"composite": true,
"declaration": true, // defaults to true when composite is true
// Delete the paths property
// Other options...
"allowJs": false,
"allowSyntheticDefaultImports": true
}
}
```
{% /tab %}
{% /tabs %}
The root `tsconfig.json` file should extend `tsconfig.base.json` and not include any files. It needs to have `references` for every project in the repository so that editor tooling works correctly.
{% tabs %}
{% tab label="Before" %}
```jsonc {% fileName="tsconfig.json" %}
{
"extends": "./tsconfig.base.json",
"files": [] // intentionally empty
}
```
{% /tab %}
{% tab label="After" %}
```jsonc {% fileName="tsconfig.json" %}
{
"extends": "./tsconfig.base.json",
"files": [], // intentionally empty
"references": [
// All projects in the repository
{
"path": "./libs/utils"
},
{
"path": "./libs/ui"
}
// Future generated projects will automatically be added here by the generator
]
}
```
{% /tab %}
{% /tabs %}
## Create Individual Project package.json files
When using package manager project linking, every project needs to have a `package.json` file. You can leave all the task configuration in the existing `project.json` file. For application projects, you only need to specify the `name` property. For library projects, you should add an `exports` property that accounts for any TypeScript path aliases that referenced the project. A typical configuration is shown below:
```json {% fileName="libs/ui/package.json" %}
{
"name": "@myorg/ui",
"exports": {
".": "./src/index.js"
}
}
```
{% callout type="warning" title="Package Names with Multiple Slashes" %}
The `package.json` name can only have one `/` character in it. This is more restrictive than the TypeScript path aliases. So if you have a project that you have been referencing with `@myorg/shared/ui`, you'll need to make the `package.json` name be something like `@myorg/shared-ui` and update all the import statements in your codebase to reference the new name.
{% /callout %}
## Update Individual Project TypeScript Configuration
Each project's `tsconfig.json` file should extend the `tsconfig.base.json` file and list `references` to the project's dependencies.
```jsonc {% fileName="libs/ui/tsconfig.json" %}
{
"extends": "../../tsconfig.base.json",
"files": [], // intentionally empty
"references": [
// All project dependencies
// UPDATED BY NX SYNC
// This project's other tsconfig.*.json files
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
```
Each project's `tsconfig.lib.json` file extends the project's `tsconfig.json` file and adds `references` to the `tsconfig.lib.json` files of project dependencies.
```jsonc {% fileName="libs/ui/tsconfig.lib.json" %}
{
"extends": "./tsconfig.json",
"compilerOptions": {
// Any overrides
},
"include": ["src/**/*.ts"],
"exclude": [
// exclude config and test files
],
"references": [
// tsconfig.lib.json files for project dependencies
// UPDATED BY NX SYNC
]
}
```
The project's `tsconfig.spec.json` does not need to reference project dependencies.
```jsonc {% fileName="libs/ui/tsconfig.spec.json" %}
{
"extends": "./tsconfig.json",
"compilerOptions": {
// Any overrides
},
"include": [
// test files
],
"references": [
// tsconfig.lib.json for this project
{
"path": "./tsconfig.lib.json"
}
]
}
```
After creating these `tsconfig.*.json` files, run `nx sync` to have Nx automatically add the correct references for each project.
## Future Plans
We realize that this manual migration process is tedious. We are investigating automating parts of this process with generators.

View File

@ -33,6 +33,7 @@
- [Common Tasks](/concepts/common-tasks)
- [Nx Daemon](/concepts/nx-daemon)
- [Sync Generators](/concepts/sync-generators)
- [TypeScript Project Linking](/concepts/typescript-project-linking)
- [Nx and Turborepo](/concepts/turbo-and-nx)
- [Buildable and Publishable Libraries](/concepts/buildable-and-publishable-libraries)
- [Module Federation](/concepts/module-federation)
@ -158,6 +159,7 @@
- [Tips and tricks](/recipes/tips-n-tricks)
- [Convert from a Standalone Repository to a Monorepo](/recipes/tips-n-tricks/standalone-to-monorepo)
- [Configuring ESLint with Typescript](/recipes/tips-n-tricks/eslint)
- [Switch to Workspaces and TS Project References](/recipes/tips-n-tricks/switch-to-workspaces-project-references)
- [Enable Typescript Batch Mode](/recipes/tips-n-tricks/enable-tsc-batch-mode)
- [Define Secondary Entrypoints for Typescript Packages](/recipes/tips-n-tricks/define-secondary-entrypoints)
- [Compile Typescript Packages to Multiple Formats](/recipes/tips-n-tricks/compile-multiple-formats)
@ -482,7 +484,6 @@
- [js](/nx-api/js)
- [documents](/nx-api/js/documents)
- [Overview](/nx-api/js/documents/overview)
- [Configure TypeScript Project References in an Nx Workspace](/nx-api/js/documents/typescript-project-references)
- [executors](/nx-api/js/executors)
- [tsc](/nx-api/js/executors/tsc)
- [swc](/nx-api/js/executors/swc)

View File

@ -1000,7 +1000,8 @@ const latestRecipesRefactoring = {
// nx concepts
'/recipes/module-federation/faster-builds':
'/concepts/more-concepts/faster-builds-with-module-federation',
'/nx-api/js/documents/typescript-project-references':
'/concepts/typescript-project-linking',
'/reference/commands': '/reference/nx-commands',
};